Browse Source

Add MapInternalClaims and handle multiple public "scope" claims for backward compatibility

pull/913/head
Kévin Chalet 6 years ago
parent
commit
ac5f302b01
  1. 8
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  2. 8
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  3. 58
      src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
  4. 6
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs
  5. 98
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  6. 73
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  7. 9
      src/OpenIddict.Validation/OpenIddictValidationOptions.cs
  8. 28
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
  9. 4
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs
  10. 245
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
  11. 4
      test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

8
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -150,7 +150,7 @@ namespace Mvc.Server
client : await _applicationManager.GetIdAsync(application),
status : Statuses.Valid,
type : AuthorizationTypes.Permanent,
scopes : ImmutableArray.CreateRange(request.GetScopes())).ToListAsync();
scopes : request.GetScopes()).ToListAsync();
switch (await _applicationManager.GetConsentTypeAsync(application))
{
@ -189,7 +189,7 @@ namespace Mvc.Server
subject : await _userManager.GetUserIdAsync(user),
client : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent,
scopes : ImmutableArray.CreateRange(principal.GetScopes()));
scopes : principal.GetScopes());
}
principal.SetInternalAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
@ -245,7 +245,7 @@ namespace Mvc.Server
client : await _applicationManager.GetIdAsync(application),
status : Statuses.Valid,
type : AuthorizationTypes.Permanent,
scopes : ImmutableArray.CreateRange(request.GetScopes())).ToListAsync();
scopes : request.GetScopes()).ToListAsync();
// Note: the same check is already made in the other action but is repeated
// here to ensure a malicious user can't abuse this POST-only endpoint and
@ -281,7 +281,7 @@ namespace Mvc.Server
subject : await _userManager.GetUserIdAsync(user),
client : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent,
scopes : ImmutableArray.CreateRange(principal.GetScopes()));
scopes : principal.GetScopes());
}
principal.SetInternalAuthorizationId(await _authorizationManager.GetIdAsync(authorization));

8
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -90,18 +90,18 @@ namespace OpenIddict.Abstractions
public const string AccessTokenLifetime = "oi_act_lft";
public const string AuthorizationId = "oi_au_id";
public const string AuthorizationCodeLifetime = "oi_auc_lft";
public const string ClaimDestinations = "oi_cl_dstn";
public const string ClaimDestinationsMap = "oi_cl_dstn";
public const string CodeChallenge = "oi_cd_chlg";
public const string CodeChallengeMethod = "oi_cd_chlg_meth";
public const string DeviceCodeId = "oi_dvc_id";
public const string DeviceCodeLifetime = "oi_dvc_lft";
public const string IdentityTokenLifetime = "oi_idt_lft";
public const string Nonce = "oi_nce";
public const string Presenters = "oi_prst";
public const string Presenter = "oi_prst";
public const string RedirectUri = "oi_reduri";
public const string RefreshTokenLifetime = "oi_reft_lft";
public const string Resources = "oi_rsrc";
public const string Scopes = "oi_scp";
public const string Resource = "oi_rsrc";
public const string Scope = "oi_scp";
public const string TokenId = "oi_tkn_id";
public const string TokenType = "oi_tkn_typ";
public const string UserCodeLifetime = "oi_usrc_lft";

58
src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs

