diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 82c39261..bee1f43c 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -98,7 +98,12 @@ namespace OpenIddict.Server BeautifyUserCode.Descriptor, AttachAccessTokenProperties.Descriptor, - AttachDeviceCodeProperties.Descriptor) + AttachDeviceCodeProperties.Descriptor, + + /* + * Sign-out processing: + */ + ValidateSignOutDemand.Descriptor) .AddRange(Authentication.DefaultHandlers) .AddRange(Device.DefaultHandlers) @@ -4052,5 +4057,43 @@ namespace OpenIddict.Server return default; } } + + /// + /// Contains the logic responsible of ensuring that the sign-out demand + /// is compatible with the type of the endpoint that handled the request. + /// + public class ValidateSignOutDemand : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public ValueTask HandleAsync([NotNull] ProcessSignOutContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.EndpointType != OpenIddictServerEndpointType.Logout) + { + throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint."); + } + + return default; + } + } } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs index f8e4ec23..90e4a71f 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs @@ -1646,7 +1646,7 @@ namespace OpenIddict.Server.FunctionalTests [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] - public async Task ProcessSignIn_AllowsRejectingAuthorizationRequest(string error, string description, string uri) + public async Task ProcessSignIn_AllowsRejectingRequest(string error, string description, string uri) { // Arrange var client = CreateClient(options => @@ -1677,44 +1677,6 @@ namespace OpenIddict.Server.FunctionalTests Assert.Equal(uri, 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 ProcessSignIn_AllowsRejectingTokenRequest(string error, string description, string uri) - { - // Arrange - var client = CreateClient(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); - - return default; - })); - }); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); - - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } - [Fact] public async Task ProcessSignIn_AllowsHandlingResponse() { @@ -2834,6 +2796,95 @@ namespace OpenIddict.Server.FunctionalTests Mock.Get(manager).Verify(manager => manager.CreateAsync(It.IsAny(), It.IsAny()), Times.Never()); } + [Fact] + public async Task ProcessSignOut_InvalidEndpointCausesAnException() + { + // Arrange + var client = CreateClient(options => + { + options.EnableDegradedMode(); + options.SetConfigurationEndpointUris("/signout"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + }); + + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/signout"); + }); + + Assert.Equal("An OpenID Connect response cannot be returned from this endpoint.", exception.Message); + } + + [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 ProcessSignOut_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + var client = CreateClient(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); + + return default; + })); + }); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ProcessSignOut_AllowsHandlingResponse() + { + // Arrange + var client = CreateClient(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new + { + name = "Bob le Bricoleur" + }); + + context.HandleRequest(); + + return default; + })); + }); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Equal("Bob le Bricoleur", (string) response["name"]); + } + protected virtual void ConfigureServices(IServiceCollection services) { services.AddOpenIddict()