diff --git a/Wollmilchsau/Components/Layout/MainLayout.razor b/Wollmilchsau/Components/Layout/MainLayout.razor
index 3f00865..adae076 100644
--- a/Wollmilchsau/Components/Layout/MainLayout.razor
+++ b/Wollmilchsau/Components/Layout/MainLayout.razor
@@ -6,6 +6,28 @@
+
+
+
+
+ @{
+ var user = authenticationState.User;
+ var displayName = string.Join(" ", new[]
+ {
+ user.FindFirst("given_name")?.Value,
+ user.FindFirst("family_name")?.Value
+ }.Where(value => !string.IsNullOrWhiteSpace(value)));
+ var classroom = user.FindFirst("classroom")?.Value
+ ?? (user.IsInRole("ROLE_TEACHER") ? "Lehrer" : null);
+ }
+ @displayName@(classroom is null ? string.Empty : $" ({classroom})")
+
+
+
+
@Body
diff --git a/Wollmilchsau/Components/_Imports.razor b/Wollmilchsau/Components/_Imports.razor
index d155b97..f6a600c 100644
--- a/Wollmilchsau/Components/_Imports.razor
+++ b/Wollmilchsau/Components/_Imports.razor
@@ -1,5 +1,6 @@
@using System.Net.Http
@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@@ -9,4 +10,4 @@
@using Wollmilchsau
@using Wollmilchsau.Components
@using Wollmilchsau.Components.Layout
-@using MudBlazor
\ No newline at end of file
+@using MudBlazor
diff --git a/Wollmilchsau/Program.cs b/Wollmilchsau/Program.cs
index e0c7c29..3f0f370 100644
--- a/Wollmilchsau/Program.cs
+++ b/Wollmilchsau/Program.cs
@@ -1,14 +1,76 @@
using Fluxor;
using JasperFx;
using Marten;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using MudBlazor.Services;
using System.Net.Http.Headers;
+using System.Security.Claims;
+using System.Text.Json;
using Wollmilchsau.Components;
using Wollmilchsau.Exceptions;
using Wollmilchsau.Services.Ldap;
var builder = WebApplication.CreateBuilder(args);
+var oidcConfiguration = builder.Configuration.GetRequiredSection("OIDC");
+var oidcScopes = oidcConfiguration.GetSection("Scopes").Get()
+ ?? throw new ConfigurationException("OIDC:Scopes");
+
+builder.Services.AddAuthentication(options =>
+{
+ options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
+})
+.AddCookie()
+.AddOpenIdConnect(options =>
+{
+ options.Authority = oidcConfiguration["Authority"] ?? throw new ConfigurationException("OIDC:Authority");
+ options.ClientId = oidcConfiguration["ClientId"] ?? throw new ConfigurationException("OIDC:ClientId");
+ options.ClientSecret = oidcConfiguration["ClientSecret"] ?? throw new ConfigurationException("OIDC:ClientSecret");
+ options.ResponseType = oidcConfiguration["ResponseType"] ?? throw new ConfigurationException("OIDC:ResponseType");
+ options.GetClaimsFromUserInfoEndpoint = true;
+ options.SaveTokens = true;
+ options.MapInboundClaims = false;
+ options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
+ options.Scope.Clear();
+
+ foreach (var scope in oidcScopes)
+ {
+ options.Scope.Add(scope);
+ }
+
+ options.Events.OnTokenValidated = context =>
+ {
+ AddUserClaims(
+ context.Principal?.Identity as ClaimsIdentity,
+ context.Principal?.FindAll("groups").Select(claim => claim.Value).ToArray() ?? [],
+ context.Principal?.FindFirst("klasse")?.Value);
+
+ return Task.CompletedTask;
+ };
+ options.Events.OnUserInformationReceived = context =>
+ {
+ var userInfo = context.User.RootElement;
+ var groups = userInfo.TryGetProperty("groups", out var groupsElement) && groupsElement.ValueKind == JsonValueKind.Array
+ ? groupsElement.EnumerateArray()
+ .Where(group => group.ValueKind == JsonValueKind.String)
+ .Select(group => group.GetString())
+ .OfType()
+ : [];
+ var classroom = userInfo.TryGetProperty("klasse", out var classroomElement) && classroomElement.ValueKind == JsonValueKind.String
+ ? classroomElement.GetString()
+ : null;
+
+ AddUserClaims(context.Principal?.Identity as ClaimsIdentity, groups, classroom);
+
+ return Task.CompletedTask;
+ };
+});
+builder.Services.AddAuthorization();
+builder.Services.AddCascadingAuthenticationState();
+
builder.Services.AddOptions()
.BindConfiguration("LDAP")
.ValidateDataAnnotations()
@@ -70,10 +132,47 @@ if (!app.Environment.IsDevelopment())
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
+app.UseAuthentication();
+app.UseAuthorization();
+
app.UseAntiforgery();
app.MapStaticAssets();
+app.MapPost("/logout", async (HttpContext context) =>
+ {
+ var authenticationResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+ var properties = authenticationResult.Properties ?? new AuthenticationProperties();
+ properties.RedirectUri = "/";
+
+ return Results.SignOut(
+ properties,
+ [CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme]);
+ })
+ .RequireAuthorization()
+ .WithMetadata(new Microsoft.AspNetCore.Antiforgery.RequireAntiforgeryTokenAttribute(true));
app.MapRazorComponents()
- .AddInteractiveServerRenderMode();
+ .AddInteractiveServerRenderMode()
+ .RequireAuthorization();
return await app.RunJasperFxCommands(args);
+
+static void AddUserClaims(ClaimsIdentity? identity, IEnumerable groups, string? classroom)
+{
+ if (identity is null)
+ {
+ return;
+ }
+
+ foreach (var group in groups.Where(group => !string.IsNullOrWhiteSpace(group)))
+ {
+ if (!identity.HasClaim(ClaimTypes.Role, group))
+ {
+ identity.AddClaim(new Claim(ClaimTypes.Role, group));
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(classroom) && !identity.HasClaim("classroom", classroom))
+ {
+ identity.AddClaim(new Claim("classroom", classroom));
+ }
+}
diff --git a/Wollmilchsau/appsettings.Development.json b/Wollmilchsau/appsettings.Development.json
index 510c3b6..52f985b 100644
--- a/Wollmilchsau/appsettings.Development.json
+++ b/Wollmilchsau/appsettings.Development.json
@@ -31,10 +31,10 @@
},
"OIDC": {
"Authority": "https://id.bs18.de/realms/master",
- "ClientId": "WollmilchsauDev",
+ "ClientId": "wollmilchsaudev",
"ClientSecret": "WQlhc6lzngs0nkC1TQeRbw16qffHaeg4",
"ResponseType": "code",
- "Scopes": [ "openid", "profile" ]
+ "Scopes": [ "openid", "profile", "gruppen", "KlassenAttribut" ]
},
"MSGraph": {
"ClientId": "c938782f-2779-45e6-8430-21238e69aa9e",