Browse Source

Reject authorization code/device authorization code grant requests containing a "scope" parameter

pull/1587/head 4.0.0-preview6
Kévin Chalet 4 years ago
parent
commit
ffd47678fc
  1. 61
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  2. 146
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs

61
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -44,6 +44,7 @@ public static partial class OpenIddictServerHandlers
ValidateRefreshTokenParameter.Descriptor,
ValidateResourceOwnerCredentialsParameters.Descriptor,
ValidateProofKeyForCodeExchangeParameters.Descriptor,
ValidateScopeParameter.Descriptor,
ValidateScopes.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
@ -706,6 +707,53 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests that specify an invalid scope parameter.
/// </summary>
public sealed class ValidateScopeParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.UseSingletonHandler<ValidateScopeParameter>()
.SetOrder(ValidateResourceOwnerCredentialsParameters.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject authorization code and device authorization code requests that contain a "scope" parameter.
//
// Note: using the "scope" parameter with grant_type=refresh_token is deliberately allowed
// by the specification and is typically used to retrieve an access token granting a more
// limited access than the scopes initially specified in the authorization request.
//
// For more information, see https://tools.ietf.org/html/rfc6749#section-6.
if (!string.IsNullOrEmpty(context.Request.Scope) && (context.Request.IsAuthorizationCodeGrantType() ||
context.Request.IsDeviceCodeGrantType()))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6094), Parameters.Scope);
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2074(Parameters.Scope),
uri: SR.FormatID8000(SR.ID2074));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for 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.
@ -734,7 +782,7 @@ public static partial class OpenIddictServerHandlers
new ValidateScopes(provider.GetService<IOpenIddictScopeManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000)
.SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -1576,8 +1624,8 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting token requests that specify scopes that
/// were not initially granted by the resource owner during the authorization request.
/// Contains the logic responsible for rejecting refresh token requests that specify scopes
/// that were not initially granted by the resource owner during the authorization request.
/// </summary>
public sealed class ValidateGrantedScopes : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
@ -1599,12 +1647,7 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
{
return default;
}
if (string.IsNullOrEmpty(context.Request.Scope))
if (string.IsNullOrEmpty(context.Request.Scope) || !context.Request.IsRefreshTokenGrantType())
{
return default;
}

146
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs

@ -339,6 +339,60 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.NotNull(response.AccessToken);
}
[Fact]
public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsSent()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = GrantTypes.AuthorizationCode,
Scope = "profile phone"
});
// Assert
Assert.Equal(Errors.InvalidRequest, response.Error);
Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri);
}
[Fact]
public async Task ValidateTokenRequest_DeviceAuthorizationCodeCausesAnErrorWhenScopeIsSent()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
GrantType = GrantTypes.DeviceCode,
Scope = "profile phone"
});
// Assert
Assert.Equal(Errors.InvalidRequest, response.Error);
Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri);
}
[Fact]
public async Task ValidateTokenRequest_InvalidAuthorizationCodeCausesAnError()
{
@ -1037,98 +1091,6 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.NotNull(response.AccessToken);
}
[Fact]
public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsUnexpected()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetScopes(Enumerable.Empty<string>())
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = GrantTypes.AuthorizationCode,
Scope = "profile phone"
});
// Assert
Assert.Equal(Errors.InvalidGrant, response.Error);
Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri);
}
[Fact]
public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsInvalid()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetScopes("profile", "email")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = GrantTypes.AuthorizationCode,
Scope = "profile phone"
});
// Assert
Assert.Equal(Errors.InvalidGrant, response.Error);
Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri);
}
[Fact]
public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenScopeIsUnexpected()
{

Loading…
Cancel
Save