Skip to content

Commit

Permalink
Extracted similar code to UserClaimsProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
kirill-abblix committed Apr 19, 2024
1 parent a342cef commit 36d75c3
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 103 deletions.
1 change: 1 addition & 0 deletions Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
59 changes: 24 additions & 35 deletions Abblix.Oidc.Server/Endpoints/UserInfo/UserInfoRequestProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// 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.
/// </summary>
internal class UserInfoRequestProcessor : IUserInfoRequestProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="UserInfoRequestProcessor"/> class.
/// </summary>
/// <param name="userInfoProvider">Provider for user information based on JWT claims. This component is responsible
/// for fetching user-related data that can be returned to the client.</param>
/// <param name="scopeClaimsProvider">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.</param>
/// <param name="subjectTypeConverter">Converter for transforming subject identifiers (sub claims) based on client
/// requirements, supporting privacy and client-specific identifier formats.</param>
/// <param name="issuerProvider">Provider for the issuer URL, used in generating fully qualified claim names and ensuring
/// consistency in the issuer claim across responses.</param>
public UserInfoRequestProcessor(
IUserInfoProvider userInfoProvider,
IScopeClaimsProvider scopeClaimsProvider,
ISubjectTypeConverter subjectTypeConverter,
IIssuerProvider issuerProvider)
/// <param name="issuerProvider">Provider for the issuer URL, which is essential for generating fully qualified
/// claim names and ensuring consistency in the 'iss' claim across responses.</param>
/// <param name="userClaimsProvider">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.</param>
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;

/// <summary>
/// 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.
/// </summary>
/// <param name="request">The valid user info request to process.</param>
/// <param name="request">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.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation,
/// which upon completion will yield a <see cref="UserInfoResponse"/>.</returns>
/// which upon completion will yield a <see cref="UserInfoResponse"/> encapsulating either the user's claims
/// or an error response.</returns>
public async Task<UserInfoResponse> 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);
Expand Down
32 changes: 27 additions & 5 deletions Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -103,8 +104,6 @@ public static IServiceCollection AddCommonServices(this IServiceCollection servi
services.TryAddSingleton<IConsentService, NullConsentService>();
services.TryAddSingleton(TimeProvider.System);
services.TryAddSingleton<IHashService, HashService>();
services.TryAddSingleton<ISubjectTypeConverter, SubjectTypeConverter>();
services.TryAddSingleton<IScopeClaimsProvider, ScopeClaimsProvider>();
services.TryAddSingleton<IBinarySerializer, Utf8JsonBinarySerializer>();
services.TryAddSingleton<IEntityStorage, DistributedCacheStorage>();
return services.AddJsonWebTokens();
Expand Down Expand Up @@ -353,14 +352,37 @@ public static IServiceCollection AddLicense(this IServiceCollection services, st
}

/// <summary>
/// Adds singleton services related to storage mechanisms to the specified <see cref="IServiceCollection"/>.
/// 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.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
/// <param name="services">The <see cref="IServiceCollection"/> 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.</param>
/// <returns>The modified <see cref="IServiceCollection"/> after adding the storage services, permitting additional
/// configurations to be chained.</returns>
public static IServiceCollection AddStorages(this IServiceCollection services)
{
services.TryAddSingleton<IAuthorizationCodeService, AuthorizationCodeService>();
services.TryAddSingleton<IAuthorizationRequestStorage, AuthorizationRequestStorage>();
return services;
}

/// <summary>
/// Registers services related to user claims management into the provided <see cref="IServiceCollection"/>.
/// 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.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> 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.</param>
/// <returns>The updated <see cref="IServiceCollection"/> after adding the services, allowing for further
/// modifications and additions to be chained.</returns>
public static IServiceCollection AddUserInfo(this IServiceCollection services)
{
services.TryAddSingleton<IUserClaimsProvider, UserClaimsProvider>();
services.TryAddSingleton<ISubjectTypeConverter, SubjectTypeConverter>();
services.TryAddSingleton<IScopeClaimsProvider, ScopeClaimsProvider>();
return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </remarks>
Task<EncodedJsonWebToken> CreateIdentityTokenAsync(
Task<EncodedJsonWebToken?> CreateIdentityTokenAsync(
AuthSession authSession,
AuthorizationContext authContext,
ClientInfo clientInfo,
Expand Down
47 changes: 25 additions & 22 deletions Abblix.Oidc.Server/Features/Tokens/IdentityTokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// 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.
/// </summary>
internal class IdentityTokenService : IIdentityTokenService
{
/// <summary>
/// Initializes a new instance of the <see cref="IdentityTokenService"/> class, setting up the necessary components
/// for identity token creation.
/// </summary>
/// <param name="issuerProvider">Provides the issuer URL, used in the 'iss' claim of the identity token.</param>
/// <param name="clock">Provides the current UTC time, used to set the issued and expiration times of the identity
/// token.</param>
/// <param name="jwtFormatter">Handles the formatting and signing of the JSON Web Token, ensuring it meets
/// the security requirements for transmission.</param>
/// <param name="userClaimsProvider">Retrieves user-specific claims to be embedded in the identity token,
/// based on the authentication session and client's requested scopes and claims.</param>
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;

/// <summary>
/// Generates an identity token encapsulating the user's authenticated session, optionally embedding claims based on
Expand All @@ -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.
/// </remarks>
public async Task<EncodedJsonWebToken> CreateIdentityTokenAsync(
public async Task<EncodedJsonWebToken?> CreateIdentityTokenAsync(
AuthSession authSession,
AuthorizationContext authContext,
ClientInfo clientInfo,
Expand All @@ -112,15 +117,14 @@ public async Task<EncodedJsonWebToken> 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();

Expand All @@ -137,7 +141,6 @@ public async Task<EncodedJsonWebToken> 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,
Expand Down
1 change: 1 addition & 0 deletions Abblix.Oidc.Server/Features/Tokens/LogoutTokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// 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.
/// </summary>
public interface IScopeClaimsProvider
{
/// <summary>
/// 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.
/// </summary>
/// <param name="scopes">The requested scopes.</param>
/// <param name="requestedClaims">The requested claim details.</param>
/// <returns>An IEnumerable of claim names.</returns>
IEnumerable<string> GetRequestedClaims(IEnumerable<string> scopes, Dictionary<string, RequestedClaimDetails>? requestedClaims);
/// <param name="scopes">An enumerable of strings representing the requested scopes. Each scope can be associated
/// with one or multiple claims as defined by the implementation.</param>
/// <param name="requestedClaims">An optional collection of additional claims requested, which may not necessarily
/// be tied to specific scopes but are required by the client.</param>
/// <returns>An IEnumerable of strings, each representing a claim name that should be included based on the
/// requested scopes and additional claims.</returns>
IEnumerable<string> GetRequestedClaims(IEnumerable<string> scopes, IEnumerable<string>? requestedClaims);

/// <summary>
/// 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.
/// </summary>
IEnumerable<string> ScopesSupported { get; }

/// <summary>
/// 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.
/// </summary>
IEnumerable<string> ClaimsSupported { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

using Abblix.Oidc.Server.Features.ClientInformation;

namespace Abblix.Oidc.Server.Common.Interfaces;
namespace Abblix.Oidc.Server.Features.UserInfo;

/// <summary>
/// Defines the interface for a service that converts user subject identifiers according to the client's specified
Expand Down
Loading

0 comments on commit 36d75c3

Please sign in to comment.