Skip to content

Commit

Permalink
Added registration and metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
kirill-abblix committed Sep 28, 2024
1 parent 88e4256 commit 358ac72
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Abblix OIDC Server Library
// Copyright (c) Abblix LLP. All rights reserved.
//
// DISCLAIMER: This software is provided 'as-is', without any express or implied
// warranty. Use at your own risk. Abblix LLP is not liable for any damages
// arising from the use of this software.
//
// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed
// in any form outside of the official GitHub repository at:
// https://github.com/Abblix/OIDC.Server. All development and modifications
// must occur within the official repository and are managed solely by Abblix LLP.
//
// Unauthorized use, modification, or distribution of this software is strictly
// prohibited and may be subject to legal action.
//
// For full licensing terms, please visit:
//
// https://oidc.abblix.com/license
//
// CONTACT: For license inquiries or permissions, contact Abblix LLP at
// info@abblix.com

namespace Abblix.Oidc.Server.Common.Constants;

/// <summary>
/// Defines the available delivery modes for backchannel token delivery in Client-Initiated Backchannel Authentication
/// (CIBA). These modes specify how the authentication server communicates the result of the backchannel authentication
/// process to the client.
/// </summary>
public class BackchannelTokenDeliveryModes
{
/// <summary>
/// The "poll" mode where the client periodically polls the authorization server to check if the user has been
/// authenticated. This method is useful in cases where the client prefers to control the polling interval and
/// the process.
/// </summary>
public const string Poll = "poll";

/// <summary>
/// The "ping" mode where the authorization server notifies the client via a callback when the user has been
/// authenticated. The client still needs to make a subsequent request to retrieve the token.
/// </summary>
public const string Ping = "ping";

/// <summary>
/// The "push" mode where the authorization server directly pushes the token to the client once the user has been
/// authenticated. This method streamlines the process by delivering the token to the client without the need for
/// further requests.
/// </summary>
public const string Push = "push";
}
8 changes: 4 additions & 4 deletions Abblix.Oidc.Server/Common/Constants/GrantTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public static class GrantTypes
public const string ClientCredentials = "client_credentials";

/// <summary>
/// Represents the Refresh Token grant type. Used to obtain a new access token using a refresh token.
/// Helpful for maintaining user sessions without requiring reauthentication.
/// Represents the Refresh Token grant type. Used to get a new access token using a refresh token.
/// Helpful for maintaining user sessions without requiring re-authentication.
/// </summary>
public const string RefreshToken = "refresh_token";

/// <summary>
/// Represents the Implicit grant type. Used in single-page applications to obtain access tokens directly
/// Represents the Implicit grant type. Used in single-page applications to get access tokens directly
/// from the authorization endpoint. Suitable for browser-based applications.
/// </summary>
public const string Implicit = "implicit";
Expand All @@ -71,7 +71,7 @@ public static class GrantTypes

