Browse Source

Fix the ProcessHostRedirectionResponse handler to be invoked after ProcessRedirectionResponse

pull/982/head
Kévin Chalet 6 years ago
parent
commit
45d90453c8
  1. 5
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs
  2. 24
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs
  3. 4
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs
  4. 2
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs
  5. 4
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs
  6. 12
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs
  7. 104
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  8. 5
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreOptions.cs
  9. 2
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs
  10. 10
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs
  11. 104
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  12. 55
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs

5
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs

@ -72,8 +72,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// Enables error pass-through support, so that the rest of the request processing pipeline is
/// automatically invoked when returning an error from the interactive authorization and logout endpoints.
/// When this option is enabled, special logic must be added to these actions to handle errors, that can be
/// retrieved using <see cref="OpenIddictServerAspNetCoreHelpers.GetOpenIddictServerResponse(HttpContext)"/>
/// retrieved using <see cref="OpenIddictServerAspNetCoreHelpers.GetOpenIddictServerResponse(HttpContext)"/>.
/// </summary>
/// <remarks>
/// Important: the error pass-through mode cannot be used when the status code pages integration is enabled.
/// </remarks>
/// <returns>The <see cref="OpenIddictServerAspNetCoreBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictServerAspNetCoreBuilder EnableErrorPassthrough()

24
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs

@ -18,7 +18,8 @@ namespace OpenIddict.Server.AspNetCore
/// </summary>
public class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions<AuthenticationOptions>,
IConfigureOptions<OpenIddictServerOptions>,
IPostConfigureOptions<AuthenticationOptions>
IPostConfigureOptions<AuthenticationOptions>,
IPostConfigureOptions<OpenIddictServerAspNetCoreOptions>
{
/// <summary>
/// Registers the OpenIddict server handler in the global authentication options.
@ -97,5 +98,26 @@ namespace OpenIddict.Server.AspNetCore
.ToString());
}
}
/// <summary>
/// Populates the default OpenIddict server ASP.NET Core options and
/// ensures that the configuration is in a consistent and valid state.
/// </summary>
/// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictServerAspNetCoreOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.EnableErrorPassthrough && options.EnableStatusCodePagesIntegration)
{
throw new InvalidOperationException(new StringBuilder()
.Append("The error pass-through mode cannot be used when the status code pages integration is enabled.")
.ToString());
}
}
}
}

4
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs

@ -63,7 +63,9 @@ namespace Microsoft.Extensions.DependencyInjection
ServiceDescriptor.Singleton<IConfigureOptions<AuthenticationOptions>, OpenIddictServerAspNetCoreConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<AuthenticationOptions>, OpenIddictServerAspNetCoreConfiguration>(),
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictServerOptions>, OpenIddictServerAspNetCoreConfiguration>()
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictServerOptions>, OpenIddictServerAspNetCoreConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictServerAspNetCoreOptions>, OpenIddictServerAspNetCoreConfiguration>()
});
return new OpenIddictServerAspNetCoreBuilder(builder.Services);

2
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs

@ -57,8 +57,8 @@ namespace OpenIddict.Server.AspNetCore
ProcessFormPostResponse.Descriptor,
ProcessQueryResponse.Descriptor,
ProcessFragmentResponse.Descriptor,
ProcessStatusCodePagesErrorResponse<ApplyAuthorizationResponseContext>.Descriptor,
ProcessPassthroughErrorResponse<ApplyAuthorizationResponseContext, RequireAuthorizationEndpointPassthroughEnabled>.Descriptor,
ProcessStatusCodePagesErrorResponse<ApplyAuthorizationResponseContext>.Descriptor,
ProcessLocalErrorResponse<ApplyAuthorizationResponseContext>.Descriptor);
/// <summary>

4
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs

@ -43,10 +43,10 @@ namespace OpenIddict.Server.AspNetCore
*/
AttachHttpResponseCode<ApplyVerificationResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyVerificationResponseContext>.Descriptor,
ProcessHostRedirectionResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessStatusCodePagesErrorResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessPassthroughErrorResponse<ApplyVerificationResponseContext, RequireVerificationEndpointPassthroughEnabled>.Descriptor,
ProcessStatusCodePagesErrorResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessLocalErrorResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessHostRedirectionResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessEmptyResponse<ApplyVerificationResponseContext>.Descriptor);
}
}

12
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs

