Classify SGU accounts into AD role groups

This commit is contained in:
2026-09-08 11:33:02 -06:00
parent 0a2dbbeb93
commit 7a4f599f55
11 changed files with 153 additions and 5 deletions
+1
View File
@@ -21,4 +21,5 @@ internal static class BrokerEventIds
internal static readonly EventId DirectorySynchronizationFailure = new(1300, nameof(DirectorySynchronizationFailure));
internal static readonly EventId DirectoryOptionalMetadataFailure = new(1301, nameof(DirectoryOptionalMetadataFailure));
internal static readonly EventId DirectoryGroupMembershipFailure = new(1302, nameof(DirectoryGroupMembershipFailure));
internal static readonly EventId DirectoryRoleGroupMembershipAdded = new(1303, nameof(DirectoryRoleGroupMembershipAdded));
}
@@ -87,6 +87,14 @@ public sealed class BrokerOptions
{
throw new InvalidOperationException($"The OU mapping for {role} must be beneath BaseDn.");
}
string groupDn = Directory.GetGroupDn(role);
if (string.IsNullOrWhiteSpace(groupDn) ||
!groupDn.StartsWith("CN=", StringComparison.OrdinalIgnoreCase) ||
!groupDn.EndsWith($",{Directory.BaseDn}", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"The security-group mapping for {role} must identify a group beneath BaseDn.");
}
}
if (!string.IsNullOrWhiteSpace(Directory.RemoteDesktopGroupDn) &&
@@ -168,6 +176,12 @@ public sealed class ActiveDirectoryOptions
public string AdministrativeOuDn { get; init; } = "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string ProfessorGroupDn { get; init; } = "CN=SGU-Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string StudentGroupDn { get; init; } = "CN=SGU-Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string AdministrativeGroupDn { get; init; } = "CN=SGU-Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string RemoteDesktopGroupDn { get; init; } = string.Empty;
public string DefaultCompany { get; init; } = "La Salle";
@@ -181,4 +195,12 @@ public sealed class ActiveDirectoryOptions
InstitutionalRole.Administrative => AdministrativeOuDn,
_ => throw new ArgumentOutOfRangeException(nameof(role), role, null)
};
public string GetGroupDn(InstitutionalRole role) => role switch
{
InstitutionalRole.Professor => ProfessorGroupDn,
InstitutionalRole.Student => StudentGroupDn,
InstitutionalRole.Administrative => AdministrativeGroupDn,
_ => throw new ArgumentOutOfRangeException(nameof(role), role, null)
};
}
@@ -109,6 +109,12 @@ public sealed class ActiveDirectorySynchronizer(
user.CommitChanges();
}
// Role membership is part of account provisioning, not optional
// enrichment. Do it before changing the password so a missing or
// inaccessible authorization group cannot leave a newly usable
// account without its required classification.
EnsureRoleGroupMembership(user, identity);
// The exact institutional password received by the broker is passed to AD.
// It is not derived, transformed, written to disk, or included in logs.
user.Invoke("SetPassword", [password]);
@@ -195,6 +201,33 @@ public sealed class ActiveDirectorySynchronizer(
}
}
private void EnsureRoleGroupMembership(DirectoryEntry user, UserIdentity identity)
{
user.RefreshCache(["distinguishedName"]);
string? userDn = Convert.ToString(user.Properties["distinguishedName"].Value);
if (string.IsNullOrWhiteSpace(userDn))
{
throw new InvalidOperationException($"Active Directory did not return a distinguished name for {identity.UserName}.");
}
string groupDn = options.GetGroupDn(identity.Role);
using DirectoryEntry group = Bind(groupDn);
_ = group.NativeObject;
if (group.Properties["member"].Contains(userDn))
{
return;
}
group.Properties["member"].Add(userDn);
group.CommitChanges();
logger.LogInformation(
BrokerEventIds.DirectoryRoleGroupMembershipAdded,
"Added {InstitutionalUser} with role {Role} to Active Directory security group {GroupDn}.",
identity.UserName,
identity.Role,
groupDn);
}
private void TryEnsureRemoteDesktopGroupMembership(DirectoryEntry user, string institutionalUser)
{
if (string.IsNullOrWhiteSpace(options.RemoteDesktopGroupDn))
+3
View File
@@ -50,6 +50,9 @@
"ProfessorOuDn": "OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"StudentOuDn": "OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"AdministrativeOuDn": "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"ProfessorGroupDn": "CN=SGU-Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"StudentGroupDn": "CN=SGU-Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"AdministrativeGroupDn": "CN=SGU-Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx",
"RemoteDesktopGroupDn": "",
"DefaultCompany": "La Salle",
"CreateMissingOus": false