This commit is contained in:
Rhywden
2026-08-23 15:31:34 +02:00
5 changed files with 47 additions and 12 deletions
+5 -1
View File
@@ -28,6 +28,7 @@ else
<th>Classroom</th> <th>Classroom</th>
<th>Given name</th> <th>Given name</th>
<th>Incomplete</th> <th>Incomplete</th>
<th>Role</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -39,6 +40,7 @@ else
<td>@user.Classroom</td> <td>@user.Classroom</td>
<td>@user.GivenName</td> <td>@user.GivenName</td>
<td>@user.Incomplete</td> <td>@user.Incomplete</td>
<td>@user.Role</td>
</tr> </tr>
} }
</tbody> </tbody>
@@ -56,7 +58,9 @@ else
private Task DownloadCsvAsync() private Task DownloadCsvAsync()
{ {
var csv = string.Join("\r\n", Users.Select(user => string.Join(';', var csv = string.Join("\r\n", users!
.Where(user => user.Role == Wollmilchsau.Models.LdapUserRole.Student)
.Select(user => string.Join(';',
user.GivenName ?? string.Empty, user.GivenName ?? string.Empty,
user.Surname ?? string.Empty, user.Surname ?? string.Empty,
user.Classroom ?? string.Empty, user.Classroom ?? string.Empty,
+8 -1
View File
@@ -1,8 +1,15 @@
namespace Wollmilchsau.Models; namespace Wollmilchsau.Models;
public enum LdapUserRole
{
Student,
Teacher
}
public sealed record LdapUser( public sealed record LdapUser(
string Username, string Username,
string? Surname, string? Surname,
string? Classroom, string? Classroom,
string? GivenName, string? GivenName,
bool Incomplete); bool Incomplete,
LdapUserRole Role);
@@ -24,4 +24,7 @@ public sealed class LdapOptions
[Required] [Required]
public string StudentsRole { get; init; } = string.Empty; public string StudentsRole { get; init; } = string.Empty;
[Required]
public string TeacherRole { get; init; } = string.Empty;
} }
+26 -5
View File
@@ -49,27 +49,30 @@ public sealed class LdapService(IOptions<LdapOptions> options) : ILdapService
private IEnumerable<LdapUser> GetUsers(LdapConnection connection) private IEnumerable<LdapUser> GetUsers(LdapConnection connection)
{ {
var studentRoleDn = BuildDistinguishedName(_options.StudentsRole, _options.RoleBase); var studentRoleDn = BuildDistinguishedName(_options.StudentsRole, _options.RoleBase);
var teacherRoleDn = BuildDistinguishedName(_options.TeacherRole, _options.RoleBase);
var request = new SearchRequest( var request = new SearchRequest(
BuildDistinguishedName(_options.UserBase), BuildDistinguishedName(_options.UserBase),
$"(memberOf={EscapeFilterValue(studentRoleDn)})", $"(|(memberOf={EscapeFilterValue(studentRoleDn)})(memberOf={EscapeFilterValue(teacherRoleDn)}))",
SearchScope.Subtree, SearchScope.Subtree,
"uid", "uid",
"sn", "sn",
"departmentNumber", "departmentNumber",
"givenName"); "givenName",
"memberOf");
var response = (SearchResponse)connection.SendRequest(request); var response = (SearchResponse)connection.SendRequest(request);
return response.Entries return response.Entries
.Cast<SearchResultEntry>() .Cast<SearchResultEntry>()
.Select(CreateUser); .Select(entry => CreateUser(entry, studentRoleDn, teacherRoleDn));
} }
private LdapUser CreateUser(SearchResultEntry entry) private LdapUser CreateUser(SearchResultEntry entry, string studentRoleDn, string teacherRoleDn)
{ {
var username = GetFirstAttributeValue(entry, "uid") ?? throw new InvalidOperationException($"LDAP user entry '{entry.DistinguishedName}' is missing uid."); var username = GetFirstAttributeValue(entry, "uid") ?? throw new InvalidOperationException($"LDAP user entry '{entry.DistinguishedName}' is missing uid.");
var surname = CapitalizeFirstLetter(GetFirstAttributeValue(entry, "sn")); var surname = CapitalizeFirstLetter(GetFirstAttributeValue(entry, "sn"));
var classroom = GetFirstAttributeValue(entry, "departmentNumber"); var classroom = GetFirstAttributeValue(entry, "departmentNumber");
var givenName = GetFirstAttributeValue(entry, "givenName"); var givenName = GetFirstAttributeValue(entry, "givenName");
var role = DetermineUserRole(entry, studentRoleDn, teacherRoleDn);
return new LdapUser( return new LdapUser(
username, username,
@@ -78,7 +81,25 @@ public sealed class LdapService(IOptions<LdapOptions> options) : ILdapService
givenName, givenName,
string.IsNullOrWhiteSpace(surname) || string.IsNullOrWhiteSpace(surname) ||
string.IsNullOrWhiteSpace(classroom) || string.IsNullOrWhiteSpace(classroom) ||
string.IsNullOrWhiteSpace(givenName)); string.IsNullOrWhiteSpace(givenName),
role);
}
private static LdapUserRole DetermineUserRole(SearchResultEntry entry, string studentRoleDn, string teacherRoleDn)
{
var roles = GetAttributeValues(entry, "memberOf");
if (roles.Contains(studentRoleDn, StringComparer.OrdinalIgnoreCase))
{
return LdapUserRole.Student;
}
if (roles.Contains(teacherRoleDn, StringComparer.OrdinalIgnoreCase))
{
return LdapUserRole.Teacher;
}
throw new InvalidOperationException($"LDAP user entry '{entry.DistinguishedName}' is not a member of a supported role.");
} }
private string BuildDistinguishedName(params string[] relativeNames) private string BuildDistinguishedName(params string[] relativeNames)
+1 -1
View File
@@ -14,7 +14,7 @@
<PackageReference Include="Hangfire" Version="1.8.24" /> <PackageReference Include="Hangfire" Version="1.8.24" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.24" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.8.24" />
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.12.0" /> <PackageReference Include="Hangfire.Redis.StackExchange" Version="1.12.0" />
<PackageReference Include="Marten.AspNetCore" Version="9.27.0" /> <PackageReference Include="Marten.AspNetCore" Version="9.28.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.11" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.11" /> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.11" />
<PackageReference Include="Microsoft.Graph" Version="6.5.0" /> <PackageReference Include="Microsoft.Graph" Version="6.5.0" />