Browse Source

Add a ValidateClientType handler for introspection and revocation to reject requests made by public clients and containing a client secret

pull/837/head
Kévin Chalet 6 years ago
parent
commit
96a03a9d21
  1. 2
      src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
  2. 9
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  3. 87
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  4. 87
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

2
src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs

@ -383,7 +383,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
/// </returns>
ValueTask<bool> ValidateClientSecretAsync([NotNull] object application, string secret, CancellationToken cancellationToken = default);
ValueTask<bool> ValidateClientSecretAsync([NotNull] object application, [NotNull] string secret, CancellationToken cancellationToken = default);
/// <summary>
/// Validates the redirect_uri to ensure it's associated with an application.

9
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -1049,12 +1049,16 @@ namespace OpenIddict.Core
/// whose result returns a boolean indicating whether the client secret was valid.
/// </returns>
public virtual async ValueTask<bool> ValidateClientSecretAsync(
[NotNull] TApplication application, string secret, CancellationToken cancellationToken = default)
[NotNull] TApplication application, [NotNull] string secret, CancellationToken cancellationToken = default)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(secret))
{
throw new ArgumentException("The secret cannot be null or empty.", nameof(secret));
}
if (await IsPublicAsync(application, cancellationToken))
{
@ -1067,7 +1071,8 @@ namespace OpenIddict.Core
if (string.IsNullOrEmpty(value))
{
Logger.LogError("Client authentication failed for {Client} because " +
"no client secret was associated with the application.");
"no client secret was associated with the application.",
await GetClientIdAsync(application, cancellationToken));
return false;
}

87
src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs

@ -44,6 +44,7 @@ namespace OpenIddict.Server
ValidateTokenParameter.Descriptor,
ValidateClientIdParameter.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
@ -527,6 +528,90 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Contains the logic responsible of rejecting introspection requests made by applications
/// whose client type is not compatible with the presence or absence of a client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientType : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
.ToString());
public ValidateClientType([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateIntrospectionRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException("The client application details cannot be found in the database.");
}
if (await _applicationManager.IsPublicAsync(application))
{
// Reject introspection requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogError("The introspection request was rejected because the public application '{ClientId}' " +
"was not allowed to send a client secret.", context.ClientId);
context.Reject(
error: Errors.InvalidRequest,
description: "The 'client_secret' parameter is not valid for this client application.");
return;
}
return;
}
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogError("The introspection request was rejected because the confidential or hybrid application " +
"'{ClientId}' didn't specify a client secret.", context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: "The 'client_secret' parameter required for this client application is missing.");
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting introspection requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
@ -554,7 +639,7 @@ namespace OpenIddict.Server
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.Build();
/// <summary>

87
src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

@ -39,6 +39,7 @@ namespace OpenIddict.Server
ValidateTokenParameter.Descriptor,
ValidateClientIdParameter.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
@ -475,6 +476,90 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests made by applications
/// whose client type is not compatible with the presence or absence of a client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientType : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
.Append("Alternatively, you can disable the built-in database-based server features by enabling ")
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
.ToString());
public ValidateClientType([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public async ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException("The client application details cannot be found in the database.");
}
if (await _applicationManager.IsPublicAsync(application))
{
// Reject revocation requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogError("The revocation request was rejected because the public application '{ClientId}' " +
"was not allowed to send a client secret.", context.ClientId);
context.Reject(
error: Errors.InvalidRequest,
description: "The 'client_secret' parameter is not valid for this client application.");
return;
}
return;
}
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogError("The revocation request was rejected because the confidential or hybrid application " +
"'{ClientId}' didn't specify a client secret.", context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: "The 'client_secret' parameter required for this client application is missing.");
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting revocation requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
@ -502,7 +587,7 @@ namespace OpenIddict.Server
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.Build();
/// <summary>

Loading…
Cancel
Save