@ -1167,7 +1167,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns>The presenters list or an empty set if the claims cannot be found.</returns>
public static ImmutableArray<string> GetPresenters([NotNull] this ClaimsPrincipal principal)
=> principal.GetClaims(Claims.Private.Presenters);
=> principal.GetClaims(Claims.Private.Presenter);
/// <summary>
/// Gets the resources list stored in the claims principal.
@ -1175,7 +1175,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns>The resources list or an empty set if the claims cannot be found.</returns>
public static ImmutableArray<string> GetResources([NotNull] this ClaimsPrincipal principal)
=> principal.GetClaims(Claims.Private.Resources);
=> principal.GetClaims(Claims.Private.Resource);
/// <summary>
/// Gets the scopes list stored in the claims principal.
@ -1183,7 +1183,7 @@ namespace OpenIddict.Abstractions
/// <param name="principal">The claims principal.</param>
/// <returns>The scopes list or an empty set if the claim cannot be found.</returns>
public static ImmutableArray<string> GetScopes([NotNull] this ClaimsPrincipal principal)
=> principal.GetClaims(Claims.Private.Scopes);
=> principal.GetClaims(Claims.Private.Scope);
/// <summary>
/// Gets the access token lifetime associated with the claims principal.
@ -1290,15 +1290,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The audience cannot be null or empty.", nameof(audience));
}
foreach (var claim in principal.FindAll(Claims.Audience))
{
if (string.Equals(claim.Value, audience, StringComparison.Ordinal))
{
return true;
}
}
return false;
return principal.HasClaim(Claims.Audience, audience);
}
/// <summary>
@ -1313,7 +1305,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Presenters).Any();
return principal.FindAll(Claims.Private.Presenter).Any();
}
/// <summary>
@ -1334,15 +1326,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The presenter cannot be null or empty.", nameof(presenter));
}
foreach (var claim in principal.FindAll(Claims.Private.Presenters))
{
if (string.Equals(claim.Value, presenter, StringComparison.Ordinal))
{
return true;
}
}
return false;
return principal.HasClaim(Claims.Private.Presenter, presenter);
}
/// <summary>
@ -1357,7 +1341,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Resources).Any();
return principal.FindAll(Claims.Private.Resource).Any();
}
/// <summary>
@ -1378,15 +1362,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
}
foreach (var claim in principal.FindAll(Claims.Private.Resources))
{
if (string.Equals(claim.Value, resource, StringComparison.Ordinal))
{
return true;
}
}
return false;
return principal.HasClaim(Claims.Private.Resource, resource);
}
/// <summary>
@ -1401,7 +1377,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentNullException(nameof(principal));
}
return principal.FindAll(Claims.Private.Scopes).Any();
return principal.FindAll(Claims.Private.Scope).Any();
}
/// <summary>
@ -1422,15 +1398,7 @@ namespace OpenIddict.Abstractions
throw new ArgumentException("The scope cannot be null or empty.", nameof(scope));
}
foreach (var claim in principal.FindAll(Claims.Private.Scopes))
{
if (string.Equals(claim.Value, scope, StringComparison.Ordinal))
{
return true;
}
}
return false;
return principal.HasClaim(Claims.Private.Scope, scope);
}
/// <summary>
@ -1514,7 +1482,7 @@ namespace OpenIddict.Abstractions
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetPresenters(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] ImmutableArray<string> presenters)
=> principal.SetClaims(Claims.Private.Presenters, presenters);
=> principal.SetClaims(Claims.Private.Presenter, presenters);
/// <summary>
/// Sets the presenters list in the claims principal.
@ -1547,7 +1515,7 @@ namespace OpenIddict.Abstractions
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetResources(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] ImmutableArray<string> resources)
=> principal.SetClaims(Claims.Private.Resources, resources);
=> principal.SetClaims(Claims.Private.Resource, resources);
/// <summary>
/// Sets the resources list in the claims principal.
@ -1580,7 +1548,7 @@ namespace OpenIddict.Abstractions
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetScopes(
[NotNull] this ClaimsPrincipal principal, [CanBeNull] ImmutableArray<string> scopes)
=> principal.SetClaims(Claims.Private.Scopes, scopes);
=> principal.SetClaims(Claims.Private.Scope, scopes);
/// <summary>
/// Sets the scopes list in the claims principal.

6
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs

@ -237,11 +237,11 @@ namespace OpenIddict.Server.DataProtection
Claims.Private.CodeChallengeMethod => false,
Claims.Private.IdentityTokenLifetime => false,
Claims.Private.Nonce => false,
Claims.Private.Presenters => false,
Claims.Private.Presenter => false,
Claims.Private.RedirectUri => false,
Claims.Private.RefreshTokenLifetime => false,
Claims.Private.Resources => false,
Claims.Private.Scopes => false,
Claims.Private.Resource => false,
Claims.Private.Scope => false,
Claims.Private.TokenId => false,
_ => true

