From af59463d477c932a51bda601f2ef4b15603e558f Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Fri, 19 Jan 2024 12:17:42 +0000 Subject: [PATCH] Handle per-host roleprovider config --- .../Domain/RoleProviderConfigurationTests.cs | 61 +++++++++++++++++++ .../RoleProvisioning/RoleProviderService.cs | 48 +++++++++------ .../Domain/RoleProviderConfiguration.cs | 16 ++--- 3 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 src/IIIFAuth2/IIIFAuth2.API.Tests/Models/Domain/RoleProviderConfigurationTests.cs diff --git a/src/IIIFAuth2/IIIFAuth2.API.Tests/Models/Domain/RoleProviderConfigurationTests.cs b/src/IIIFAuth2/IIIFAuth2.API.Tests/Models/Domain/RoleProviderConfigurationTests.cs new file mode 100644 index 0000000..13f3d05 --- /dev/null +++ b/src/IIIFAuth2/IIIFAuth2.API.Tests/Models/Domain/RoleProviderConfigurationTests.cs @@ -0,0 +1,61 @@ +using IIIFAuth2.API.Models.Domain; + +namespace IIIFAuth2.API.Tests.Models.Domain; + +public class RoleProviderConfigurationTests +{ + [Fact] + public void GetConfiguration_ReturnsDefault_IfNoHostSpecificConfig() + { + // Arrange + var defaultConfig = new ClickthroughConfiguration { GestureTitle = "test" }; + var roleProviderConfig = new RoleProviderConfiguration + { + { RoleProviderConfiguration.DefaultKey, defaultConfig } + }; + + // Act + var actual = roleProviderConfig.GetConfiguration("localhost"); + + // Assert + actual.Should().Be(defaultConfig); + } + + [Fact] + public void GetConfiguration_ReturnsDefault_IfHostSpecificConfigNotFound() + { + // Arrange + var defaultConfig = new ClickthroughConfiguration { GestureTitle = "test" }; + var hostConfig = new ClickthroughConfiguration { GestureTitle = "anotherTest" }; + var roleProviderConfig = new RoleProviderConfiguration + { + { RoleProviderConfiguration.DefaultKey, defaultConfig }, + { "test.example", hostConfig } + }; + + // Act + var actual = roleProviderConfig.GetConfiguration("localhost"); + + // Assert + actual.Should().Be(defaultConfig); + } + + [Fact] + public void GetConfiguration_ReturnsHostSpecific_IfFound() + { + // Arrange + var defaultConfig = new ClickthroughConfiguration { GestureTitle = "test" }; + var hostConfig = new ClickthroughConfiguration { GestureTitle = "anotherTest" }; + var roleProviderConfig = new RoleProviderConfiguration + { + { RoleProviderConfiguration.DefaultKey, defaultConfig }, + { "test.example", hostConfig } + }; + + // Act + var actual = roleProviderConfig.GetConfiguration("test.example"); + + // Assert + actual.Should().Be(hostConfig); + } +} \ No newline at end of file diff --git a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/RoleProviderService.cs b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/RoleProviderService.cs index d8167fc..f7e8913 100644 --- a/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/RoleProviderService.cs +++ b/src/IIIFAuth2/IIIFAuth2.API/Infrastructure/Auth/RoleProvisioning/RoleProviderService.cs @@ -2,6 +2,7 @@ using IIIFAuth2.API.Data.Entities; using IIIFAuth2.API.Infrastructure.Auth.RoleProvisioning.Oidc; using IIIFAuth2.API.Models.Domain; +using IIIFAuth2.API.Utils; namespace IIIFAuth2.API.Infrastructure.Auth.RoleProvisioning; @@ -13,17 +14,20 @@ public class RoleProviderService private readonly AuthServicesContext dbContext; private readonly ClickThroughProviderHandler clickthroughRoleHandler; private readonly OidcRoleProviderHandler oidcRoleProviderHandler; + private readonly IHttpContextAccessor httpContextAccessor; private readonly ILogger logger; public RoleProviderService( AuthServicesContext dbContext, ClickThroughProviderHandler clickthroughRoleHandler, OidcRoleProviderHandler oidcRoleProviderHandler, + IHttpContextAccessor httpContextAccessor, ILogger logger) { this.dbContext = dbContext; this.clickthroughRoleHandler = clickthroughRoleHandler; this.oidcRoleProviderHandler = oidcRoleProviderHandler; + this.httpContextAccessor = httpContextAccessor; this.logger = logger; } @@ -36,17 +40,9 @@ public RoleProviderService( var accessService = await GetAccessServices(customerId, accessServiceName); if (accessService == null) return null; - var roleProvider = accessService.RoleProvider; - if (roleProvider == null) - { - logger.LogWarning( - "AccessService '{AccessServiceId}' ({CustomerId}:{AccessServiceName}) has no RoleProvider", - accessService.Id, customerId, accessService.Name); - return null; - } + var providerConfiguration = GetProviderConfigurationForHost(accessService); + if (providerConfiguration == null) return null; - // TODO - does this need to be smarter? How does it look up the key? Hostname? - var providerConfiguration = roleProvider.Configuration.GetDefaultConfiguration(); switch (providerConfiguration.Config) { case RoleProviderType.Clickthrough: @@ -74,17 +70,10 @@ public RoleProviderService( { var accessService = await GetAccessServices(customerId, accessServiceName); if (accessService == null) return null; + + var providerConfiguration = GetProviderConfigurationForHost(accessService); + if (providerConfiguration == null) return null; - var roleProvider = accessService.RoleProvider; - if (roleProvider == null) - { - logger.LogWarning( - "AccessService '{AccessServiceId}' ({CustomerId}:{AccessServiceName}) has no RoleProvider", - accessService.Id, customerId, accessService.Name); - return null; - } - - var providerConfiguration = roleProvider.Configuration.GetDefaultConfiguration(); var result = await oidcRoleProviderHandler.HandleLoginCallback(customerId, roleProvisionToken, authCode, accessService, providerConfiguration, cancellationToken); return result; @@ -98,4 +87,23 @@ public RoleProviderService( .SingleOrDefault(s => s.Name.Equals(accessServiceName, StringComparison.OrdinalIgnoreCase)); return accessService; } + + private IProviderConfiguration? GetProviderConfigurationForHost(AccessService accessService) + { + var roleProvider = accessService.RoleProvider; + if (roleProvider == null) + { + logger.LogWarning( + "AccessService '{AccessServiceId}' ({CustomerId}:{AccessServiceName}) has no RoleProvider", + accessService.Id, accessService.Customer, accessService.Name); + return null; + } + + var currentRequest = httpContextAccessor.SafeHttpContext().Request; + var host = currentRequest.Host.Value; + logger.LogTrace("Getting provider configuration for host {Host}", host); + + var providerConfiguration = roleProvider.Configuration.GetConfiguration(host); + return providerConfiguration; + } } \ No newline at end of file diff --git a/src/IIIFAuth2/IIIFAuth2.API/Models/Domain/RoleProviderConfiguration.cs b/src/IIIFAuth2/IIIFAuth2.API/Models/Domain/RoleProviderConfiguration.cs index b283f5e..68a3ae8 100644 --- a/src/IIIFAuth2/IIIFAuth2.API/Models/Domain/RoleProviderConfiguration.cs +++ b/src/IIIFAuth2/IIIFAuth2.API/Models/Domain/RoleProviderConfiguration.cs @@ -5,17 +5,19 @@ namespace IIIFAuth2.API.Models.Domain; /// /// A collection of objects, keyed as dictionary. +/// A key of "default" will always be present. Further keys that match incoming host can be supplied, this allows for +/// different configurations for different hosts. /// -/// -/// The key will always be "default" but is a placeholder to allow more flexible configuration in the future. For auth1 -/// it stored hostname as different RoleProviders could be used per host -/// public class RoleProviderConfiguration : Dictionary { public const string DefaultKey = "default"; - - public IProviderConfiguration GetDefaultConfiguration() - => this[DefaultKey]; + + /// + /// Get the most appropriate object. This will be host-specific, if found, + /// or fallback to Default config. + /// + public IProviderConfiguration GetConfiguration(string host) + => TryGetValue(host, out var hostConfiguration) ? hostConfiguration : this[DefaultKey]; } ///