@ -52,11 +52,11 @@ namespace OpenIddict.Server.AspNetCore
RemoveCachedRequest.Descriptor,
AttachHttpResponseCode<ApplyLogoutResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyLogoutResponseContext>.Descriptor,
ProcessQueryResponse.Descriptor,
ProcessHostRedirectionResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessStatusCodePagesErrorResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessRedirectionResponse.Descriptor,
ProcessPassthroughErrorResponse<ApplyLogoutResponseContext, RequireLogoutEndpointPassthroughEnabled>.Descriptor,
ProcessStatusCodePagesErrorResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessLocalErrorResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessHostRedirectionResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessEmptyResponse<ApplyLogoutResponseContext>.Descriptor);
/// <summary>
@ -308,7 +308,7 @@ namespace OpenIddict.Server.AspNetCore
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireLogoutEndpointCachingEnabled>()
.UseSingletonHandler<RemoveCachedRequest>()
.SetOrder(ProcessQueryResponse.Descriptor.Order - 1_000)
.SetOrder(ProcessRedirectionResponse.Descriptor.Order - 1_000)
.Build();
/// <summary>
@ -344,7 +344,7 @@ namespace OpenIddict.Server.AspNetCore
/// Contains the logic responsible of processing logout responses.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class ProcessQueryResponse : IOpenIddictServerHandler<ApplyLogoutResponseContext>
public class ProcessRedirectionResponse : IOpenIddictServerHandler<ApplyLogoutResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
@ -352,7 +352,7 @@ namespace OpenIddict.Server.AspNetCore
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyLogoutResponseContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ProcessQueryResponse>()
.UseSingletonHandler<ProcessRedirectionResponse>()
.SetOrder(ProcessStatusCodePagesErrorResponse<ApplyLogoutResponseContext>.Descriptor.Order - 1_000)
.Build();

104
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -820,58 +820,6 @@ namespace OpenIddict.Server.AspNetCore
}
}
/// <summary>
/// Contains the logic responsible of processing empty OpenID Connect responses that should trigger a host redirection.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class ProcessHostRedirectionResponse<TContext> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ProcessHostRedirectionResponse<TContext>>()
.SetOrder(ProcessJsonResponse<TContext>.Descriptor.Order - 1_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpRequest()?.HttpContext.Response;
if (response == null)
{
throw new InvalidOperationException("The ASP.NET Core HTTP request cannot be resolved.");
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName);
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
{
response.Redirect(properties.RedirectUri);
context.Logger.LogInformation("The response was successfully returned as a 302 response.");
context.HandleRequest();
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
@ -1410,6 +1358,58 @@ namespace OpenIddict.Server.AspNetCore
}
}
/// <summary>
/// Contains the logic responsible of processing empty OpenID Connect responses that should trigger a host redirection.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class ProcessHostRedirectionResponse<TContext> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ProcessHostRedirectionResponse<TContext>>()
.SetOrder(ProcessEmptyResponse<TContext>.Descriptor.Order - 1_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpRequest()?.HttpContext.Response;
if (response == null)
{
throw new InvalidOperationException("The ASP.NET Core HTTP request cannot be resolved.");
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName);
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
{
response.Redirect(properties.RedirectUri);
context.Logger.LogInformation("The response was successfully returned as a 302 response.");
context.HandleRequest();
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of processing OpenID Connect responses that don't specify any parameter.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.

5
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreOptions.cs

@ -35,8 +35,11 @@ namespace OpenIddict.Server.AspNetCore
/// Gets or sets a boolean indicating whether OpenIddict should allow the rest of the request processing pipeline
/// to be invoked when returning an error from the interactive authorization and logout endpoints.
/// When this option is enabled, special logic must be added to these actions to handle errors, that can be
/// retrieved using <see cref="OpenIddictServerAspNetCoreHelpers.GetOpenIddictServerResponse(HttpContext)"/>
/// retrieved using <see cref="OpenIddictServerAspNetCoreHelpers.GetOpenIddictServerResponse(HttpContext)"/>.
/// </summary>
/// <remarks>
/// Important: the error pass-through mode cannot be used when the status code pages integration is enabled.
/// </remarks>
public bool EnableErrorPassthrough { get; set; }
/// <summary>

2
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs

@ -43,9 +43,9 @@ namespace OpenIddict.Server.Owin
*/
AttachHttpResponseCode<ApplyVerificationResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyVerificationResponseContext>.Descriptor,
ProcessHostRedirectionResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessPassthroughErrorResponse<ApplyVerificationResponseContext, RequireVerificationEndpointPassthroughEnabled>.Descriptor,
ProcessLocalErrorResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessHostRedirectionResponse<ApplyVerificationResponseContext>.Descriptor,
ProcessEmptyResponse<ApplyVerificationResponseContext>.Descriptor);
}
}