98
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -38,6 +38,7 @@ namespace OpenIddict.Server
NormalizeUserCode.Descriptor,
ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor,
MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateTokenEntry.Descriptor,
@ -496,26 +497,79 @@ namespace OpenIddict.Server
});
// Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object).
if (token.TryGetPayloadValue(Claims.Private.ClaimDestinations, out ImmutableDictionary<string, string[]> destinations))
if (token.TryGetPayloadValue(Claims.Private.ClaimDestinationsMap, out ImmutableDictionary<string, string[]> destinations))
{
context.Principal.SetDestinations(destinations);
}
if (context.Principal.HasTokenType(TokenTypeHints.AccessToken))
context.Logger.LogTrace("The token '{Token}' was successfully validated and the following claims " +
"could be extracted: {Claims}.", context.Token, context.Principal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of mapping internal claims used by OpenIddict.
/// </summary>
public class MapInternalClaims : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<MapInternalClaims>()
.SetOrder(ValidateIdentityModelToken.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 ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
{
if (context == null)
{
// Map the standardized "azp" and "scope" claims to their "oi_" equivalent so that
// the ClaimsPrincipal extensions exposed by OpenIddict return consistent results.
context.Principal.SetPresenters(context.Principal.GetClaims(Claims.AuthorizedParty));
throw new ArgumentNullException(nameof(context));
}
// Note: starting in OpenIddict 3.0, the public "scope" claim is formatted
// as a unique space-separated string containing all the granted scopes.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-03 for more information.
context.Principal.SetScopes(context.Principal.GetClaim(Claims.Scope)
?.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries));
if (context.Principal == null)
{
return default;
}
context.Logger.LogTrace("The token '{Token}' was successfully validated and the following claims " +
"could be extracted: {Claims}.", context.Token, context.Principal.Claims);
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken))
{
return default;
}
// Map the standardized "azp" and "scope" claims to their "oi_" equivalent so that
// the ClaimsPrincipal extensions exposed by OpenIddict return consistent results.
if (!context.Principal.HasPresenter())
{
context.Principal.SetPresenters(context.Principal.GetClaims(Claims.AuthorizedParty));
}
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
// is formatted as a unique space-separated string containing all the granted scopes.
// To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-03 for more information.
if (!context.Principal.HasScope())
{
var scopes = context.Principal.GetClaims(Claims.Scope);
if (scopes.Length == 1)
{
scopes = scopes[0].Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray();
}
context.Principal.SetScopes(scopes);
}
return default;
}
@ -548,7 +602,7 @@ namespace OpenIddict.Server
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000)
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.Build();
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
@ -1378,7 +1432,7 @@ namespace OpenIddict.Server
// When the request is a verification request, don't flow the copy from the user code.
if (context.EndpointType == OpenIddictServerEndpointType.Verification &&
string.Equals(claims.Key, Claims.Private.Scopes, StringComparison.OrdinalIgnoreCase))
string.Equals(claims.Key, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase))
{
continue;
}
@ -1763,8 +1817,8 @@ namespace OpenIddict.Server
}
// Never exclude the presenters and scope private claims.
if (string.Equals(claim.Type, Claims.Private.Presenters, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Scopes, StringComparison.OrdinalIgnoreCase))
if (string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase))
{
return true;
}
@ -2708,9 +2762,9 @@ namespace OpenIddict.Server
// that are manually mapped to public standard azp/scope JWT claims.
var principal = context.AccessTokenPrincipal.Clone(claim => claim.Type switch
{
Claims.Private.Presenters => false,
Claims.Private.Scopes => false,
Claims.Private.TokenId => false,
Claims.Private.Presenter => false,
Claims.Private.Scope => false,
Claims.Private.TokenId => false,
_ => true
});
@ -2913,7 +2967,7 @@ namespace OpenIddict.Server
{
descriptor.Claims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[Claims.Private.ClaimDestinations] = destinations
[Claims.Private.ClaimDestinationsMap] = destinations
};
}
@ -3092,7 +3146,7 @@ namespace OpenIddict.Server
{
descriptor.Claims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[Claims.Private.ClaimDestinations] = destinations
[Claims.Private.ClaimDestinationsMap] = destinations
};
}
@ -3372,7 +3426,7 @@ namespace OpenIddict.Server
{
descriptor.Claims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[Claims.Private.ClaimDestinations] = destinations
[Claims.Private.ClaimDestinationsMap] = destinations
};
}

