/* * 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.Net.Http; using System.Security.Claims; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using static OpenIddict.Server.OpenIddictServerHandlers.Revocation; namespace OpenIddict.Server.IntegrationTests; public abstract partial class OpenIddictServerIntegrationTests { [Theory] [InlineData(nameof(HttpMethod.Delete))] [InlineData(nameof(HttpMethod.Get))] [InlineData(nameof(HttpMethod.Head))] [InlineData(nameof(HttpMethod.Options))] [InlineData(nameof(HttpMethod.Put))] [InlineData(nameof(HttpMethod.Trace))] public async Task ExtractRevocationRequest_UnexpectedMethodReturnsAnError(string method) { // Arrange await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); await using var client = await server.CreateClientAsync(); // Act var response = await client.SendAsync(method, "/connect/revoke", new OpenIddictRequest()); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); } [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] public async Task ExtractRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Reject(error, description, uri); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); // Assert Assert.Equal(error ?? Errors.InvalidRequest, response.Error); Assert.Equal(description, response.ErrorDescription); Assert.Equal(uri, response.ErrorUri); } [Fact] public async Task ExtractRevocationRequest_AllowsHandlingResponse() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); // Assert Assert.Equal("Bob le Bricoleur", (string?) response["name"]); } [Fact] public async Task ExtractRevocationRequest_AllowsSkippingHandler() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.SkipRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); // Assert Assert.Equal("Bob le Magnifique", (string?) response["name"]); } [Fact] public async Task ValidateRevocationRequest_MissingTokenCausesAnError() { // Arrange await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = null }); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal(SR.FormatID2029(Parameters.Token), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientAssertionIsSpecifiedWithoutType() { // Arrange await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientAssertion = "2YotnFZFEjr1zCsicMWpAA", ClientAssertionType = null, ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal(SR.FormatID2037(Parameters.ClientAssertionType, Parameters.ClientAssertion), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2037), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientAssertionTypeIsSpecifiedWithoutAssertion() { // Arrange await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientAssertion = null, ClientAssertionType = ClientAssertionTypes.JwtBearer, ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal(SR.FormatID2037(Parameters.ClientAssertion, Parameters.ClientAssertionType), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2037), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenUnsupportedClientAssertionTypeIsSpecified() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.PrivateKeyJwt)); options.Configure(options => options.ClientAssertionTypes.Remove(ClientAssertionTypes.JwtBearer)); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientAssertion = "2YotnFZFEjr1zCsicMWpAA", ClientAssertionType = ClientAssertionTypes.JwtBearer, ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.FormatID2032(Parameters.ClientAssertionType), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenMultipleCredentialsAreSpecified() { // Arrange await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientAssertion = "2YotnFZFEjr1zCsicMWpAA", ClientAssertionType = ClientAssertionTypes.JwtBearer, ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2087), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() { // Arrange await using var server = await CreateServerAsync(options => { options.Configure(options => options.AcceptAnonymousClients = false); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCannotBeFound() { // Arrange var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(value: null); }); await using var server = await CreateServerAsync(options => { options.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) .ReturnsAsync(true); mock.Setup(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Revocation, It.IsAny())) .ReturnsAsync(false); }); await using var server = await CreateServerAsync(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(manager); options.Configure(options => options.IgnoreEndpointPermissions = false); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.UnauthorizedClient, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2078), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2078), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) .ReturnsAsync(true); }); await using var server = await CreateServerAsync(options => { options.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateRevocationRequest_ClientSecretIsRequiredForNonPublicClients() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) .ReturnsAsync(false); }); await using var server = await CreateServerAsync(options => { options.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = null, Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) .ReturnsAsync(false); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(false); }); await using var server = await CreateServerAsync(builder => { builder.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); } [Theory] [InlineData(TokenTypeHints.AuthorizationCode)] [InlineData(TokenTypeHints.DeviceCode)] [InlineData(TokenTypeHints.IdToken)] [InlineData(TokenTypeHints.UserCode)] public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type) { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("5HtRgAtc02", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(type); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", Token = "5HtRgAtc02" }); // Assert Assert.Equal(Errors.UnsupportedTokenType, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2079), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2079), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken) .SetAudiences("AdventureWorks") .SetPresenters("Contoso"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AccessToken }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("8xLOxBtZp8", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Contoso"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { ClientId = "Fabrikam", Token = "8xLOxBtZp8", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri); } [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] public async Task ValidateRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Reject(error, description, uri); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(error ?? Errors.InvalidRequest, response.Error); Assert.Equal(description, response.ErrorDescription); Assert.Equal(uri, response.ErrorUri); } [Fact] public async Task ValidateRevocationRequest_AllowsHandlingResponse() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Bricoleur", (string?) response["name"]); } [Fact] public async Task ValidateRevocationRequest_AllowsSkippingHandler() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.SkipRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Magnifique", (string?) response["name"]); } [Fact] public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsUnknown() { // Arrange var manager = CreateTokenManager(mock => { mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(value: null); }); await using var server = await CreateServerAsync(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); options.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsAlreadyRevoked() { // Arrange var token = new OpenIddictToken(); var manager = CreateTokenManager(mock => { mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(false); mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) .ReturnsAsync(TokenTypeHints.RefreshToken); }); await using var server = await CreateServerAsync(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); options.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task HandleRevocationRequest_TokenIsSuccessfullyRevoked() { // Arrange var token = new OpenIddictToken(); var manager = CreateTokenManager(mock => { mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(true); mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) .ReturnsAsync(TokenTypeHints.RefreshToken); mock.Setup(manager => manager.TryRevokeAsync(token, It.IsAny())) .ReturnsAsync(true); }); await using var server = await CreateServerAsync(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); options.Services.AddSingleton(manager); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "SlAV32hkKG" }); // Assert Assert.Empty(response.GetParameters()); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(token, It.IsAny()), Times.Once()); } [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] public async Task HandleRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Reject(error, description, uri); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(error ?? Errors.InvalidRequest, response.Error); Assert.Equal(description, response.ErrorDescription); Assert.Equal(uri, response.ErrorUri); } [Fact] public async Task HandleRevocationRequest_AllowsHandlingResponse() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Bricoleur", (string?) response["name"]); } [Fact] public async Task HandleRevocationRequest_AllowsSkippingHandler() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.SkipRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Magnifique", (string?) response["name"]); } [Fact] public async Task ApplyRevocationResponse_AllowsHandlingResponse() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Bricoleur", (string?) response["name"]); } [Fact] public async Task ApplyRevocationResponse_ResponseContainsCustomParameters() { // Arrange await using var server = await CreateServerAsync(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Response["custom_parameter"] = "custom_value"; context.Response["parameter_with_multiple_values"] = new[] { "custom_value_1", "custom_value_2" }; return default; })); }); await using var client = await server.CreateClientAsync(); // Act var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("custom_value", (string?) response["custom_parameter"]); Assert.Equal(["custom_value_1", "custom_value_2"], (string[]?) response["parameter_with_multiple_values"]); } }