/// <summary>
/// Represents the Device Authorization grant type. This grant type is used in scenarios where the client device
/// lacks a browser or has limited input capabilities, allowing it to obtain user authorization from another device
/// lacks a browser or has limited input capabilities, allowing it to get user authorization from another device
/// with better input capabilities. It is particularly useful for devices in the IoT (Internet of Things) sector
/// and smart devices that require user interaction for authorization.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ namespace Abblix.Oidc.Server.Common;
/// <typeparamref name="T"/>, or result in an error with an error code and description.
/// </summary>
/// <typeparam name="T">The type of the value returned in case of a successful result.</typeparam>
public abstract record OperationResult<T>
public abstract record Result<T>
{
private OperationResult() { }
private Result() { }

/// <summary>
/// Represents a successful result containing a value of type <typeparamref name="T"/>.
/// Represents a successful result containing a value of specific type.
/// </summary>
/// <param name="Value">The value returned by the successful operation.</param>
public sealed record Success(T Value) : OperationResult<T>
public sealed record Success(T Value) : Result<T>
{
/// <summary>
/// Returns a string that represents the current object, either the successful value or an error description.
Expand All @@ -54,7 +54,7 @@ public sealed record Success(T Value) : OperationResult<T>
/// </summary>
/// <param name="ErrorCode">The code representing the type or cause of the error.</param>
/// <param name="ErrorDescription">A human-readable description of the error.</param>
public sealed record Error(string ErrorCode, string ErrorDescription) : OperationResult<T>
public sealed record Error(string ErrorCode, string ErrorDescription) : Result<T>
{
/// <summary>
/// Returns a string that represents the current object, either the successful value or an error description.
Expand All @@ -71,12 +71,12 @@ public override string ToString()
/// Implicitly converts a value of type <typeparamref name="T"/> into a successful <see cref="Success"/> result.
/// </summary>
/// <param name="value">The value to be wrapped as a successful result.</param>
public static implicit operator OperationResult<T>(T value) => new Success(value);
public static implicit operator Result<T>(T value) => new Success(value);

/// <summary>
/// Implicitly converts an <see cref="ErrorResponse"/> into an <see cref="Error"/> result.
/// </summary>
/// <param name="error">The error response to be wrapped as an error result.</param>
public static implicit operator OperationResult<T>(ErrorResponse error)
public static implicit operator Result<T>(ErrorResponse error)
=> new Error(error.Error, error.ErrorDescription);
}
5 changes: 3 additions & 2 deletions Abblix.Oidc.Server/Common/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static class StringExtensions
/// </summary>
/// <param name="values">The array of strings to check.</param>
/// <param name="flag">The flag to search for.</param>
/// <returns>True if the flag is found; otherwise, false.</returns>
/// <returns>True, if the flag is found, otherwise, false.</returns>
public static bool HasFlag(this string[]? values, string flag)
=> values != null && values.Contains(flag, StringComparer.OrdinalIgnoreCase);

Expand All @@ -56,7 +56,8 @@ public static bool TryParse(this string source, string[] allowedValues, char sep
var result = new List<string>(sourceValues.Length);
foreach (var sourceValue in sourceValues)
{
var allowedValue = allowedValues.FirstOrDefault(value => string.Equals(value, sourceValue, StringComparison.OrdinalIgnoreCase));
var allowedValue = allowedValues.FirstOrDefault(
value => string.Equals(value, sourceValue, StringComparison.OrdinalIgnoreCase));
if (allowedValue == null)
{
values = default!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ public async Task<FetchResult> FetchAsync(AuthorizationRequest request)
var fetchResult = await FetchAsync(request, request.Request);
return fetchResult switch
{
OperationResult<AuthorizationRequest>.Success(var authorizationRequest) => authorizationRequest,
Result<AuthorizationRequest>.Success(var authorizationRequest) => authorizationRequest,

OperationResult<AuthorizationRequest>.Error(var error, var description)
Result<AuthorizationRequest>.Error(var error, var description)
=> ErrorFactory.ValidationError(error, description),

_ => throw new UnexpectedTypeException(nameof(fetchResult), fetchResult.GetType()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ public async Task<BackChannelAuthenticationResponse> HandleAsync(
var fetchResult = await _fetcher.FetchAsync(request);
switch (fetchResult)
{
case OperationResult<BackChannelAuthenticationRequest>.Success(var requestObject):
case Result<BackChannelAuthenticationRequest>.Success(var requestObject):
request = requestObject;
break;

case OperationResult<BackChannelAuthenticationRequest>.Error(var error, var description):
case Result<BackChannelAuthenticationRequest>.Error(var error, var description):
return new BackChannelAuthenticationError(error, description);

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,18 @@ public CompositeRequestFetcher(IBackChannelAuthenticationRequestFetcher[] fetche
/// <param name="request">The backchannel authentication request to be processed.</param>
/// <returns>A <see cref="FetchResult"/> that represents the outcome of the fetching process.
/// It could be a success, fault, or an unexpected type error if the result is not handled correctly.</returns>
public async Task<OperationResult<BackChannelAuthenticationRequest>> FetchAsync(BackChannelAuthenticationRequest request)
public async Task<Result<BackChannelAuthenticationRequest>> FetchAsync(BackChannelAuthenticationRequest request)
{
foreach (var fetcher in _fetchers)
{
var result = await fetcher.FetchAsync(request);
switch (result)
{
case OperationResult<BackChannelAuthenticationRequest>.Success(var success):
case Result<BackChannelAuthenticationRequest>.Success(var success):
request = success;
continue;

case OperationResult<BackChannelAuthenticationRequest>.Error error:
case Result<BackChannelAuthenticationRequest>.Error error:
return error;

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ public interface IBackChannelAuthenticationRequestFetcher
/// <param name="request">The backchannel authentication request to be fetched and validated.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="FetchResult"/>
/// indicating whether the fetch was successful or if it resulted in an error.</returns>
Task<OperationResult<BackChannelAuthenticationRequest>> FetchAsync(BackChannelAuthenticationRequest request);
Task<Result<BackChannelAuthenticationRequest>> FetchAsync(BackChannelAuthenticationRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public RequestObjectFetcher(
/// encapsulates the full request details.
/// </param>
/// <returns>
/// A task representing the asynchronous operation. The task result contains an <see cref="OperationResult{T}"/>
/// A task representing the asynchronous operation. The task result contains an <see cref="Result{T}"/>
/// with either a successfully processed <see cref="Model.BackChannelAuthenticationRequest"/> or an error result
/// indicating validation issues.
/// </returns>
Expand All @@ -70,8 +70,8 @@ public RequestObjectFetcher(
/// the backchannel authentication request.
/// If the JWT is valid, it binds the JWT payload to the backchannel authentication request model.
/// If the JWT is invalid, the method logs a warning and returns an error result encapsulated in
/// an <see cref="OperationResult{T}"/>.
/// an <see cref="Result{T}"/>.
/// </remarks>
public Task<OperationResult<BackChannelAuthenticationRequest>> FetchAsync(BackChannelAuthenticationRequest request)
public Task<Result<BackChannelAuthenticationRequest>> FetchAsync(BackChannelAuthenticationRequest request)
=> FetchAsync(request, request.Request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ public UserIdentityValidator(
var idTokenResult = await ValidateIdTokenHint(context, request.IdTokenHint);
switch (idTokenResult)
{
case OperationResult<JsonWebToken>.Success(var idToken):
case Result<JsonWebToken>.Success(var idToken):
context.IdToken = idToken;
break;

case OperationResult<JsonWebToken>.Error(var error, var description):
case Result<JsonWebToken>.Error(var error, var description):
return new BackChannelAuthenticationValidationError(error, description);
}
}
Expand All @@ -141,10 +141,10 @@ public UserIdentityValidator(
/// <param name="context">The validation context containing the client information.</param>
/// <param name="idTokenHint">The ID token hint string to be validated.</param>
/// <returns>
/// An <see cref="OperationResult{JsonWebToken}"/> representing the validation result,
/// An <see cref="Result{T}"/> representing the validation result,
/// which can either be a successful token or an error.
/// </returns>
private async Task<OperationResult<JsonWebToken>> ValidateIdTokenHint(
private async Task<Result<JsonWebToken>> ValidateIdTokenHint(
BackChannelAuthenticationValidationContext context,
string idTokenHint)
{
Expand Down Expand Up @@ -173,7 +173,7 @@ private async Task<OperationResult<JsonWebToken>> ValidateIdTokenHint(
}

// Helper method to generate an error response for invalid requests
OperationResult<JsonWebToken>.Error InvalidRequest(string description)
Result<JsonWebToken>.Error InvalidRequest(string description)
=> new (ErrorCodes.InvalidRequest, description);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
using Abblix.Oidc.Server.Features.ClientAuthentication;
using Abblix.Oidc.Server.Features.Tokens.Validation;
using Abblix.Oidc.Server.Model;

using Microsoft.Extensions.Logging;


namespace Abblix.Oidc.Server.Endpoints.Revocation;
Expand All @@ -44,18 +44,26 @@ public class RevocationRequestValidator : IRevocationRequestValidator
/// Initializes a new instance of the <see cref="RevocationRequestValidator"/> class.
/// The constructor sets up the validator with necessary components for client authentication and JWT validation.
/// </summary>
/// <param name="logger">Provides logging capabilities to record validation outcomes and errors.</param>
/// <param name="clientAuthenticator">
/// The client request authenticator to be used in the validation process.</param>
/// The client request authenticator to be used in the validation process. Ensures that the client sending the
/// revocation request is authenticated and authorized to revoke tokens.
/// </param>
/// <param name="jwtValidator">
/// The JWT validator to be used for validating the token included in the revocation request.</param>
/// The JWT validator to be used for validating the token included in the revocation request. Ensures that
/// the token is valid and that it belongs to the client requesting revocation.
/// </param>
public RevocationRequestValidator(
ILogger<RevocationRequestValidator> logger,
IClientAuthenticator clientAuthenticator,
IAuthServiceJwtValidator jwtValidator)
{
_logger = logger;
_clientAuthenticator = clientAuthenticator;
_jwtValidator = jwtValidator;
}

private readonly ILogger _logger;
private readonly IClientAuthenticator _clientAuthenticator;
private readonly IAuthServiceJwtValidator _jwtValidator;

Expand All @@ -65,17 +73,24 @@ public RevocationRequestValidator(
/// that the token belongs to the authenticated client and is valid as per JWT standards.
/// </summary>
/// <param name="revocationRequest">
/// The revocation request to be validated. Contains the token to be revoked and client information.</param>
/// The revocation request to be validated. Contains the token to be revoked and client information.
/// </param>
/// <param name="clientRequest">Additional client request information for contextual validation.</param>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation, which upon completion will yield a
/// <see cref="RevocationRequestValidationResult"/>.
/// The result indicates whether the request is valid or contains any errors.
/// <see cref="RevocationRequestValidationResult"/>. The result indicates whether the request is valid or
/// contains any errors.
/// </returns>
/// <remarks>
/// This method follows the OAuth 2.0 revocation flow, ensuring that the token being revoked belongs to
/// the authenticated client, protecting against cross-client token revocation. In case of validation failure,
/// it logs a warning with the specific cause.
/// </remarks>
public async Task<RevocationRequestValidationResult> ValidateAsync(
RevocationRequest revocationRequest,
ClientRequest clientRequest)
{
// Authenticate the client making the revocation request.
var clientInfo = await _clientAuthenticator.TryAuthenticateClientAsync(clientRequest);
if (clientInfo == null)
{
Expand All @@ -87,16 +102,21 @@ public async Task<RevocationRequestValidationResult> ValidateAsync(
var result = await _jwtValidator.ValidateAsync(revocationRequest.Token);
switch (result)
{
// If token validation fails, log the error and return an invalid token result.
case JwtValidationError error:
_logger.LogWarning("The token validation failed: {@Error}", error);
return ValidRevocationRequest.InvalidToken(revocationRequest);

// If the token was issued to a different client, log a warning and return an invalid token result.
case ValidJsonWebToken { Token.Payload.ClientId: var clientId } when clientId != clientInfo.ClientId:
//TODO maybe log the message: The token was issued to another client?
_logger.LogWarning("The token was issued to another client {ClientId}", clientId);
return ValidRevocationRequest.InvalidToken(revocationRequest);

// If the token is valid and belongs to the authenticated client, return a valid revocation request.
case ValidJsonWebToken { Token: var token }:
return new ValidRevocationRequest(revocationRequest, token);

case JwtValidationError: //TODO log error
return ValidRevocationRequest.InvalidToken(revocationRequest);

// Unexpected result type, throw an exception to indicate a misconfiguration or unexpected validation outcome.
default:
throw new UnexpectedTypeException(nameof(result), result.GetType());
}
Expand Down
Loading

0 comments on commit 358ac72

Please sign in to comment.