diff --git a/build/version.props b/build/version.props index b0387258..bf0d2289 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ 1.0.0 - rc1 + rc2 $(VersionSuffix)-$(BuildNumber) diff --git a/src/OpenIddict/OpenIddictProvider.Helpers.cs b/src/OpenIddict/OpenIddictProvider.Helpers.cs index b2fb74de..6b0ee4f2 100644 --- a/src/OpenIddict/OpenIddictProvider.Helpers.cs +++ b/src/OpenIddict/OpenIddictProvider.Helpers.cs @@ -5,7 +5,6 @@ */ using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -37,11 +36,6 @@ namespace OpenIddict var authorizations = context.RequestServices.GetRequiredService>(); var logger = context.RequestServices.GetRequiredService>>(); - if (options.DisableTokenRevocation) - { - return; - } - var descriptor = new OpenIddictAuthorizationDescriptor { Status = OpenIddictConstants.Statuses.Valid, @@ -228,74 +222,119 @@ namespace OpenIddict } private async Task ReceiveTokenAsync( - [NotNull] string value, [NotNull] OpenIddictOptions options, - [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, + [NotNull] string type, [NotNull] string value, + [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, + [NotNull] OpenIdConnectRequest request, [NotNull] ISecureDataFormat format) { var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); - if (!options.UseReferenceTokens) - { - return null; - } + Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken || + type == OpenIdConnectConstants.TokenUsages.AuthorizationCode || + type == OpenIdConnectConstants.TokenUsages.RefreshToken, + "Only authorization codes, access and refresh tokens should be validated using this method."); - string hash; - try + string identifier; + AuthenticationTicket ticket; + TToken token; + + if (options.UseReferenceTokens) { - // Compute the digest of the received token and use it - // to retrieve the reference token from the database. - using (var algorithm = SHA256.Create()) + string hash; + try { - hash = Convert.ToBase64String(algorithm.ComputeHash(Base64UrlEncoder.DecodeBytes(value))); + // Compute the digest of the received token and use it + // to retrieve the reference token from the database. + using (var algorithm = SHA256.Create()) + { + hash = Convert.ToBase64String(algorithm.ComputeHash(Base64UrlEncoder.DecodeBytes(value))); + } } - } - // Swallow format-related exceptions to ensure badly formed - // or tampered tokens don't cause an exception at this stage. - catch - { - return null; - } + // Swallow format-related exceptions to ensure badly formed + // or tampered tokens don't cause an exception at this stage. + catch + { + return null; + } - // Retrieve the token entry from the database. If it - // cannot be found, assume the token is not valid. - var token = await tokens.FindByHashAsync(hash, context.RequestAborted); - if (token == null) - { - logger.LogInformation("The reference token corresponding to the '{Hash}' hashed " + - "identifier cannot be found in the database.", hash); + // Retrieve the token entry from the database. If it + // cannot be found, assume the token is not valid. + token = await tokens.FindByHashAsync(hash, context.RequestAborted); + if (token == null) + { + logger.LogInformation("The reference token corresponding to the '{Hash}' hashed " + + "identifier cannot be found in the database.", hash); - return null; - } + return null; + } - var identifier = await tokens.GetIdAsync(token, context.RequestAborted); - if (string.IsNullOrEmpty(identifier)) - { - logger.LogWarning("The identifier associated with the received token cannot be retrieved. " + - "This may indicate that the token entry is corrupted."); - return null; + identifier = await tokens.GetIdAsync(token, context.RequestAborted); + if (string.IsNullOrEmpty(identifier)) + { + logger.LogWarning("The identifier associated with the received token cannot be retrieved. " + + "This may indicate that the token entry is corrupted."); + + return null; + } + + // Extract the encrypted payload from the token. If it's null or empty, + // assume the token is not a reference token and consider it as invalid. + var ciphertext = await tokens.GetCiphertextAsync(token, context.RequestAborted); + if (string.IsNullOrEmpty(ciphertext)) + { + logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be retrieved. " + + "This may indicate that the token is not a reference token.", identifier); + + return null; + } + + ticket = format.Unprotect(ciphertext); + if (ticket == null) + { + logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be decrypted. " + + "This may indicate that the token entry is corrupted or tampered.", + await tokens.GetIdAsync(token, context.RequestAborted)); + + return null; + } } - // Extract the encrypted payload from the token. If it's null or empty, - // assume the token is not a reference token and consider it as invalid. - var ciphertext = await tokens.GetCiphertextAsync(token, context.RequestAborted); - if (string.IsNullOrEmpty(ciphertext)) + else if (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode || + type == OpenIdConnectConstants.TokenUsages.RefreshToken) { - logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be retrieved. " + - "This may indicate that the token is not a reference token.", identifier); + ticket = format.Unprotect(value); + if (ticket == null) + { + logger.LogTrace("The received token was invalid or malformed: {Token}.", value); - return null; + return null; + } + + // Retrieve the authorization code/refresh token entry from the database. + // If it cannot be found, assume the authorization code/refresh token is not valid. + token = await tokens.FindByIdAsync(ticket.GetTokenId(), context.RequestAborted); + if (token == null) + { + logger.LogInformation("The token '{Identifier}' cannot be found in the database.", ticket.GetTokenId()); + + return null; + } + + identifier = await tokens.GetIdAsync(token, context.RequestAborted); + if (string.IsNullOrEmpty(identifier)) + { + logger.LogWarning("The identifier associated with the received token cannot be retrieved. " + + "This may indicate that the token entry is corrupted."); + + return null; + } } - var ticket = format.Unprotect(ciphertext); - if (ticket == null) + else { - logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be decrypted. " + - "This may indicate that the token entry is corrupted or tampered.", - await tokens.GetIdAsync(token, context.RequestAborted)); - return null; } @@ -311,9 +350,9 @@ namespace OpenIddict ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, await tokens.GetAuthorizationIdAsync(token, context.RequestAborted)); - logger.LogTrace("The reference token '{Identifier}' was successfully retrieved " + - "from the database and decrypted: {Claims} ; {Properties}.", - identifier, ticket.Principal.Claims, ticket.Properties.Items); + logger.LogTrace("The token '{Identifier}' was successfully decrypted and " + + "retrieved from the database: {Claims} ; {Properties}.", + ticket.GetTokenId(), ticket.Principal.Claims, ticket.Properties.Items); return ticket; } @@ -370,12 +409,6 @@ namespace OpenIddict foreach (var token in await tokens.FindByAuthorizationIdAsync(identifier, context.RequestAborted)) { - // Don't overwrite the status of the token used in the token request. - if (string.Equals(ticket.GetTokenId(), await tokens.GetIdAsync(token, context.RequestAborted))) - { - continue; - } - try { await tokens.RevokeAsync(token, context.RequestAborted); diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs index 0d7afa50..a45f3119 100644 --- a/src/OpenIddict/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict/OpenIddictProvider.Serialization.cs @@ -18,84 +18,70 @@ namespace OpenIddict public override async Task DeserializeAccessToken([NotNull] DeserializeAccessTokenContext context) { var options = (OpenIddictOptions) context.Options; - if (!options.UseReferenceTokens) + if (options.DisableTokenRevocation) { return; } context.Ticket = await ReceiveTokenAsync( + OpenIdConnectConstants.TokenUsages.AccessToken, context.AccessToken, options, context.HttpContext, context.Request, context.DataFormat); // Prevent the OpenID Connect server middleware from using - // its default logic to deserialize the reference token. - if (context.Ticket != null) + // its default logic to deserialize reference access tokens. + if (options.UseReferenceTokens) { context.HandleResponse(); } - - else - { - context.SkipToNextMiddleware(); - } } public override async Task DeserializeAuthorizationCode([NotNull] DeserializeAuthorizationCodeContext context) { var options = (OpenIddictOptions) context.Options; - if (!options.UseReferenceTokens) + if (options.DisableTokenRevocation) { return; } context.Ticket = await ReceiveTokenAsync( + OpenIdConnectConstants.TokenUsages.AuthorizationCode, context.AuthorizationCode, options, context.HttpContext, context.Request, context.DataFormat); - // Prevent the OpenID Connect server middleware from using - // its default logic to deserialize the reference token. - if (context.Ticket != null) - { - context.HandleResponse(); - } - - else - { - context.SkipToNextMiddleware(); - } + // Prevent the OpenID Connect server middleware from using its default logic. + context.HandleResponse(); } public override async Task DeserializeRefreshToken([NotNull] DeserializeRefreshTokenContext context) { var options = (OpenIddictOptions) context.Options; - if (!options.UseReferenceTokens) + if (options.DisableTokenRevocation) { return; } context.Ticket = await ReceiveTokenAsync( + OpenIdConnectConstants.TokenUsages.RefreshToken, context.RefreshToken, options, context.HttpContext, context.Request, context.DataFormat); - // Prevent the OpenID Connect server middleware from using - // its default logic to deserialize the reference token. - if (context.Ticket != null) - { - context.HandleResponse(); - } - - else - { - context.SkipToNextMiddleware(); - } + // Prevent the OpenID Connect server middleware from using its default logic. + context.HandleResponse(); } public override async Task SerializeAccessToken([NotNull] SerializeAccessTokenContext context) { + var options = (OpenIddictOptions) context.Options; + if (options.DisableTokenRevocation) + { + return; + } + var token = await CreateTokenAsync( OpenIdConnectConstants.TokenUsages.AccessToken, - context.Ticket, (OpenIddictOptions) context.Options, - context.HttpContext, context.Request, context.DataFormat); + context.Ticket, options, context.HttpContext, + context.Request, context.DataFormat); // If a reference token was returned by CreateTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -111,12 +97,18 @@ namespace OpenIddict public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) { + var options = (OpenIddictOptions) context.Options; + if (options.DisableTokenRevocation) + { + return; + } + Debug.Assert(context.Request.IsAuthorizationRequest(), "The request should be an authorization request."); var token = await CreateTokenAsync( OpenIdConnectConstants.TokenUsages.AuthorizationCode, - context.Ticket, (OpenIddictOptions) context.Options, - context.HttpContext, context.Request, context.DataFormat); + context.Ticket, options, context.HttpContext, + context.Request, context.DataFormat); // If a reference token was returned by CreateTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -132,12 +124,18 @@ namespace OpenIddict public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { + var options = (OpenIddictOptions) context.Options; + if (options.DisableTokenRevocation) + { + return; + } + Debug.Assert(context.Request.IsTokenRequest(), "The request should be a token request."); var token = await CreateTokenAsync( OpenIdConnectConstants.TokenUsages.RefreshToken, - context.Ticket, (OpenIddictOptions) context.Options, - context.HttpContext, context.Request, context.DataFormat); + context.Ticket, options, context.HttpContext, + context.Request, context.DataFormat); // If a reference token was returned by CreateTokenAsync(), // force the OpenID Connect server middleware to use it. diff --git a/src/OpenIddict/OpenIddictProvider.cs b/src/OpenIddict/OpenIddictProvider.cs index 0af6a545..37c72398 100644 --- a/src/OpenIddict/OpenIddictProvider.cs +++ b/src/OpenIddict/OpenIddictProvider.cs @@ -110,7 +110,9 @@ namespace OpenIddict { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, - description: "The specified authorization code is no longer valid."); + description: context.Request.IsAuthorizationCodeGrantType() ? + "The specified authorization code is no longer valid." : + "The specified refresh token is no longer valid."); return; } diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs index 9c483d25..1d4d0513 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs @@ -562,7 +562,7 @@ namespace OpenIddict.Tests // Assert Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); - Assert.Equal("The specified authorization code is no longer valid.", response.ErrorDescription); + Assert.Equal("The specified authorization code is invalid.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); } @@ -619,7 +619,7 @@ namespace OpenIddict.Tests // Assert Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); - Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription); + Assert.Equal("The specified refresh token is invalid.", response.ErrorDescription); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once()); } @@ -649,6 +649,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(true); }); @@ -686,7 +689,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny()), Times.Once()); } @@ -714,6 +717,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(true); }); @@ -749,7 +755,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified refresh token has already been redeemed.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny()), Times.Once()); } @@ -800,6 +806,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(true); @@ -877,6 +889,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(true); @@ -935,6 +953,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(tokens[0]); + instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + instance.Setup(mock => mock.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny())) .ReturnsAsync(true); @@ -983,7 +1007,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny()), Times.Once()); @@ -1018,6 +1042,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(tokens[0]); + instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + instance.Setup(mock => mock.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny())) .ReturnsAsync(true); @@ -1064,7 +1094,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified refresh token has already been redeemed.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny()), Times.Once()); @@ -1096,6 +1126,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -1136,7 +1169,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified authorization code is no longer valid.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny()), Times.Once()); } @@ -1165,6 +1198,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -1203,7 +1239,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny()), Times.Once()); } @@ -1250,6 +1286,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(true); }); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs index 8392419f..14aa5a64 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs @@ -573,6 +573,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(false); }); @@ -612,7 +615,7 @@ namespace OpenIddict.Tests Assert.Single(response.GetParameters()); Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny()), Times.Once()); } @@ -710,6 +713,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(false); }); @@ -749,7 +755,7 @@ namespace OpenIddict.Tests Assert.Single(response.GetParameters()); Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny()), Times.Once()); } } diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs index 389d2159..8965cf5a 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs @@ -457,6 +457,9 @@ namespace OpenIddict.Tests { instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); }); var server = CreateAuthorizationServer(builder => @@ -477,7 +480,7 @@ namespace OpenIddict.Tests // Assert Assert.Empty(response.GetParameters()); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(token, It.IsAny()), Times.Once()); } } diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs index c4c42043..7f78f116 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs @@ -88,7 +88,7 @@ namespace OpenIddict.Tests OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); + ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken); var format = new Mock>(); @@ -124,7 +124,7 @@ namespace OpenIddict.Tests builder.Services.AddSingleton(manager); - builder.Configure(options => options.RefreshTokenFormat = format.Object); + builder.Configure(options => options.AccessTokenFormat = format.Object); }); var client = new OpenIdConnectClient(server.CreateClient()); @@ -146,7 +146,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAccessToken_ReturnsNullForMissingTokenIdentifier() + public async Task DeserializeAccessToken_ReturnsNullForMissingReferenceTokenIdentifier() { // Arrange var token = new OpenIddictToken(); @@ -201,7 +201,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAccessToken_ReturnsNullForMissingTokenCiphertext() + public async Task DeserializeAccessToken_ReturnsNullForMissingReferenceTokenCiphertext() { // Arrange var token = new OpenIddictToken(); @@ -259,7 +259,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAccessToken_ReturnsNullForInvalidTokenCiphertext() + public async Task DeserializeAccessToken_ReturnsNullForInvalidReferenceTokenCiphertext() { // Arrange var format = new Mock>(); @@ -325,7 +325,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAccessToken_ReturnsExpectedToken() + public async Task DeserializeAccessToken_ReturnsExpectedReferenceToken() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); @@ -337,7 +337,6 @@ namespace OpenIddict.Tests OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); var format = new Mock>(); @@ -471,7 +470,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAuthorizationCode_AuthorizationCodeIsNotRetrievedFromDatabaseWhenReferenceTokensAreDisabled() + public async Task DeserializeAuthorizationCode_AuthorizationCodeIsNotRetrievedUsingHashWhenReferenceTokensAreDisabled() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); @@ -483,7 +482,6 @@ namespace OpenIddict.Tests OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); var format = new Mock>(); @@ -497,6 +495,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(true); }); @@ -541,7 +542,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAuthorizationCode_ReturnsNullForMissingTokenIdentifier() + public async Task DeserializeAuthorizationCode_ReturnsNullForMissingReferenceTokenIdentifier() { // Arrange var token = new OpenIddictToken(); @@ -596,7 +597,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAuthorizationCode_ReturnsNullForMissingTokenCiphertext() + public async Task DeserializeAuthorizationCode_ReturnsNullForMissingReferenceTokenCiphertext() { // Arrange var token = new OpenIddictToken(); @@ -654,7 +655,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAuthorizationCode_ReturnsNullForInvalidTokenCiphertext() + public async Task DeserializeAuthorizationCode_ReturnsNullForInvalidReferenceTokenCiphertext() { // Arrange var format = new Mock>(); @@ -720,7 +721,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeAuthorizationCode_ReturnsExpectedToken() + public async Task DeserializeAuthorizationCode_ReturnsExpectedReferenceToken() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); @@ -731,9 +732,6 @@ namespace OpenIddict.Tests new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); - ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); - var format = new Mock>(); format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) @@ -816,6 +814,192 @@ namespace OpenIddict.Tests format.Verify(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"), Times.Once()); } + [Fact] + public async Task DeserializeAuthorizationCode_ReturnsNullForMissingTokenIdentifier() + { + // Arrange + var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); + identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur"); + + var ticket = new AuthenticationTicket( + new ClaimsPrincipal(identity), + new AuthenticationProperties(), + OpenIdConnectServerDefaults.AuthenticationScheme); + + var format = new Mock>(); + + format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) + .Returns(ticket); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Configure(options => options.AuthorizationCodeFormat = format.Object); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.AuthorizationCode + }); + + // Assert + Assert.Single(response.GetParameters()); + Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]); + } + + [Fact] + public async Task DeserializeAuthorizationCode_ReturnsNullForInvalidTokenCiphertext() + { + // Arrange + var format = new Mock>(); + + format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) + .Returns(value: null); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Configure(options => options.AuthorizationCodeFormat = format.Object); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.AuthorizationCode + }); + + // Assert + Assert.Single(response.GetParameters()); + Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]); + + format.Verify(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"), Times.Once()); + } + + [Fact] + public async Task DeserializeAuthorizationCode_ReturnsExpectedToken() + { + // Arrange + var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); + identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur"); + + var ticket = new AuthenticationTicket( + new ClaimsPrincipal(identity), + new AuthenticationProperties(), + OpenIdConnectServerDefaults.AuthenticationScheme); + + ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); + + var format = new Mock>(); + + format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) + .Returns(ticket); + + var token = new OpenIddictToken(); + + var manager = CreateTokenManager(instance => + { + instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); + + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.GetCreationDateAsync(token, It.IsAny())) + .ReturnsAsync(new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero)); + + instance.Setup(mock => mock.GetExpirationDateAsync(token, It.IsAny())) + .ReturnsAsync(new DateTimeOffset(2017, 01, 10, 00, 00, 00, TimeSpan.Zero)); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Services.AddSingleton(manager); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == + new DateTimeOffset(2017, 01, 05, 00, 00, 00, TimeSpan.Zero)); + + options.AuthorizationCodeFormat = format.Object; + }); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.AuthorizationCode + }); + + // Assert + Assert.True((bool) response[OpenIdConnectConstants.Claims.Active]); + Assert.Equal("3E228451-1555-46F7-A471-951EFBA23A56", response[OpenIdConnectConstants.Claims.JwtId]); + Assert.Equal(1483228800, (long) response[OpenIdConnectConstants.Claims.IssuedAt]); + Assert.Equal(1484006400, (long) response[OpenIdConnectConstants.Claims.ExpiresAt]); + + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.AtLeastOnce()); + format.Verify(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"), Times.Once()); + } + [Fact] public async Task DeserializeRefreshToken_ReturnsNullForMalformedReferenceToken() { @@ -866,7 +1050,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeRefreshToken_RefreshTokenIsNotRetrievedFromDatabaseWhenReferenceTokensAreDisabled() + public async Task DeserializeRefreshToken_RefreshTokenIsNotRetrievedUsingHashWhenReferenceTokensAreDisabled() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); @@ -878,7 +1062,6 @@ namespace OpenIddict.Tests OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); var format = new Mock>(); @@ -892,6 +1075,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(true); }); @@ -936,7 +1122,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeRefreshToken_ReturnsNullForMissingTokenIdentifier() + public async Task DeserializeRefreshToken_ReturnsNullForMissingReferenceTokenIdentifier() { // Arrange var token = new OpenIddictToken(); @@ -991,7 +1177,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeRefreshToken_ReturnsNullForMissingTokenCiphertext() + public async Task DeserializeRefreshToken_ReturnsNullForMissingReferenceTokenCiphertext() { // Arrange var token = new OpenIddictToken(); @@ -1049,7 +1235,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeRefreshToken_ReturnsNullForInvalidTokenCiphertext() + public async Task DeserializeRefreshToken_ReturnsNullForInvalidReferenceTokenCiphertext() { // Arrange var format = new Mock>(); @@ -1115,7 +1301,7 @@ namespace OpenIddict.Tests } [Fact] - public async Task DeserializeRefreshToken_ReturnsExpectedToken() + public async Task DeserializeRefreshToken_ReturnsExpectedReferenceToken() { // Arrange var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); @@ -1126,9 +1312,6 @@ namespace OpenIddict.Tests new AuthenticationProperties(), OpenIdConnectServerDefaults.AuthenticationScheme); - ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); - var format = new Mock>(); format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) @@ -1211,6 +1394,192 @@ namespace OpenIddict.Tests format.Verify(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"), Times.Once()); } + [Fact] + public async Task DeserializeRefreshToken_ReturnsNullForMissingTokenIdentifier() + { + // Arrange + var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); + identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur"); + + var ticket = new AuthenticationTicket( + new ClaimsPrincipal(identity), + new AuthenticationProperties(), + OpenIdConnectServerDefaults.AuthenticationScheme); + + var format = new Mock>(); + + format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) + .Returns(ticket); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Configure(options => options.RefreshTokenFormat = format.Object); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Single(response.GetParameters()); + Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]); + } + + [Fact] + public async Task DeserializeRefreshToken_ReturnsNullForInvalidTokenCiphertext() + { + // Arrange + var format = new Mock>(); + + format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) + .Returns(value: null); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Configure(options => options.RefreshTokenFormat = format.Object); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Single(response.GetParameters()); + Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]); + + format.Verify(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"), Times.Once()); + } + + [Fact] + public async Task DeserializeRefreshToken_ReturnsExpectedToken() + { + // Arrange + var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); + identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur"); + + var ticket = new AuthenticationTicket( + new ClaimsPrincipal(identity), + new AuthenticationProperties(), + OpenIdConnectServerDefaults.AuthenticationScheme); + + ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); + + var format = new Mock>(); + + format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA")) + .Returns(ticket); + + var token = new OpenIddictToken(); + + var manager = CreateTokenManager(instance => + { + instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); + + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.GetCreationDateAsync(token, It.IsAny())) + .ReturnsAsync(new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero)); + + instance.Setup(mock => mock.GetExpirationDateAsync(token, It.IsAny())) + .ReturnsAsync(new DateTimeOffset(2017, 01, 10, 00, 00, 00, TimeSpan.Zero)); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Services.AddSingleton(manager); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == + new DateTimeOffset(2017, 01, 05, 00, 00, 00, TimeSpan.Zero)); + + options.RefreshTokenFormat = format.Object; + }); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken + }); + + // Assert + Assert.True((bool) response[OpenIdConnectConstants.Claims.Active]); + Assert.Equal("3E228451-1555-46F7-A471-951EFBA23A56", response[OpenIdConnectConstants.Claims.JwtId]); + Assert.Equal(1483228800, (long) response[OpenIdConnectConstants.Claims.IssuedAt]); + Assert.Equal(1484006400, (long) response[OpenIdConnectConstants.Claims.ExpiresAt]); + + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.AtLeastOnce()); + format.Verify(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"), Times.Once()); + } + [Fact] public async Task SerializeAccessToken_AccessTokenIsNotPersistedWhenReferenceTokensAreDisabled() { @@ -1736,7 +2105,6 @@ namespace OpenIddict.Tests OpenIdConnectServerDefaults.AuthenticationScheme); ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103"); - ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken); ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess); var format = new Mock>(); @@ -1754,6 +2122,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.cs index 053c54aa..3be3d1d2 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.cs @@ -148,6 +148,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -213,6 +216,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -286,6 +292,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -401,6 +410,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(true); }); @@ -435,7 +447,7 @@ namespace OpenIddict.Tests }); // Assert - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny()), Times.Once()); } @@ -467,6 +479,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.IsValidAsync(token, It.IsAny())) .ReturnsAsync(true); @@ -507,7 +522,7 @@ namespace OpenIddict.Tests Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); Assert.Equal("The specified authorization code is no longer valid.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny()), Times.Once()); } @@ -542,6 +557,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -570,7 +588,7 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.RefreshToken); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny()), Times.Once()); } @@ -605,6 +623,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -635,9 +656,9 @@ namespace OpenIddict.Tests // Assert Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error); - Assert.Equal("The specified authorization code is no longer valid.", response.ErrorDescription); + Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny()), Times.Once()); } @@ -695,7 +716,7 @@ namespace OpenIddict.Tests // Assert Assert.Null(response.RefreshToken); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny()), Times.Never()); } @@ -734,6 +755,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(tokens[0]); + instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + + instance.Setup(mock => mock.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny())) .ReturnsAsync(false); @@ -765,7 +792,7 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.RefreshToken); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny()), Times.Once()); } @@ -802,6 +829,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(tokens[0]); + instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny())) .ReturnsAsync(false); @@ -831,7 +861,7 @@ namespace OpenIddict.Tests // Assert Assert.Null(response.RefreshToken); - Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Exactly(2)); + Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny()), Times.Never()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny()), Times.Never()); } @@ -864,6 +894,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false); @@ -996,6 +1029,9 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); + instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny())) .ReturnsAsync(false);