diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs index 1590da3d..65dbff23 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs @@ -671,18 +671,15 @@ namespace OpenIddict.Server // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, // which requires that a special claim containing the token type be present in the security principal. - if (context.ValidTokenTypes.Count > 0) + var type = context.Principal.GetTokenType(); + if (string.IsNullOrEmpty(type)) { - var type = context.Principal.GetTokenType(); - if (string.IsNullOrEmpty(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); + } - if (!context.ValidTokenTypes.Contains(type)) - { - throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); - } + if (context.ValidTokenTypes.Count > 0 && !context.ValidTokenTypes.Contains(type)) + { + throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); } return default; diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs index e2c461ee..62170f26 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs @@ -615,18 +615,15 @@ namespace OpenIddict.Validation // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, // which requires that a special claim containing the token type be present in the security principal. - if (context.ValidTokenTypes.Count > 0) + var type = context.Principal.GetTokenType(); + if (string.IsNullOrEmpty(type)) { - var type = context.Principal.GetTokenType(); - if (string.IsNullOrEmpty(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); + } - if (!context.ValidTokenTypes.Contains(type)) - { - throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); - } + if (context.ValidTokenTypes.Count > 0 && !context.ValidTokenTypes.Contains(type)) + { + throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); } return default; diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs new file mode 100644 index 00000000..26c4108f --- /dev/null +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs @@ -0,0 +1,566 @@ + + +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Collections.Immutable; +using System.Security.Claims; +using System.Threading.Tasks; +using OpenIddict.Abstractions; +using Xunit; +using static OpenIddict.Abstractions.OpenIddictConstants; +using static OpenIddict.Server.OpenIddictServerEvents; +using static OpenIddict.Server.OpenIddictServerHandlers.Protection; +using SR = OpenIddict.Abstractions.OpenIddictResources; + +namespace OpenIddict.Server.IntegrationTests +{ + public abstract partial class OpenIddictServerIntegrationTests + { + [Fact] + public async Task ValidateToken_IssuedAtIsMappedToCreationDate() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(new Claim(Claims.IssuedAt, "1577836800", ClaimValueTypes.Integer64)); + + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "access_token" + }); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(1577836800, (long) response[Claims.IssuedAt]); + Assert.Equal("Wed, 01 Jan 2020 00:00:00 GMT", (string?) response[Claims.Private.CreationDate]); + } + + [Fact] + public async Task ValidateToken_ExpiresAtIsMappedToExpirationDate() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(new Claim(Claims.ExpiresAt, "2524608000", ClaimValueTypes.Integer64)); + + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "access_token" + }); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(2524608000, (long) response[Claims.ExpiresAt]); + Assert.Equal("Sat, 01 Jan 2050 00:00:00 GMT", (string?) response[Claims.Private.ExpirationDate]); + } + + [Fact] + public async Task ValidateToken_AuthorizedPartyIsMappedToPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + 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); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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 ValidateToken_ClientIdIsMappedToPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.ClientId, "Fabrikam"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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.ClientId]); + Assert.Equal("Fabrikam", (string?) response[Claims.Private.Presenter]); + } + + [Fact] + public async Task ValidateToken_SinglePublicAudienceIsMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.Audience, "Fabrikam"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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.Audience]); + Assert.Equal("Fabrikam", (string?) response[Claims.Private.Audience]); + } + + [Fact] + public async Task ValidateToken_MultiplePublicAudiencesAreMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaims(Claims.Audience, ImmutableArray.Create("Fabrikam", "Contoso")); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Audience]); + Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Private.Audience]); + } + + [Fact] + public async Task ValidateToken_MultiplePublicScopesAreNormalizedToSingleClaim() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + 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); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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]); + } + + [Fact] + public async Task ValidateToken_SinglePublicScopeIsMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + 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); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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.Private.Scope]); + } + + [Fact] + public async Task ValidateToken_MultiplePublicScopesAreMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + 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); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // 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.Private.Scope]); + } + + [Fact] + public async Task ValidateToken_MissingTokenTypeThrowsAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(Array.Empty(), context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(null) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/connect/introspect", new OpenIddictRequest + { + Token = "access_token" + }); + }); + + // Assert + Assert.Equal(SR.GetResourceString(SR.ID0004), exception.Message); + } + + [Fact] + public async Task ValidateToken_InvalidTokenTypeThrowsAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "access_token" + }); + }); + + // Assert + Assert.Equal(SR.FormatID0005(TokenTypeHints.AuthorizationCode, TokenTypeHints.AccessToken), exception.Message); + } + } +} diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs index b07ddac1..91e6f35f 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs @@ -7,7 +7,6 @@ */ using System; -using System.Collections.Immutable; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -221,557 +220,6 @@ namespace OpenIddict.Server.IntegrationTests Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); } - [Fact] - public async Task ProcessAuthentication_IssuedAtIsMappedToCreationDate() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(new Claim(Claims.IssuedAt, "1577836800", ClaimValueTypes.Integer64)); - - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" - }); - - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(1577836800, (long) response[Claims.IssuedAt]); - Assert.Equal("Wed, 01 Jan 2020 00:00:00 GMT", (string?) response[Claims.Private.CreationDate]); - } - - [Fact] - public async Task ProcessAuthentication_ExpiresAtIsMappedToExpirationDate() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(new Claim(Claims.ExpiresAt, "2524608000", ClaimValueTypes.Integer64)); - - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" - }); - - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(2524608000, (long) response[Claims.ExpiresAt]); - Assert.Equal("Sat, 01 Jan 2050 00:00:00 GMT", (string?) response[Claims.Private.ExpirationDate]); - } - - [Fact] - public async Task ProcessAuthentication_AuthorizedPartyIsMappedToPresenter() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - 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); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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_ClientIdIsMappedToPresenter() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.ClientId, "Fabrikam"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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.ClientId]); - Assert.Equal("Fabrikam", (string?) response[Claims.Private.Presenter]); - } - - [Fact] - public async Task ProcessAuthentication_SinglePublicAudienceIsMappedToPrivateClaims() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.Audience, "Fabrikam"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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.Audience]); - Assert.Equal("Fabrikam", (string?) response[Claims.Private.Audience]); - } - - [Fact] - public async Task ProcessAuthentication_MultiplePublicAudiencesAreMappedToPrivateClaims() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaims(Claims.Audience, ImmutableArray.Create("Fabrikam", "Contoso")); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Audience]); - Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Private.Audience]); - } - - [Fact] - public async Task ProcessAuthentication_MultiplePublicScopesAreNormalizedToSingleClaim() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - 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); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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]); - } - - [Fact] - public async Task ProcessAuthentication_SinglePublicScopeIsMappedToPrivateClaims() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - 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); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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.Private.Scope]); - } - - [Fact] - public async Task ProcessAuthentication_MultiplePublicScopesAreMappedToPrivateClaims() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - 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); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // 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.Private.Scope]); - } - - [Fact] - public async Task ProcessAuthentication_MissingTokenTypeThrowsAnException() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(null) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" - }); - }); - - // Assert - Assert.Equal(SR.GetResourceString(SR.ID0004), exception.Message); - } - - [Fact] - public async Task ProcessAuthentication_InvalidTokenTypeThrowsAnException() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" - }); - }); - - // Assert - Assert.Equal(SR.FormatID0005(TokenTypeHints.AuthorizationCode, TokenTypeHints.AccessToken), exception.Message); - } - [Fact] public async Task ProcessAuthentication_MissingIdTokenHintReturnsNull() {