Added Group backend CRUD

This commit is contained in:
2025-10-03 21:16:34 +02:00
parent 317b38a4d0
commit 3a97bd6024
6 changed files with 231 additions and 7 deletions

View 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;
}
}

View File

@@ -0,0 +1,3 @@
namespace Berufsschule_HAM.Exceptions;
public class GroupModelConfigurationException : Exception {}

42
src/Models/GroupModel.cs Normal file
View 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
}

View 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;
}

View File

@@ -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();

View File

@@ -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 UpdateObject(UsersBaseDn, "uid", uid, attributeName, attributeValue);
}
public async Task UpdateGroup(string cn, string attributeName, string attributeValue)
{
await UpdateObject(GroupsBaseDn, "cn", cn, attributeName, attributeValue);
}
public async Task UpdateObject(string baseDn, string rdnKey, string rdnValue, string attributeName, string attributeValue)
{ {
await ConnectAndBind(); await ConnectAndBind();
string dn = PrependRDN($"uid={uid}", UsersBaseDn); string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn);
if (attributeName == "uid") if (attributeName == rdnKey)
{ {
await _conn.RenameAsync(dn, $"uid={attributeValue}", true); await _conn.RenameAsync(dn, $"{rdnKey}={attributeValue}", true);
} }
else else
{ {