mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Merge pull request #52 from LD-Reborn/10-feature-implement-groups-backend
10 feature implement groups backend
This commit is contained in:
117
src/Controllers/GroupsController.cs
Normal file
117
src/Controllers/GroupsController.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using Berufsschule_HAM.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Novell.Directory.Ldap;
|
||||||
|
using Berufsschule_HAM.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class GroupsController : Controller
|
||||||
|
{
|
||||||
|
private readonly LdapService _ldap;
|
||||||
|
private readonly ILogger<UsersController> _logger;
|
||||||
|
|
||||||
|
public GroupsController(LdapService ldap, ILogger<UsersController> logger)
|
||||||
|
{
|
||||||
|
_ldap = ldap;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Index")]
|
||||||
|
public async Task<IEnumerable<GroupModel>> Index(GroupsIndexRequestModel requestModel)
|
||||||
|
{
|
||||||
|
string? cn = requestModel.Cn;
|
||||||
|
List<string> attributes = [.. _ldap.GroupsAttributes];
|
||||||
|
if (!requestModel.GidNumber) attributes.Remove("gidNumber");
|
||||||
|
if (!requestModel.Permissions) attributes.Remove("description");
|
||||||
|
IEnumerable<GroupModel> groups;
|
||||||
|
if (cn is null)
|
||||||
|
{
|
||||||
|
groups = await _ldap.ListGroupsAsync([.. attributes]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
groups = [await _ldap.GetGroupByCnAsync(cn, [.. attributes])];
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
groups = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Delete")]
|
||||||
|
public async Task<bool> Delete(string uid)
|
||||||
|
{
|
||||||
|
return await Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ldap.DeleteGroup(uid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Create")]
|
||||||
|
public async Task<bool> Create(string cn, string gidNumber, GroupPermission[] permissions, string description)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
description ??= JsonSerializer.Serialize(new GroupPermissions() {Permissions = []});
|
||||||
|
|
||||||
|
LdapAttributeSet attributeSet =
|
||||||
|
[
|
||||||
|
new LdapAttribute("objectClass", "posixGroup"),
|
||||||
|
new LdapAttribute("objectClass", "top"),
|
||||||
|
new LdapAttribute("cn", cn),
|
||||||
|
new LdapAttribute("gidNumber", gidNumber),
|
||||||
|
new LdapAttribute("description",
|
||||||
|
JsonSerializer.Serialize(
|
||||||
|
new GroupPermissions()
|
||||||
|
{
|
||||||
|
Permissions = [.. permissions]
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
await _ldap.CreateGroup(cn, attributeSet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to create user: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("Update")]
|
||||||
|
public async Task<bool> Update([FromBody]GroupsModifyRequestModel requestModel)
|
||||||
|
{
|
||||||
|
if (requestModel is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to update a group because the GroupsModifyRequestModel is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
string cn = requestModel.Cn;
|
||||||
|
|
||||||
|
if (requestModel.NewCn is not null)
|
||||||
|
{
|
||||||
|
await _ldap.UpdateGroup(cn, "cn", requestModel.NewCn);
|
||||||
|
cn = requestModel.NewCn;
|
||||||
|
}
|
||||||
|
if (requestModel.GidNumber is not null)
|
||||||
|
{
|
||||||
|
await _ldap.UpdateGroup(cn, "gidNumber", requestModel.GidNumber);
|
||||||
|
}
|
||||||
|
if (requestModel.Permissions is not null)
|
||||||
|
{
|
||||||
|
await _ldap.UpdateGroup(cn, "description", JsonSerializer.Serialize(requestModel.Permissions));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/Exceptions/ConfigurationExceptions.cs
Normal file
3
src/Exceptions/ConfigurationExceptions.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Berufsschule_HAM.Exceptions;
|
||||||
|
|
||||||
|
public class GroupModelConfigurationException : Exception {}
|
||||||
@@ -19,6 +19,9 @@ public class DatabaseHealthCheck : IHealthCheck
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _ldapService.ListUsersAsync();
|
await _ldapService.ListUsersAsync();
|
||||||
|
await _ldapService.ListDeviceAsync();
|
||||||
|
await _ldapService.ListGroupsAsync(["cn", "description"]);
|
||||||
|
await _ldapService.ListLocationsAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
42
src/Models/GroupModel.cs
Normal file
42
src/Models/GroupModel.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Berufsschule_HAM.Exceptions;
|
||||||
|
|
||||||
|
namespace Berufsschule_HAM.Models;
|
||||||
|
|
||||||
|
public class GroupModel
|
||||||
|
{
|
||||||
|
public required string Cn { get; set; }
|
||||||
|
public string? GidNumber { get; set; }
|
||||||
|
public List<GroupPermission> Permissions { get; set; }
|
||||||
|
public GroupModel(Dictionary<string, string> ldapData)
|
||||||
|
{
|
||||||
|
Cn = ldapData.GetValueOrDefault("cn") ?? throw new GroupModelConfigurationException();
|
||||||
|
GidNumber = ldapData.GetValueOrDefault("gidNumber");
|
||||||
|
string? descriptionValue = ldapData.GetValueOrDefault("description");
|
||||||
|
if (descriptionValue is null)
|
||||||
|
{
|
||||||
|
Permissions = [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Permissions = JsonSerializer.Deserialize<GroupPermissions>(descriptionValue)?.Permissions ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GroupPermissions
|
||||||
|
{
|
||||||
|
public required List<GroupPermission> Permissions { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum GroupPermission
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
CanInventorize,
|
||||||
|
CanManageUsers,
|
||||||
|
CanManageLocations,
|
||||||
|
CanManageAssets,
|
||||||
|
CanManageGroups
|
||||||
|
}
|
||||||
16
src/Models/GroupsRequestModels.cs
Normal file
16
src/Models/GroupsRequestModels.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Berufsschule_HAM.Models;
|
||||||
|
|
||||||
|
public class GroupsIndexRequestModel
|
||||||
|
{
|
||||||
|
public string? Cn { get; set; }
|
||||||
|
public bool GidNumber { get; set; } = true;
|
||||||
|
public bool Permissions { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GroupsModifyRequestModel
|
||||||
|
{
|
||||||
|
public required string Cn { get; set; }
|
||||||
|
public string? NewCn { get; set; } = null;
|
||||||
|
public string? GidNumber { get; set; } = null;
|
||||||
|
public GroupPermissions? Permissions { get; set; } = null;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
|||||||
using Berufsschule_HAM.Services;
|
using Berufsschule_HAM.Services;
|
||||||
using Berufsschule_HAM.Models;
|
using Berufsschule_HAM.Models;
|
||||||
using Berufsschule_HAM.HealthChecks;
|
using Berufsschule_HAM.HealthChecks;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Bind options
|
// Bind options
|
||||||
@@ -14,7 +15,9 @@ builder.Services.AddLocalization(options => options.ResourcesPath = "Resources")
|
|||||||
|
|
||||||
builder.Services.AddControllersWithViews()
|
builder.Services.AddControllersWithViews()
|
||||||
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
|
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
|
||||||
.AddDataAnnotationsLocalization();
|
.AddDataAnnotationsLocalization()
|
||||||
|
.AddJsonOptions(options =>
|
||||||
|
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public partial class LdapService : IDisposable
|
|||||||
public string UsersBaseDn => string.IsNullOrEmpty(_opts.UsersOu) ? _opts.BaseDn : $"{_opts.UsersOu},{_opts.BaseDn}";
|
public string UsersBaseDn => string.IsNullOrEmpty(_opts.UsersOu) ? _opts.BaseDn : $"{_opts.UsersOu},{_opts.BaseDn}";
|
||||||
public string GroupsBaseDn => string.IsNullOrEmpty(_opts.GroupsOu) ? _opts.BaseDn : $"{_opts.GroupsOu},{_opts.BaseDn}";
|
public string GroupsBaseDn => string.IsNullOrEmpty(_opts.GroupsOu) ? _opts.BaseDn : $"{_opts.GroupsOu},{_opts.BaseDn}";
|
||||||
public string MigrationsBaseDn => string.IsNullOrEmpty(_opts.MigrationsOu) ? _opts.BaseDn : $"{_opts.MigrationsOu},{_opts.BaseDn}";
|
public string MigrationsBaseDn => string.IsNullOrEmpty(_opts.MigrationsOu) ? _opts.BaseDn : $"{_opts.MigrationsOu},{_opts.BaseDn}";
|
||||||
|
public string[] GroupsAttributes => ["cn", "gidNumber", "description"];
|
||||||
public async Task<IEnumerable<Dictionary<string, string>>> ListLocationsAsync()
|
public async Task<IEnumerable<Dictionary<string, string>>> ListLocationsAsync()
|
||||||
{
|
{
|
||||||
return await ListObjectBy(LocationsBaseDn, "(ou=locations)", ["l", "street", "description"]);
|
return await ListObjectBy(LocationsBaseDn, "(ou=locations)", ["l", "street", "description"]);
|
||||||
@@ -107,14 +107,34 @@ public partial class LdapService : IDisposable
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<GroupModel>> ListGroupsAsync(string[] attributes)
|
||||||
|
{
|
||||||
|
List<GroupModel> returnValue = [];
|
||||||
|
(await ListObjectBy(GroupsBaseDn, "", attributes))
|
||||||
|
.ToList()
|
||||||
|
.ForEach(x =>
|
||||||
|
returnValue.Add(
|
||||||
|
new GroupModel(x)
|
||||||
|
{
|
||||||
|
Cn = x["cn"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
public async Task<UserModel> GetUserByUidAsync(string uid)
|
public async Task<UserModel> GetUserByUidAsync(string uid)
|
||||||
{
|
{
|
||||||
return new UserModel((await ListObjectBy(UsersBaseDn, $"uid={uid}", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"])).First()) {Uid = uid};
|
return new UserModel((await ListObjectBy(UsersBaseDn, $"uid={uid}", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"])).First()) { Uid = uid };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserModel> GetUserByUidAsync(string uid, string[] attributes)
|
public async Task<UserModel> GetUserByUidAsync(string uid, string[] attributes)
|
||||||
{
|
{
|
||||||
return new UserModel((await ListObjectBy(UsersBaseDn, $"uid={uid}", attributes)).First()) {Uid = uid};
|
return new UserModel((await ListObjectBy(UsersBaseDn, $"uid={uid}", attributes)).First()) { Uid = uid };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GroupModel> GetGroupByCnAsync(string cn, string[] attributes)
|
||||||
|
{
|
||||||
|
return new GroupModel((await ListObjectBy(GroupsBaseDn, $"cn={cn}", attributes)).First()) { Cn = cn };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -129,6 +149,12 @@ public partial class LdapService : IDisposable
|
|||||||
await CreateObject(dn, attributeSet);
|
await CreateObject(dn, attributeSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CreateGroup(string cn, LdapAttributeSet attributeSet)
|
||||||
|
{
|
||||||
|
string dn = PrependRDN($"cn={cn}", GroupsBaseDn);
|
||||||
|
await CreateObject(dn, attributeSet);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task CreateAsset(LdapAttributeSet attributeSet)
|
public async Task CreateAsset(LdapAttributeSet attributeSet)
|
||||||
{
|
{
|
||||||
await CreateObject(AssetsBaseDn, attributeSet);
|
await CreateObject(AssetsBaseDn, attributeSet);
|
||||||
@@ -228,13 +254,30 @@ public partial class LdapService : IDisposable
|
|||||||
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
|
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
|
||||||
DeleteObjectByDn(dn);
|
DeleteObjectByDn(dn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeleteGroup(string cn)
|
||||||
|
{
|
||||||
|
string dn = PrependRDN($"cn={cn}", GroupsBaseDn);
|
||||||
|
DeleteObjectByDn(dn);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateUser(string uid, string attributeName, string attributeValue)
|
public async Task UpdateUser(string uid, string attributeName, string attributeValue)
|
||||||
{
|
{
|
||||||
await ConnectAndBind();
|
await UpdateObject(UsersBaseDn, "uid", uid, attributeName, attributeValue);
|
||||||
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
|
}
|
||||||
if (attributeName == "uid")
|
|
||||||
|
public async Task UpdateGroup(string cn, string attributeName, string attributeValue)
|
||||||
{
|
{
|
||||||
await _conn.RenameAsync(dn, $"uid={attributeValue}", true);
|
await UpdateObject(GroupsBaseDn, "cn", cn, attributeName, attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateObject(string baseDn, string rdnKey, string rdnValue, string attributeName, string attributeValue)
|
||||||
|
{
|
||||||
|
await ConnectAndBind();
|
||||||
|
string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn);
|
||||||
|
if (attributeName == rdnKey)
|
||||||
|
{
|
||||||
|
await _conn.RenameAsync(dn, $"{rdnKey}={attributeValue}", true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user