From b68e4d857cc0afb429bfae7b9fc064fb3e073e07 Mon Sep 17 00:00:00 2001 From: Oleksii Nikiforov Date: Tue, 23 Apr 2024 18:01:11 +0300 Subject: [PATCH] Add KeycloakFormatBinder --- samples/AuthGettingStarted/Program.cs | 50 +++++++++++-------- .../ServiceCollectionExtensions.OpenApi.cs | 34 ++++++++----- src/Directory.Packages.props | 1 + .../ServiceCollectionExtensions.cs | 5 +- .../ServiceCollectionExtensions.cs | 13 ++--- .../Keycloak.AuthServices.Common.csproj | 1 + .../KeycloakInstallationOptions.cs | 8 +++ .../KeycloakInstallationOptionsTests.cs | 8 +-- 8 files changed, 73 insertions(+), 47 deletions(-) diff --git a/samples/AuthGettingStarted/Program.cs b/samples/AuthGettingStarted/Program.cs index 8a46a742..ecfb8981 100644 --- a/samples/AuthGettingStarted/Program.cs +++ b/samples/AuthGettingStarted/Program.cs @@ -2,6 +2,7 @@ using Api; using Keycloak.AuthServices.Authentication; using Keycloak.AuthServices.Authorization; +using Keycloak.AuthServices.Common; using Keycloak.AuthServices.Sdk.Admin; var builder = WebApplication.CreateBuilder(args); @@ -12,29 +13,32 @@ host.ConfigureLogger(); -services - .AddEndpointsApiExplorer() - .AddSwagger(); +services.AddEndpointsApiExplorer().AddSwagger(); var authenticationOptions = configuration .GetSection(KeycloakAuthenticationOptions.Section) - .Get(); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder); -services.AddKeycloakAuthentication(authenticationOptions); +services.AddKeycloakAuthentication(authenticationOptions!); var authorizationOptions = configuration .GetSection(KeycloakProtectionClientOptions.Section) - .Get(); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder); services - .AddAuthorization(o => o.AddPolicy("IsAdmin", b => - { - b.RequireRealmRoles("admin"); - b.RequireResourceRoles("r-admin"); - // TokenValidationParameters.RoleClaimType is overriden - // by KeycloakRolesClaimsTransformation - b.RequireRole("r-admin"); - })) + .AddAuthorization(o => + o.AddPolicy( + "IsAdmin", + b => + { + b.RequireRealmRoles("admin"); + b.RequireResourceRoles("r-admin"); + // TokenValidationParameters.RoleClaimType is overriden + // by KeycloakRolesClaimsTransformation + b.RequireRole("r-admin"); + } + ) + ) .AddKeycloakAuthorization(authorizationOptions); var adminClientOptions = configuration @@ -45,17 +49,19 @@ var app = builder.Build(); -app - .UseSwagger() - .UseSwaggerUI(); +app.UseSwagger().UseSwaggerUI(); app.UseAuthentication(); app.UseAuthorization(); -app.MapGet("/", (ClaimsPrincipal user) => -{ - // TokenValidationParameters.NameClaimType is overriden based on keycloak specific claim - app.Logger.LogInformation("{@User}", user.Identity.Name); -}).RequireAuthorization("IsAdmin"); +app.MapGet( + "/", + (ClaimsPrincipal user) => + { + // TokenValidationParameters.NameClaimType is overriden based on keycloak specific claim + app.Logger.LogInformation("{@User}", user.Identity.Name); + } + ) + .RequireAuthorization("IsAdmin"); app.Run(); diff --git a/samples/AuthZGettingStarted/ServiceCollectionExtensions.OpenApi.cs b/samples/AuthZGettingStarted/ServiceCollectionExtensions.OpenApi.cs index b6c7457c..9a4dd934 100644 --- a/samples/AuthZGettingStarted/ServiceCollectionExtensions.OpenApi.cs +++ b/samples/AuthZGettingStarted/ServiceCollectionExtensions.OpenApi.cs @@ -1,19 +1,21 @@ namespace Microsoft.Extensions.DependencyInjection; using Keycloak.AuthServices.Authentication; +using Keycloak.AuthServices.Common; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; public static partial class ServiceCollectionExtensions { - public static IServiceCollection AddApplicationSwagger(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddApplicationSwagger( + this IServiceCollection services, + IConfiguration configuration + ) { - KeycloakAuthenticationOptions options = new(); - - configuration + var options = configuration .GetSection(KeycloakAuthenticationOptions.Section) - .Bind(options, opt => opt.BindNonPublicProperties = true); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder); services.AddEndpointsApiExplorer(); services.AddSwaggerGen(c => @@ -31,28 +33,34 @@ public static IServiceCollection AddApplicationSwagger(this IServiceCollection s { Implicit = new OpenApiOAuthFlow { - AuthorizationUrl = new Uri($"{options.KeycloakUrlRealm}/protocol/openid-connect/auth"), - TokenUrl = new Uri($"{options.KeycloakUrlRealm}/protocol/openid-connect/token"), + AuthorizationUrl = new Uri( + $"{options.KeycloakUrlRealm}/protocol/openid-connect/auth" + ), + TokenUrl = new Uri( + $"{options.KeycloakUrlRealm}/protocol/openid-connect/token" + ), Scopes = new Dictionary(), } } }; c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - {securityScheme, Array.Empty()} - }); + c.AddSecurityRequirement( + new OpenApiSecurityRequirement { { securityScheme, Array.Empty() } } + ); }); return services; } - public static IApplicationBuilder UseApplicationSwagger(this IApplicationBuilder app, IConfiguration configuration) + public static IApplicationBuilder UseApplicationSwagger( + this IApplicationBuilder app, + IConfiguration configuration + ) { KeycloakAuthenticationOptions options = new(); configuration .GetSection(KeycloakAuthenticationOptions.Section) - .Bind(options, opt => opt.BindNonPublicProperties = true); + .Bind(options, KeycloakInstallationOptions.KeycloakFormatBinder); app.UseSwagger(); app.UseSwaggerUI(s => s.OAuthClientId(options.Resource)); diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index fcb244e7..e3cc51a2 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,6 +1,7 @@ + diff --git a/src/Keycloak.AuthServices.Authentication/ServiceCollectionExtensions.cs b/src/Keycloak.AuthServices.Authentication/ServiceCollectionExtensions.cs index f2c6227a..9035b2e3 100644 --- a/src/Keycloak.AuthServices.Authentication/ServiceCollectionExtensions.cs +++ b/src/Keycloak.AuthServices.Authentication/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ namespace Keycloak.AuthServices.Authentication; using Claims; using Configuration; +using Keycloak.AuthServices.Common; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; @@ -75,7 +76,7 @@ public static AuthenticationBuilder AddKeycloakAuthentication( { var authenticationOptions = configuration .GetSection(KeycloakAuthenticationOptions.Section) - .Get(options => options.BindNonPublicProperties = true); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder)!; return services.AddKeycloakAuthentication(authenticationOptions, configureOptions); } @@ -97,7 +98,7 @@ public static AuthenticationBuilder AddKeycloakAuthentication( { var authenticationOptions = configuration .GetSection(keycloakClientSectionName) - .Get(options => options.BindNonPublicProperties = true); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder)!; return services.AddKeycloakAuthentication(authenticationOptions, configureOptions); } diff --git a/src/Keycloak.AuthServices.Authorization/ServiceCollectionExtensions.cs b/src/Keycloak.AuthServices.Authorization/ServiceCollectionExtensions.cs index 848b0244..18f65eb2 100644 --- a/src/Keycloak.AuthServices.Authorization/ServiceCollectionExtensions.cs +++ b/src/Keycloak.AuthServices.Authorization/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ namespace Keycloak.AuthServices.Authorization; +using Keycloak.AuthServices.Common; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -17,7 +18,8 @@ public static class ServiceCollectionExtensions /// public static IServiceCollection AddKeycloakAuthorization( this IServiceCollection services, - KeycloakProtectionClientOptions keycloakOptions) + KeycloakProtectionClientOptions keycloakOptions + ) { services.AddKeycloakProtectionHttpClient(keycloakOptions); @@ -40,13 +42,12 @@ public static IServiceCollection AddKeycloakAuthorization( public static IServiceCollection AddKeycloakAuthorization( this IServiceCollection services, IConfiguration configuration, - string? keycloakClientSectionName = default) + string? keycloakClientSectionName = default + ) { - KeycloakProtectionClientOptions options = new(); - - configuration + var options = configuration .GetSection(keycloakClientSectionName ?? KeycloakProtectionClientOptions.Section) - .Bind(options, opt => opt.BindNonPublicProperties = true); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder)!; services.AddKeycloakAuthorization(options); return services; diff --git a/src/Keycloak.AuthServices.Common/Keycloak.AuthServices.Common.csproj b/src/Keycloak.AuthServices.Common/Keycloak.AuthServices.Common.csproj index 0b66a457..45ae4451 100644 --- a/src/Keycloak.AuthServices.Common/Keycloak.AuthServices.Common.csproj +++ b/src/Keycloak.AuthServices.Common/Keycloak.AuthServices.Common.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Keycloak.AuthServices.Common/KeycloakInstallationOptions.cs b/src/Keycloak.AuthServices.Common/KeycloakInstallationOptions.cs index c24b4fad..76ab4f62 100644 --- a/src/Keycloak.AuthServices.Common/KeycloakInstallationOptions.cs +++ b/src/Keycloak.AuthServices.Common/KeycloakInstallationOptions.cs @@ -109,6 +109,14 @@ private static string NormalizeUrl(string url) [ConfigurationKeyName("RolesSource")] public RolesClaimTransformationSource RolesSource { get; set; } = RolesClaimTransformationSource.ResourceAccess; + + /// + /// Default binder required to handle kebab-case configuration format used by Keycloak + /// +#pragma warning disable CA2211 // Non-constant fields should not be visible + public static Action KeycloakFormatBinder = options => + options.BindNonPublicProperties = true; +#pragma warning restore CA2211 // Non-constant fields should not be visible } /// diff --git a/tests/Keycloak.AuthServices.Common.Tests/KeycloakInstallationOptionsTests.cs b/tests/Keycloak.AuthServices.Common.Tests/KeycloakInstallationOptionsTests.cs index 8d2eb1f8..189d8677 100644 --- a/tests/Keycloak.AuthServices.Common.Tests/KeycloakInstallationOptionsTests.cs +++ b/tests/Keycloak.AuthServices.Common.Tests/KeycloakInstallationOptionsTests.cs @@ -5,7 +5,7 @@ public class KeycloakInstallationOptionsTests { - private static readonly KeycloakInstallationOptions _expected = + private static readonly KeycloakInstallationOptions Expected = new() { Realm = "Test", @@ -23,9 +23,9 @@ public void TestKebabCaseNotation() var authenticationOptions = configuration .GetSection("Keycloak1") - .Get(options => options.BindNonPublicProperties = true); + .Get(KeycloakInstallationOptions.KeycloakFormatBinder); - authenticationOptions.Should().BeEquivalentTo(_expected); + authenticationOptions.Should().BeEquivalentTo(Expected); } [Fact] @@ -37,6 +37,6 @@ public void TestPascalCaseNotation() .GetSection("Keycloak2") .Get(); - authenticationOptions.Should().BeEquivalentTo(_expected); + authenticationOptions.Should().BeEquivalentTo(Expected); } }