10
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs

@ -52,10 +52,10 @@ namespace OpenIddict.Server.Owin
RemoveCachedRequest.Descriptor,
AttachHttpResponseCode<ApplyLogoutResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyLogoutResponseContext>.Descriptor,
ProcessQueryResponse.Descriptor,
ProcessHostRedirectionResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessRedirectionResponse.Descriptor,
ProcessPassthroughErrorResponse<ApplyLogoutResponseContext, RequireLogoutEndpointPassthroughEnabled>.Descriptor,
ProcessLocalErrorResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessHostRedirectionResponse<ApplyLogoutResponseContext>.Descriptor,
ProcessEmptyResponse<ApplyLogoutResponseContext>.Descriptor);
/// <summary>
@ -302,7 +302,7 @@ namespace OpenIddict.Server.Owin
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireLogoutEndpointCachingEnabled>()
.UseSingletonHandler<RemoveCachedRequest>()
.SetOrder(ProcessQueryResponse.Descriptor.Order - 1_000)
.SetOrder(ProcessRedirectionResponse.Descriptor.Order - 1_000)
.Build();
/// <summary>
@ -338,7 +338,7 @@ namespace OpenIddict.Server.Owin
/// Contains the logic responsible of processing logout responses.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class ProcessQueryResponse : IOpenIddictServerHandler<ApplyLogoutResponseContext>
public class ProcessRedirectionResponse : IOpenIddictServerHandler<ApplyLogoutResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
@ -346,7 +346,7 @@ namespace OpenIddict.Server.Owin
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyLogoutResponseContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ProcessQueryResponse>()
.UseSingletonHandler<ProcessRedirectionResponse>()
.SetOrder(ProcessPassthroughErrorResponse<ApplyLogoutResponseContext, IOpenIddictServerHandlerFilter<ApplyLogoutResponseContext>>.Descriptor.Order - 1_000)
.Build();

104
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -751,58 +751,6 @@ namespace OpenIddict.Server.Owin
}
}
/// <summary>
/// Contains the logic responsible of processing empty OpenID Connect responses that should trigger a host redirection.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class ProcessHostRedirectionResponse<TContext> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ProcessHostRedirectionResponse<TContext>>()
.SetOrder(ProcessJsonResponse<TContext>.Descriptor.Order - 1_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetOwinRequest()?.Context.Response;
if (response == null)
{
throw new InvalidOperationException("The OWIN request cannot be resolved.");
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName);
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
{
response.Redirect(properties.RedirectUri);
context.Logger.LogInformation("The response was successfully returned as a 302 response.");
context.HandleRequest();
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
@ -1281,6 +1229,58 @@ namespace OpenIddict.Server.Owin
}
}
/// <summary>
/// Contains the logic responsible of processing empty OpenID Connect responses that should trigger a host redirection.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class ProcessHostRedirectionResponse<TContext> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ProcessHostRedirectionResponse<TContext>>()
.SetOrder(ProcessEmptyResponse<TContext>.Descriptor.Order - 1_000)
.Build();
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="context">The context associated with the event to process.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public ValueTask HandleAsync([NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetOwinRequest()?.Context.Response;
if (response == null)
{
throw new InvalidOperationException("The OWIN request cannot be resolved.");
}
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName);
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
{
response.Redirect(properties.RedirectUri);
context.Logger.LogInformation("The response was successfully returned as a 302 response.");
context.HandleRequest();
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of processing OpenID Connect responses that don't specify any parameter.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.

55
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs

@ -531,6 +531,61 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]) response["parameter_with_multiple_values"]);
}
[Fact]
public async Task ApplyLogoutResponse_UsesPostLogoutRedirectUriWhenProvided()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ApplyLogoutResponseContext>(builder =>
builder.UseInlineHandler(context =>
{
context.Response["target_uri"] = context.PostLogoutRedirectUri;
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/logout", new OpenIddictRequest
{
PostLogoutRedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal("http://www.fabrikam.com/path", (string) response["target_uri"]);
}
[Fact]
public async Task ApplyLogoutResponse_ReturnsEmptyResponseWhenNoPostLogoutRedirectUriIsProvided()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ApplyLogoutResponseContext>(builder =>
builder.UseInlineHandler(context =>
{
context.Response["target_uri"] = context.PostLogoutRedirectUri;
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/logout", new OpenIddictRequest());
// Assert
Assert.Empty(response.GetParameters());
}
[Fact]
public async Task ApplyLogoutResponse_DoesNotSetStateWhenUserIsNotRedirected()
{

Loading…
Cancel
Save