diff --git a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs index 0770c9fc..5c3d2664 100644 --- a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs +++ b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs @@ -39,6 +39,7 @@ using Abblix.Oidc.Server.Features.Issuer; using Abblix.Oidc.Server.Features.Licensing; using Abblix.Oidc.Server.Features.LogoutNotification; +using Abblix.Oidc.Server.Features.UserInfo; using Abblix.Oidc.Server.Model; using Abblix.Utils; using Microsoft.AspNetCore.Http; diff --git a/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestProcessor.cs b/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestProcessor.cs index 9bef1876..ac297cca 100644 --- a/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestProcessor.cs +++ b/Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestProcessor.cs @@ -27,71 +27,60 @@ // For more information, please refer to the license agreement located at: // https://github.com/Abblix/Oidc.Server/blob/master/README.md -using Abblix.Jwt; using Abblix.Oidc.Server.Common.Constants; -using Abblix.Oidc.Server.Common.Interfaces; using Abblix.Oidc.Server.Endpoints.UserInfo.Interfaces; using Abblix.Oidc.Server.Features.Issuer; using Abblix.Oidc.Server.Features.Licensing; +using Abblix.Oidc.Server.Features.UserInfo; using UserInfoResponse = Abblix.Oidc.Server.Endpoints.UserInfo.Interfaces.UserInfoResponse; namespace Abblix.Oidc.Server.Endpoints.UserInfo; /// -/// Processes user information requests, retrieving and formatting user information based on the provided request. -/// This class plays a crucial role in handling requests to the UserInfo endpoint, ensuring that the returned -/// user information adheres to the requested scopes and the OAuth 2.0 and OpenID Connect standards. +/// Processes user information requests by retrieving and formatting user information based on the provided request. +/// This class is integral in handling requests to the UserInfo endpoint, ensuring that the returned user information +/// adheres to requested scopes and complies with OAuth 2.0 and OpenID Connect standards. /// internal class UserInfoRequestProcessor : IUserInfoRequestProcessor { /// /// Initializes a new instance of the class. /// - /// Provider for user information based on JWT claims. This component is responsible - /// for fetching user-related data that can be returned to the client. - /// Provider for determining which claims to include in the response based on the - /// authorization context and requested scopes. This ensures that only the claims the client is authorized to receive - /// are included. - /// Converter for transforming subject identifiers (sub claims) based on client - /// requirements, supporting privacy and client-specific identifier formats. - /// Provider for the issuer URL, used in generating fully qualified claim names and ensuring - /// consistency in the issuer claim across responses. - public UserInfoRequestProcessor( - IUserInfoProvider userInfoProvider, - IScopeClaimsProvider scopeClaimsProvider, - ISubjectTypeConverter subjectTypeConverter, - IIssuerProvider issuerProvider) + /// Provider for the issuer URL, which is essential for generating fully qualified + /// claim names and ensuring consistency in the 'iss' claim across responses. + /// Provider for user claims based on JWT claims. + /// This component fetches user-related data that can be returned to the client, tailored to the client's + /// authorization context and scope. + public UserInfoRequestProcessor(IIssuerProvider issuerProvider, IUserClaimsProvider userClaimsProvider) { - _userInfoProvider = userInfoProvider; - _scopeClaimsProvider = scopeClaimsProvider; - _subjectTypeConverter = subjectTypeConverter; _issuerProvider = issuerProvider; + _userClaimsProvider = userClaimsProvider; } - private readonly IUserInfoProvider _userInfoProvider; - private readonly IScopeClaimsProvider _scopeClaimsProvider; - private readonly ISubjectTypeConverter _subjectTypeConverter; private readonly IIssuerProvider _issuerProvider; + private readonly IUserClaimsProvider _userClaimsProvider; /// - /// Asynchronously processes a valid user information request and returns a response with the requested user information. + /// Asynchronously processes a valid user information request and returns a structured response containing + /// the requested user information. /// - /// The valid user info request to process. + /// The valid user information request containing the authentication session, + /// authorization context and client information necessary to determine the scope and specifics of + /// the requested claims. /// A representing the asynchronous operation, - /// which upon completion will yield a . + /// which upon completion will yield a encapsulating either the user's claims + /// or an error response. public async Task ProcessAsync(ValidUserInfoRequest request) { - var claimNames = _scopeClaimsProvider.GetRequestedClaims( + var userInfo = await _userClaimsProvider.GetUserClaimsAsync( + request.AuthSession, request.AuthContext.Scope, - request.AuthContext.RequestedClaims?.UserInfo); + request.AuthContext.RequestedClaims?.UserInfo, + request.ClientInfo); - var userInfo = await _userInfoProvider.GetUserInfoAsync(request.AuthSession.Subject, claimNames); if (userInfo == null) - return new UserInfoErrorResponse(ErrorCodes.InvalidGrant, "The user is not found"); - - var subject = _subjectTypeConverter.Convert(request.AuthSession.Subject, request.ClientInfo); - userInfo.SetProperty(JwtClaimTypes.Subject, subject); + return new UserInfoErrorResponse(ErrorCodes.InvalidGrant, "The user claims aren't found"); var issuer = LicenseChecker.CheckIssuer(_issuerProvider.GetIssuer()); return new UserInfoFoundResponse(userInfo, request.ClientInfo, issuer); diff --git a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs index d4cabd6e..eb7734b2 100644 --- a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs +++ b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs @@ -47,6 +47,7 @@ using Abblix.Oidc.Server.Features.Tokens.Formatters; using Abblix.Oidc.Server.Features.Tokens.Revocation; using Abblix.Oidc.Server.Features.Tokens.Validation; +using Abblix.Oidc.Server.Features.UserInfo; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -103,8 +104,6 @@ public static IServiceCollection AddCommonServices(this IServiceCollection servi services.TryAddSingleton(); services.TryAddSingleton(TimeProvider.System); services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); return services.AddJsonWebTokens(); @@ -353,14 +352,37 @@ public static IServiceCollection AddLicense(this IServiceCollection services, st } /// - /// Adds singleton services related to storage mechanisms to the specified . + /// Registers services for various storage functionalities related to the OAuth 2.0 and OpenID Connect flows within + /// the application. This method configures essential storage services that manage authorization codes and + /// authorization requests, ensuring their persistence and accessibility across the application. /// - /// The to add services to. - /// The so that additional calls can be chained. + /// The to which the storage services will be added. + /// This collection is crucial for configuring dependency injection in ASP.NET Core applications, allowing services + /// to be added, managed, and retrieved throughout the application lifecycle. + /// The modified after adding the storage services, permitting additional + /// configurations to be chained. public static IServiceCollection AddStorages(this IServiceCollection services) { services.TryAddSingleton(); services.TryAddSingleton(); return services; } + + /// + /// Registers services related to user claims management into the provided . + /// This method sets up essential services required for processing and handling user claims based on authentication + /// sessions and authorization requests, facilitating the integration of user-specific data into tokens or responses. + /// + /// The to which the user claims provider services will be + /// added. This collection is a mechanism for adding and retrieving dependencies in .NET applications, often used + /// to configure dependency injection in ASP.NET Core applications. + /// The updated after adding the services, allowing for further + /// modifications and additions to be chained. + public static IServiceCollection AddUserInfo(this IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + return services; + } } diff --git a/Abblix.Oidc.Server/Features/Tokens/IIdentityTokenService.cs b/Abblix.Oidc.Server/Features/Tokens/IIdentityTokenService.cs index 82a5bb52..11a53d1b 100644 --- a/Abblix.Oidc.Server/Features/Tokens/IIdentityTokenService.cs +++ b/Abblix.Oidc.Server/Features/Tokens/IIdentityTokenService.cs @@ -67,7 +67,7 @@ public interface IIdentityTokenService /// Identity tokens generated by this service adhere to the OpenID Connect standard, ensuring that they can be used /// reliably in identity assertions across different clients and services that support OpenID Connect. /// - Task CreateIdentityTokenAsync( + Task CreateIdentityTokenAsync( AuthSession authSession, AuthorizationContext authContext, ClientInfo clientInfo, diff --git a/Abblix.Oidc.Server/Features/Tokens/IdentityTokenService.cs b/Abblix.Oidc.Server/Features/Tokens/IdentityTokenService.cs index a66240c1..533a57fe 100644 --- a/Abblix.Oidc.Server/Features/Tokens/IdentityTokenService.cs +++ b/Abblix.Oidc.Server/Features/Tokens/IdentityTokenService.cs @@ -32,46 +32,51 @@ using Abblix.Jwt; using Abblix.Oidc.Server.Common; using Abblix.Oidc.Server.Common.Constants; -using Abblix.Oidc.Server.Common.Interfaces; -using Abblix.Oidc.Server.Endpoints.UserInfo.Interfaces; using Abblix.Oidc.Server.Features.ClientInformation; using Abblix.Oidc.Server.Features.Issuer; using Abblix.Oidc.Server.Features.Licensing; using Abblix.Oidc.Server.Features.Tokens.Formatters; using Abblix.Oidc.Server.Features.UserAuthentication; +using Abblix.Oidc.Server.Features.UserInfo; using Abblix.Utils; namespace Abblix.Oidc.Server.Features.Tokens; /// /// Facilitates the creation and management of identity tokens as part of the OpenID Connect authentication flow. -/// This service assembles identity tokens that encapsulate authenticated user identity, aligning with OpenID Connect -/// specifications. +/// This service constructs identity tokens that encapsulate the authenticated user's identity, adhering to +/// OpenID Connect specifications. It integrates additional security by incorporating claims for token integrity +/// verification. /// internal class IdentityTokenService : IIdentityTokenService { + /// + /// Initializes a new instance of the class, setting up the necessary components + /// for identity token creation. + /// + /// Provides the issuer URL, used in the 'iss' claim of the identity token. + /// Provides the current UTC time, used to set the issued and expiration times of the identity + /// token. + /// Handles the formatting and signing of the JSON Web Token, ensuring it meets + /// the security requirements for transmission. + /// Retrieves user-specific claims to be embedded in the identity token, + /// based on the authentication session and client's requested scopes and claims. public IdentityTokenService( IIssuerProvider issuerProvider, TimeProvider clock, - IUserInfoProvider userInfoProvider, - IScopeClaimsProvider scopeClaimsProvider, - ISubjectTypeConverter subjectTypeConverter, - IClientJwtFormatter jwtFormatter) + IClientJwtFormatter jwtFormatter, + IUserClaimsProvider userClaimsProvider) { _issuerProvider = issuerProvider; _clock = clock; - _userInfoProvider = userInfoProvider; - _scopeClaimsProvider = scopeClaimsProvider; - _subjectTypeConverter = subjectTypeConverter; _jwtFormatter = jwtFormatter; + _userClaimsProvider = userClaimsProvider; } private readonly IIssuerProvider _issuerProvider; private readonly TimeProvider _clock; - private readonly IUserInfoProvider _userInfoProvider; - private readonly IScopeClaimsProvider _scopeClaimsProvider; - private readonly ISubjectTypeConverter _subjectTypeConverter; private readonly IClientJwtFormatter _jwtFormatter; + private readonly IUserClaimsProvider _userClaimsProvider; /// /// Generates an identity token encapsulating the user's authenticated session, optionally embedding claims based on @@ -93,7 +98,7 @@ public IdentityTokenService( /// user identification across services. It explicitly handles `c_hash` and `at_hash` creation, providing additional /// security checks for token integrity. /// - public async Task CreateIdentityTokenAsync( + public async Task CreateIdentityTokenAsync( AuthSession authSession, AuthorizationContext authContext, ClientInfo clientInfo, @@ -112,15 +117,14 @@ public async Task CreateIdentityTokenAsync( scope = scope.Except(new[] { Scopes.Profile, Scopes.Email, Scopes.Address }).ToArray(); } - var claimNames = _scopeClaimsProvider.GetRequestedClaims( + var userInfo = await _userClaimsProvider.GetUserClaimsAsync( + authSession, scope, - authContext.RequestedClaims?.IdToken); + authContext.RequestedClaims?.IdToken, + clientInfo); - var userInfo = await _userInfoProvider.GetUserInfoAsync(authSession.Subject, claimNames); if (userInfo == null) - { - throw new InvalidOperationException("The user claims were not found by subject value"); - } + return null; var issuedAt = _clock.GetUtcNow(); @@ -137,7 +141,6 @@ public async Task CreateIdentityTokenAsync( ExpiresAt = issuedAt + clientInfo.IdentityTokenExpiresIn, Issuer = LicenseChecker.CheckIssuer(_issuerProvider.GetIssuer()), - Subject = _subjectTypeConverter.Convert(authSession.Subject, clientInfo), SessionId = authSession.SessionId, AuthenticationTime = authSession.AuthenticationTime, [JwtClaimTypes.AuthContextClassRef] = authSession.AuthContextClassRef, diff --git a/Abblix.Oidc.Server/Features/Tokens/LogoutTokenService.cs b/Abblix.Oidc.Server/Features/Tokens/LogoutTokenService.cs index 9abd23e9..902c942d 100644 --- a/Abblix.Oidc.Server/Features/Tokens/LogoutTokenService.cs +++ b/Abblix.Oidc.Server/Features/Tokens/LogoutTokenService.cs @@ -34,6 +34,7 @@ using Abblix.Oidc.Server.Features.ClientInformation; using Abblix.Oidc.Server.Features.LogoutNotification; using Abblix.Oidc.Server.Features.Tokens.Formatters; +using Abblix.Oidc.Server.Features.UserInfo; using Abblix.Utils; using Microsoft.Extensions.Logging; diff --git a/Abblix.Oidc.Server/Common/Interfaces/IScopeClaimsProvider.cs b/Abblix.Oidc.Server/Features/UserInfo/IScopeClaimsProvider.cs similarity index 52% rename from Abblix.Oidc.Server/Common/Interfaces/IScopeClaimsProvider.cs rename to Abblix.Oidc.Server/Features/UserInfo/IScopeClaimsProvider.cs index 0317ac54..25c92b93 100644 --- a/Abblix.Oidc.Server/Common/Interfaces/IScopeClaimsProvider.cs +++ b/Abblix.Oidc.Server/Features/UserInfo/IScopeClaimsProvider.cs @@ -27,30 +27,38 @@ // For more information, please refer to the license agreement located at: // https://github.com/Abblix/Oidc.Server/blob/master/README.md -using Abblix.Oidc.Server.Model; - -namespace Abblix.Oidc.Server.Common.Interfaces; +namespace Abblix.Oidc.Server.Features.UserInfo; /// -/// Represents a service responsible for mapping requested claims based on scopes and requested claim details. +/// Defines a service responsible for determining the claims associated with specific OAuth 2.0 and OpenID Connect scopes. +/// This interface facilitates the mapping of requested scopes to their corresponding claims, enabling effective claims +/// management based on the authorization policies and client request parameters. /// public interface IScopeClaimsProvider { /// - /// The requested claims based on scopes and requested claim details. + /// Retrieves the set of claim names associated with the requested scopes and any additional claim details. + /// This method allows for dynamic claim resolution based on the authorization request, supporting customization + /// of claims returned in tokens or user info responses. /// - /// The requested scopes. - /// The requested claim details. - /// An IEnumerable of claim names. - IEnumerable GetRequestedClaims(IEnumerable scopes, Dictionary? requestedClaims); + /// An enumerable of strings representing the requested scopes. Each scope can be associated + /// with one or multiple claims as defined by the implementation. + /// An optional collection of additional claims requested, which may not necessarily + /// be tied to specific scopes but are required by the client. + /// An IEnumerable of strings, each representing a claim name that should be included based on the + /// requested scopes and additional claims. + IEnumerable GetRequestedClaims(IEnumerable scopes, IEnumerable? requestedClaims); /// - /// A collection of all the scopes supported by this provider. + /// Provides a collection of all the scopes that are recognized and supported by this provider. + /// This property can be used to validate scope requests or to generate metadata for discovery documents. /// IEnumerable ScopesSupported { get; } /// - /// A collection of all the claims that can be provided by this provider. + /// Provides a collection of all the claims that this provider can handle. + /// These claims represent the total set of data points that can be requested through various scopes + /// and are used for constructing tokens and user information responses. /// IEnumerable ClaimsSupported { get; } } diff --git a/Abblix.Oidc.Server/Common/Interfaces/ISubjectTypeConverter.cs b/Abblix.Oidc.Server/Features/UserInfo/ISubjectTypeConverter.cs similarity index 98% rename from Abblix.Oidc.Server/Common/Interfaces/ISubjectTypeConverter.cs rename to Abblix.Oidc.Server/Features/UserInfo/ISubjectTypeConverter.cs index fc2cce4b..0b7be4de 100644 --- a/Abblix.Oidc.Server/Common/Interfaces/ISubjectTypeConverter.cs +++ b/Abblix.Oidc.Server/Features/UserInfo/ISubjectTypeConverter.cs @@ -29,7 +29,7 @@ using Abblix.Oidc.Server.Features.ClientInformation; -namespace Abblix.Oidc.Server.Common.Interfaces; +namespace Abblix.Oidc.Server.Features.UserInfo; /// /// Defines the interface for a service that converts user subject identifiers according to the client's specified diff --git a/Abblix.Oidc.Server/Features/UserInfo/IUserClaimsProvider.cs b/Abblix.Oidc.Server/Features/UserInfo/IUserClaimsProvider.cs new file mode 100644 index 00000000..a1ce7946 --- /dev/null +++ b/Abblix.Oidc.Server/Features/UserInfo/IUserClaimsProvider.cs @@ -0,0 +1,67 @@ +// Abblix OpenID Connect Server Library +// Copyright (c) 2024 by Abblix LLP +// +// This software is provided 'as-is', without any express or implied warranty. In no +// event will the authors be held liable for any damages arising from the use of this +// software. +// +// Permitted Use: This software is open for use and extension by non-profit, +// educational and community projects under the condition that it remains unmodified +// and used in its entirety through official Nuget packages. Any unauthorized +// modification, forking of the whole repository, or altering individual files is +// strictly prohibited to ensure development occurs solely within the official Abblix LLP +// repository. +// +// Prohibited Actions: Redistribution, modification, incorporation of this software or +// any part thereof into other products, and creation of derivative works are not +// permitted without obtaining a commercial license from Abblix LLP. +// +// Commercial Use: A separate license is required for commercial use, including +// functionalities extended beyond the original software. For information on obtaining +// a commercial license, please contact Abblix LLP. +// +// Enforcement: Unauthorized redistribution, modification, or use of this software in +// other projects or products is strictly prohibited without prior written permission +// from the copyright holder. Violations may be subject to legal action. +// +// For more information, please refer to the license agreement located at: +// https://github.com/Abblix/Oidc.Server/blob/master/README.md + +using System.Text.Json.Nodes; +using Abblix.Oidc.Server.Features.ClientInformation; +using Abblix.Oidc.Server.Features.UserAuthentication; +using Abblix.Oidc.Server.Model; + +namespace Abblix.Oidc.Server.Features.UserInfo; + +/// +/// Defines an interface for retrieving user-specific claims based on authentication sessions and requested claims. +/// This interface plays a crucial role in authentication flows, where it extracts and formats user data for inclusion +/// in tokens or other authorization responses, ensuring compliance with specified scopes and claim requests. +/// +public interface IUserClaimsProvider +{ + /// + /// Asynchronously retrieves structured user claims based on the provided authentication session, requested scopes, + /// additional claim details, and client information. This method is crucial for generating claims that are to be + /// embedded in identity tokens or provided through user info endpoints, allowing for a personalized and secure user + /// experience based on the authenticated session and application requirements. + /// + /// The authentication session which includes details about the user's authentication + /// state and may affect the resultant claims. + /// A collection of scopes indicating which categories of claims are requested. + /// Each scope can correlate to multiple claims, influencing the granularity and type of data returned. + /// Additional details about specific claims requested, often providing finer control + /// over the claims’ properties such as essentiality or value requirements, enhancing the flexibility and + /// adaptiveness of claim retrieval. + /// Information about the client application making the request, which may influence + /// the processing and filtering of claims based on client-specific settings or requirements. + /// A task that resolves to a encapsulating the user claims in a structured JSON + /// format suitable for further processing, or null if the necessary claims cannot be retrieved or are not + /// applicable based on the session details. + Task GetUserClaimsAsync( + AuthSession authSession, + ICollection scope, + ICollection>? requestedClaims, + ClientInfo clientInfo); +} diff --git a/Abblix.Oidc.Server/Endpoints/UserInfo/Interfaces/IUserInfoProvider.cs b/Abblix.Oidc.Server/Features/UserInfo/IUserInfoProvider.cs similarity index 79% rename from Abblix.Oidc.Server/Endpoints/UserInfo/Interfaces/IUserInfoProvider.cs rename to Abblix.Oidc.Server/Features/UserInfo/IUserInfoProvider.cs index 1029a8f4..4e763dd9 100644 --- a/Abblix.Oidc.Server/Endpoints/UserInfo/Interfaces/IUserInfoProvider.cs +++ b/Abblix.Oidc.Server/Features/UserInfo/IUserInfoProvider.cs @@ -29,12 +29,13 @@ using System.Text.Json.Nodes; - -namespace Abblix.Oidc.Server.Endpoints.UserInfo.Interfaces; +namespace Abblix.Oidc.Server.Features.UserInfo; /// -/// Provides functionality to retrieve user information as JWT claims, supporting both simple and structured claim values. -/// This interface enables the dynamic extraction and packaging of user attributes into JWT claims, accommodating a variety +/// Provides functionality to retrieve user information as JWT claims, supporting both simple and structured claim +/// values. +/// This interface enables the dynamic extraction and packaging of user attributes into JWT claims, accommodating a +/// variety /// of claim types including those that require complex, structured data beyond traditional scalar values. /// public interface IUserInfoProvider @@ -45,23 +46,28 @@ public interface IUserInfoProvider /// specification by allowing for the selective disclosure of user information, catering to the need for complex /// data structures within claims. /// - /// The unique subject identifier (sub claim) of the user whose information is being requested. - /// This identifier must uniquely identify the user across all applications and services. - /// A collection of names representing the claims requested by a client application. + /// + /// The unique subject identifier (sub claim) of the user whose information is being requested. + /// This identifier must uniquely identify the user across all applications and services. + /// + /// + /// A collection of names representing the claims requested by a client application. /// Implementations should check against this list to return only those claims that are requested and authorized - /// for release, including both scalar values and structured data as necessary. + /// for release, including both scalar values and structured data as necessary. + /// /// - /// A task that resolves to a , encapsulating the user's claims where each entry consists of + /// A task that resolves to a , encapsulating the user's claims where each entry consists of /// a claim name and its value. The value can be a simple scalar value (e.g., a string or number) or a structured /// object, allowing for complex data types to be represented. Returns null if no information is available for the - /// given subject. The use of facilitates the representation of hierarchical data within claims, + /// given subject. The use of facilitates the representation of hierarchical data within + /// claims, /// supporting richer and more detailed user profiles. /// /// /// Implementers should ensure that the disclosure of user information complies with applicable privacy laws and /// the principles of data minimization. Sensitive or personal information must only be shared with explicit user /// consent and in a secure manner. In cases where the requested user or claims are not found, returning null or an - /// empty helps maintain privacy and security. + /// empty helps maintain privacy and security. /// Task GetUserInfoAsync(string subject, IEnumerable requestedClaims); } diff --git a/Abblix.Oidc.Server/Common/Implementation/ScopeClaimsProvider.cs b/Abblix.Oidc.Server/Features/UserInfo/ScopeClaimsProvider.cs similarity index 71% rename from Abblix.Oidc.Server/Common/Implementation/ScopeClaimsProvider.cs rename to Abblix.Oidc.Server/Features/UserInfo/ScopeClaimsProvider.cs index a52d3683..065597b7 100644 --- a/Abblix.Oidc.Server/Common/Implementation/ScopeClaimsProvider.cs +++ b/Abblix.Oidc.Server/Features/UserInfo/ScopeClaimsProvider.cs @@ -29,16 +29,19 @@ using Abblix.Jwt; using Abblix.Oidc.Server.Common.Constants; -using Abblix.Oidc.Server.Common.Interfaces; -using Abblix.Oidc.Server.Model; -namespace Abblix.Oidc.Server.Common.Implementation; +namespace Abblix.Oidc.Server.Features.UserInfo; /// -/// Provides claim names based on requested scopes and claims. +/// Implements the interface to provide claim names based on requested scopes +/// and claims. This class manages the association between scopes and the specific claims they include, +/// facilitating the retrieval of appropriate claims for given scopes during the authorization process. /// public class ScopeClaimsProvider : IScopeClaimsProvider { + /// + /// A mapping from scopes to the respective arrays of claim types that each scope encompasses. + /// private readonly Dictionary _scopeToClaimsMap = new[] { StandardScopes.OpenId, @@ -50,25 +53,23 @@ public class ScopeClaimsProvider : IScopeClaimsProvider }.ToDictionary(definition => definition.Scope, definition => definition.ClaimTypes, StringComparer.OrdinalIgnoreCase); /// - /// Gets the requested claims based on scopes and requested claim details. + /// Retrieves the specific claims associated with the requested scopes and any additional requested claims. /// - /// An array of requested scopes. - /// A dictionary of requested claim details. - /// Enumeration of claim names. + /// The collection of scopes for which claims need to be provided. + /// Additional specific claims requested outside of the scope requests. + /// A collection of claim names that are associated with the requested scopes and additional claims. + /// public IEnumerable GetRequestedClaims( IEnumerable scopes, - Dictionary? requestedClaims) + IEnumerable? requestedClaims) { var claimNames = scopes - .SelectMany(scope => _scopeToClaimsMap.TryGetValue(scope, out var claimsInScope) ? claimsInScope : Array.Empty()) + .SelectMany(scope => _scopeToClaimsMap.TryGetValue(scope, out var claims) ? claims : Array.Empty()) .Prepend(JwtClaimTypes.Subject); if (requestedClaims != null) { - claimNames = claimNames.Concat( - from claim in requestedClaims - where claim.Value.Essential == true - select claim.Key); + claimNames = claimNames.Concat(requestedClaims); } return claimNames; diff --git a/Abblix.Oidc.Server/Common/Implementation/SubjectTypeConverter.cs b/Abblix.Oidc.Server/Features/UserInfo/SubjectTypeConverter.cs similarity index 97% rename from Abblix.Oidc.Server/Common/Implementation/SubjectTypeConverter.cs rename to Abblix.Oidc.Server/Features/UserInfo/SubjectTypeConverter.cs index 954fd7f6..3aeaefde 100644 --- a/Abblix.Oidc.Server/Common/Implementation/SubjectTypeConverter.cs +++ b/Abblix.Oidc.Server/Features/UserInfo/SubjectTypeConverter.cs @@ -30,11 +30,10 @@ using System.Security.Cryptography; using System.Text; using Abblix.Oidc.Server.Common.Constants; -using Abblix.Oidc.Server.Common.Interfaces; using Abblix.Oidc.Server.Features.ClientInformation; using Abblix.Utils; -namespace Abblix.Oidc.Server.Common.Implementation; +namespace Abblix.Oidc.Server.Features.UserInfo; /// /// Implements conversion of subject identifiers for end-users based on the subject type requested by the client. diff --git a/Abblix.Oidc.Server/Features/UserInfo/UserClaimsProvider.cs b/Abblix.Oidc.Server/Features/UserInfo/UserClaimsProvider.cs new file mode 100644 index 00000000..c48052a2 --- /dev/null +++ b/Abblix.Oidc.Server/Features/UserInfo/UserClaimsProvider.cs @@ -0,0 +1,140 @@ +// Abblix OpenID Connect Server Library +// Copyright (c) 2024 by Abblix LLP +// +// This software is provided 'as-is', without any express or implied warranty. In no +// event will the authors be held liable for any damages arising from the use of this +// software. +// +// Permitted Use: This software is open for use and extension by non-profit, +// educational and community projects under the condition that it remains unmodified +// and used in its entirety through official Nuget packages. Any unauthorized +// modification, forking of the whole repository, or altering individual files is +// strictly prohibited to ensure development occurs solely within the official Abblix LLP +// repository. +// +// Prohibited Actions: Redistribution, modification, incorporation of this software or +// any part thereof into other products, and creation of derivative works are not +// permitted without obtaining a commercial license from Abblix LLP. +// +// Commercial Use: A separate license is required for commercial use, including +// functionalities extended beyond the original software. For information on obtaining +// a commercial license, please contact Abblix LLP. +// +// Enforcement: Unauthorized redistribution, modification, or use of this software in +// other projects or products is strictly prohibited without prior written permission +// from the copyright holder. Violations may be subject to legal action. +// +// For more information, please refer to the license agreement located at: +// https://github.com/Abblix/Oidc.Server/blob/master/README.md + +using System.Text.Json.Nodes; +using Abblix.Jwt; +using Abblix.Oidc.Server.Common.Interfaces; +using Abblix.Oidc.Server.Features.ClientInformation; +using Abblix.Oidc.Server.Features.UserAuthentication; +using Abblix.Oidc.Server.Model; +using Microsoft.Extensions.Logging; + +namespace Abblix.Oidc.Server.Features.UserInfo; + +/// +/// Handles the retrieval of user claims for authentication sessions, ensuring compliance with requested scopes and +/// specific claim details. This class integrates directly with user information providers and scope-to-claim mappings +/// to fetch and validate the necessary user data. It supports converting user data into claims that adhere to +/// OpenID Connect standards, tailored to the specific needs of the client making the request. +/// +public class UserClaimsProvider : IUserClaimsProvider +{ + /// + /// Initializes a new instance of the class. + /// + /// The logger used for logging information and errors. + /// The provider used to retrieve detailed user information based on specific claims. + /// + /// The provider that maps requested scopes to the corresponding set of claims. + /// + /// The converter used to translate user identifiers into subject types as + /// required by different client configurations. + public UserClaimsProvider( + ILogger logger, + IUserInfoProvider userInfoProvider, + IScopeClaimsProvider scopeClaimsProvider, + ISubjectTypeConverter subjectTypeConverter) + { + _logger = logger; + _userInfoProvider = userInfoProvider; + _scopeClaimsProvider = scopeClaimsProvider; + _subjectTypeConverter = subjectTypeConverter; + } + + private readonly ILogger _logger; + private readonly IScopeClaimsProvider _scopeClaimsProvider; + private readonly IUserInfoProvider _userInfoProvider; + private readonly ISubjectTypeConverter _subjectTypeConverter; + + /// + /// Asynchronously retrieves structured user claims based on an authentication session and specific claim parameters. + /// This method ensures compliance with the OpenID Connect standards by validating essential claims and formatting + /// the user data into a structured JSON object. + /// + /// The authentication session providing the context for user claims retrieval. + /// A collection of scopes defining the categories of claims required. + /// A collection detailing specific claims requested by the client, including any + /// requirements for essential claims. + /// Information about the client application making the request, which may influence how + /// claims are processed and returned. + /// A task that when completed returns a representing the user claims, + /// or throws an exception if required claims are missing. + public async Task GetUserClaimsAsync( + AuthSession authSession, + ICollection scope, + ICollection>? requestedClaims, + ClientInfo clientInfo) + { + var claimNames = _scopeClaimsProvider.GetRequestedClaims( + scope, + from claim in requestedClaims select claim.Key) + ; + + var userInfo = await _userInfoProvider.GetUserInfoAsync( + authSession.Subject, + claimNames.Distinct(StringComparer.Ordinal)); + if (userInfo == null) + { + _logger.LogWarning("The user claims were not found by subject value"); + return null; + } + + var subject = _subjectTypeConverter.Convert(authSession.Subject, clientInfo); + userInfo.SetProperty(JwtClaimTypes.Subject, subject); + + if (FindMissingClaims(userInfo, requestedClaims) is { Count: > 0 } missingClaims) + { + _logger.LogWarning("The following claims are requested, but not returned from {IUserInfoProvider}: {@MissingClaims}", + _userInfoProvider.GetType().FullName, + missingClaims); + + return null; + } + + return userInfo; + } + + private static List? FindMissingClaims( + JsonObject userInfo, + ICollection>? requestedClaims) + { + if (requestedClaims == null) + return null; + + List? missingClaims = null; + foreach (var (claim, details) in requestedClaims) + if (details.Essential == true && !userInfo.TryGetPropertyValue(claim, out _)) + { + missingClaims ??= new List(); + missingClaims.Add(claim); + } + + return missingClaims; + } +} diff --git a/Abblix.Oidc.Server/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/ServiceCollectionExtensions.cs index adc318fa..23f1ac31 100644 --- a/Abblix.Oidc.Server/ServiceCollectionExtensions.cs +++ b/Abblix.Oidc.Server/ServiceCollectionExtensions.cs @@ -117,7 +117,8 @@ public static IServiceCollection AddFeatures(this IServiceCollection services) .AddSessionManagement() .AddRandomGenerators() .AddLogoutNotification() - .AddStorages(); + .AddStorages() + .AddUserInfo(); } ///