73
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -31,6 +31,7 @@ namespace OpenIddict.Validation
ValidateAccessTokenParameter.Descriptor,
ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor,
MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
@ -207,7 +208,6 @@ namespace OpenIddict.Validation
parameters = parameters.Clone();
parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key);
parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
parameters.ValidTypes = new[] { JsonWebTokenTypes.AccessToken };
// If the token cannot be validated, don't return an error to allow another handle to validate it.
var result = context.Options.JsonWebTokenHandler.ValidateToken(context.Token, parameters);
@ -220,20 +220,75 @@ namespace OpenIddict.Validation
// Attach the principal extracted from the token to the parent event context.
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
// Note: tokens that are considered valid at this point are guaranteed to be access tokens,
// as a "typ" header validation is performed by the JWT handler, based on the valid values
// set in the token validation parameters (by default, only "at+jwt" is considered valid).
context.Principal.SetClaim(Claims.Private.TokenType, TokenTypeHints.AccessToken);
context.Logger.LogTrace("The self-contained JWT token '{Token}' was successfully validated and the following " +
"claims could be extracted: {Claims}.", context.Token, context.Principal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of mapping internal claims used by OpenIddict.
/// </summary>
public class MapInternalClaims : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<MapInternalClaims>()
.SetOrder(ValidateIdentityModelToken.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 ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal == null)
{
return default;
}
// Map the standardized "azp" and "scope" claims to their "oi_" equivalent so that
// the ClaimsPrincipal extensions exposed by OpenIddict return consistent results.
context.Principal.SetPresenters(context.Principal.GetClaims(Claims.AuthorizedParty));
if (!context.Principal.HasPresenter())
{
context.Principal.SetPresenters(context.Principal.GetClaims(Claims.AuthorizedParty));
}
// Note: starting in OpenIddict 3.0, the public "scope" claim is formatted
// as a unique space-separated string containing all the granted scopes.
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
// is formatted as a unique space-separated string containing all the granted scopes.
// To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-03 for more information.
context.Principal.SetScopes(context.Principal.GetClaim(Claims.Scope)
?.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries));
if (!context.Principal.HasScope())
{
var scopes = context.Principal.GetClaims(Claims.Scope);
if (scopes.Length == 1)
{
scopes = scopes[0].Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray();
}
context.Logger.LogTrace("The self-contained JWT token '{Token}' was successfully validated and the following " +
"claims could be extracted: {Claims}.", context.Token, context.Principal.Claims);
context.Principal.SetScopes(scopes);
}
return default;
}
@ -263,7 +318,7 @@ namespace OpenIddict.Validation
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireReferenceAccessTokensEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000)
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.Build();
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)

9
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -8,7 +8,7 @@ using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Validation
{
@ -90,11 +90,12 @@ namespace OpenIddict.Validation
public TokenValidationParameters TokenValidationParameters { get; } = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero,
NameClaimType = OpenIddictConstants.Claims.Name,
RoleClaimType = OpenIddictConstants.Claims.Role,
NameClaimType = Claims.Name,
RoleClaimType = Claims.Role,
// Note: audience and lifetime are manually validated by OpenIddict itself.
ValidateAudience = false,
ValidateLifetime = false
ValidateLifetime = false,
ValidTypes = new[] { JsonWebTokenTypes.AccessToken }
};
}
}

28
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs

@ -1659,7 +1659,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Presenters, presenter.ToImmutableArray());
principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray());
// Act and assert
Assert.Equal(presenters, principal.GetPresenters());
@ -1689,7 +1689,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Resources, resource.ToImmutableArray());
principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray());
// Act and assert
Assert.Equal(resources, principal.GetResources());
@ -1719,7 +1719,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Scopes, scope.ToImmutableArray());
principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray());
// Act and assert
Assert.Equal(scopes, principal.GetScopes());
@ -2068,7 +2068,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Presenters, presenter.ToImmutableArray());
principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasPresenter());
@ -2090,7 +2090,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Presenters, presenter.ToImmutableArray());
principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasPresenter("fabrikam"));
@ -2132,7 +2132,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Resources, resource.ToImmutableArray());
principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasResource());
@ -2154,7 +2154,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Resources, resource.ToImmutableArray());
principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasResource("fabrikam"));
@ -2196,7 +2196,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Scopes, scope.ToImmutableArray());
principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasScope());
@ -2218,7 +2218,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = new ClaimsIdentity();
var principal = new ClaimsPrincipal(identity);
principal.SetClaims(Claims.Private.Scopes, scope.ToImmutableArray());
principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray());
// Act and assert
Assert.Equal(result, principal.HasScope(Scopes.OpenId));
@ -2523,7 +2523,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetPresenters(presenters);
// Assert
Assert.Equal(presenter, principal.GetClaims(Claims.Private.Presenters));
Assert.Equal(presenter, principal.GetClaims(Claims.Private.Presenter));
}
[Fact]
@ -2555,7 +2555,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetResources(resources);
// Assert
Assert.Equal(resource, principal.GetClaims(Claims.Private.Resources));
Assert.Equal(resource, principal.GetClaims(Claims.Private.Resource));
}
[Fact]
@ -2587,7 +2587,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetScopes(scopes);
// Assert
Assert.Equal(scope, principal.GetClaims(Claims.Private.Scopes));
Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope));
}
[Theory]
@ -2607,7 +2607,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetScopes((IEnumerable<string>)scopes);
// Assert
Assert.Equal(scope, principal.GetClaims(Claims.Private.Scopes));
Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope));
}
[Theory]
@ -2627,7 +2627,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
principal.SetScopes(ImmutableArray.Create(scopes));
// Assert
Assert.Equal(scope, principal.GetClaims(Claims.Private.Scopes));
Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope));
}
[Fact]

