Browse Source

Block device requests sent by clients that were not granted the device code grant permission and add integration tests for the device and verification endpoints

pull/1176/head
Kévin Chalet 5 years ago
parent
commit
22ff1fc02e
  1. 12
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 6
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  3. 114
      src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
  4. 1330
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs
  5. 2
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs
  6. 17
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

12
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1116,6 +1116,9 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
<data name="ID0286" xml:space="preserve">
<value>The specified principal doesn't contain a valid claims-based identity.</value>
</data>
<data name="ID0287" xml:space="preserve">
<value>The device grant must be allowed when enabling the device endpoint.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>
@ -1467,6 +1470,9 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
<data name="ID2120" xml:space="preserve">
<value>Callback URLs must be valid absolute URLs.</value>
</data>
<data name="ID2121" xml:space="preserve">
<value>The client application is not allowed to use the device code flow.</value>
</data>
<data name="ID4000" xml:space="preserve">
<value>The '{0}' parameter shouldn't be null or empty at this point.</value>
</data>
@ -2029,6 +2035,12 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6181" xml:space="preserve">
<value>The authorization request was rejected because the application '{ClientId}' was not allowed to use the '{ResponseType}' response type.</value>
</data>
<data name="ID6182" xml:space="preserve">
<value>The device request was rejected because the application '{ClientId}' was not allowed to use the device code flow.</value>
</data>
<data name="ID6183" xml:space="preserve">
<value>The device request was rejected because the application '{ClientId}' was not allowed to request the '{Scope}' scope.</value>
</data>
<data name="ID8000" xml:space="preserve">
<value>Removes orphaned tokens and authorizations from the database.</value>
</data>

6
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -110,6 +110,12 @@ namespace OpenIddict.Server
throw new InvalidOperationException(SR.GetResourceString(SR.ID0080));
}
// Ensure the device grant is allowed when the device endpoint is enabled.
if (options.DeviceEndpointUris.Count > 0 && !options.GrantTypes.Contains(GrantTypes.DeviceCode))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0287));
}
// Ensure the grant types/response types configuration is consistent.
foreach (var type in options.ResponseTypes)
{

114
src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs

@ -41,11 +41,13 @@ namespace OpenIddict.Server
* Device request validation:
*/
ValidateClientIdParameter.Descriptor,
ValidateScopeParameter.Descriptor,
ValidateScopes.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
ValidateScopePermissions.Descriptor,
/*
@ -364,6 +366,43 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Contains the logic responsible of rejecting device requests that don't specify a valid scope parameter.
/// </summary>
public class ValidateScopeParameter : IOpenIddictServerHandler<ValidateDeviceRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.UseSingletonHandler<ValidateScopeParameter>()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateDeviceRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject device requests that specify scope=offline_access if the refresh token flow is not enabled.
if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2035(Scopes.OfflineAccess));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that use unregistered scopes.
/// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled.
@ -392,7 +431,7 @@ namespace OpenIddict.Server
new ValidateScopes(provider.GetService<IOpenIddictScopeManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -486,7 +525,7 @@ namespace OpenIddict.Server
context.Reject(
error: Errors.InvalidClient,
description: SR.GetResourceString(SR.ID2052));
description: SR.FormatID2052(Parameters.ClientId));
return;
}
@ -685,6 +724,75 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Contains the logic responsible of rejecting device requests made by unauthorized applications.
/// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled.
/// </summary>
public class ValidateGrantTypePermissions : IOpenIddictServerHandler<ValidateDeviceRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.AddFilter<RequireGrantTypePermissionsEnabled>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateGrantTypePermissions>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateDeviceRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
// Reject the request if the application is not allowed to use the device code grant.
if (!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.DeviceCode))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6182), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: SR.GetResourceString(SR.ID2121));
return;
}
// Reject the request if the offline_access scope was request and
// if the application is not allowed to use the refresh token grant.
if (context.Request.HasScope(Scopes.OfflineAccess) &&
!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6183), context.ClientId, Scopes.OfflineAccess);
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2065(Scopes.OfflineAccess));
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting device requests made by applications
/// that haven't been granted the appropriate grant type permission.
@ -708,7 +816,7 @@ namespace OpenIddict.Server
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireScopePermissionsEnabled>()
.UseScopedHandler<ValidateScopePermissions>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

1330
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs

File diff suppressed because it is too large

2
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs

@ -411,6 +411,8 @@ namespace OpenIddict.Server.IntegrationTests
options.Configure(options => options.GrantTypes.Clear());
options.Configure(options => options.GrantTypes.Add(GrantTypes.Implicit));
options.Configure(options => options.ResponseTypes.Clear());
options.Configure(options => options.DeviceEndpointUris.Clear());
options.Configure(options => options.VerificationEndpointUris.Clear());
options.SetTokenEndpointUris(Array.Empty<Uri>());
});

17
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

@ -3527,14 +3527,17 @@ namespace OpenIddict.Server.IntegrationTests
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetConfigurationEndpointUris("/.well-known/openid-configuration")
.SetCryptographyEndpointUris("/.well-known/jwks")
.SetDeviceEndpointUris("/connect/device")
.SetIntrospectionEndpointUris("/connect/introspect")
.SetLogoutEndpointUris("/connect/logout")
.SetRevocationEndpointUris("/connect/revoke")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo");
.SetUserinfoEndpointUris("/connect/userinfo")
.SetVerificationEndpointUris("/connect/verification");
options.AllowAuthorizationCodeFlow()
.AllowClientCredentialsFlow()
.AllowDeviceCodeFlow()
.AllowHybridFlow()
.AllowImplicitFlow()
.AllowNoneFlow()
@ -3563,6 +3566,9 @@ namespace OpenIddict.Server.IntegrationTests
options.AddEventHandler<ValidateAuthorizationRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateDeviceRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateIntrospectionRequestContext>(builder =>
builder.UseInlineHandler(context => default));
@ -3574,6 +3580,15 @@ namespace OpenIddict.Server.IntegrationTests
options.AddEventHandler<ValidateTokenRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateVerificationRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ProcessSignInContext>(builder =>
builder.UseInlineHandler(context => default));
});
}

Loading…
Cancel
Save