4
test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs

@ -340,7 +340,9 @@ namespace OpenIddict.Server.AspNetCore.FunctionalTests
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(
result.Principal.Claims.ToDictionary(claim => claim.Type, claim => claim.Value)));
new OpenIddictResponse(result.Principal.Claims.GroupBy(claim => claim.Type)
.Select(group => new KeyValuePair<string, string[]>(
group.Key, group.Select(claim => claim.Value).ToArray())))));
return;
}

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

@ -5,6 +5,7 @@
*/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Claims;
using System.Text;
@ -115,6 +116,250 @@ namespace OpenIddict.Server.FunctionalTests
.ToString(), exception.Message);
}
[Fact]
public async Task ProcessAuthentication_MissingAccessTokenReturnsNull()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetLogoutEndpointUris("/authenticate");
options.AddEventHandler<HandleLogoutRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = null
});
// Assert
Assert.Null((string) response[Claims.Subject]);
}
[Fact]
public async Task ProcessAuthentication_InvalidAccessTokenReturnsNull()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetLogoutEndpointUris("/authenticate");
options.AddEventHandler<HandleLogoutRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "38323A4B-6CB2-41B8-B457-1951987CB383"
});
// Assert
Assert.Null((string) response[Claims.Subject]);
}
[Fact]
public async Task ProcessAuthentication_ValidAccessTokenReturnsExpectedIdentity()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetClaim(Claims.Subject, "Bob le Magnifique");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
}
[Fact]
public async Task ProcessAuthentication_AuthorizedPartyIsMappedToPresenter()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique")
.SetClaim(Claims.AuthorizedParty, "Fabrikam");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal("Fabrikam", (string) response[Claims.AuthorizedParty]);
Assert.Equal("Fabrikam", (string) response[Claims.Private.Presenter]);
}
[Fact]
public async Task ProcessAuthentication_SinglePublicScopeIsMappedToPrivateClaims()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique")
.SetClaim(Claims.Scope, "openid profile");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal("openid profile", (string) response[Claims.Scope]);
Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]) response[Claims.Private.Scope]);
}
[Fact]
public async Task ProcessAuthentication_MultiplePublicScopesAreMappedToPrivateClaims()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/authenticate");
options.AddEventHandler<HandleUserinfoRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetClaim(Claims.Subject, "Bob le Magnifique")
.SetClaims(Claims.Scope, ImmutableArray.Create(Scopes.OpenId, Scopes.Profile));
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
// Act
var response = await client.GetAsync("/authenticate", new OpenIddictRequest
{
AccessToken = "access_token"
});
// Assert
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]) response[Claims.Scope]);
Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]) response[Claims.Private.Scope]);
}
[Fact]
public async Task ProcessAuthentication_MissingIdTokenHintReturnsNull()
{

4
test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

@ -351,7 +351,9 @@ namespace OpenIddict.Server.Owin.FunctionalTests
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(
result.Identity.Claims.ToDictionary(claim => claim.Type, claim => claim.Value)));
new OpenIddictResponse(result.Identity.Claims.GroupBy(claim => claim.Type)
.Select(group => new KeyValuePair<string, string[]>(
group.Key, group.Select(claim => claim.Value).ToArray())))));
return;
}

Loading…
Cancel
Save