Browse Source

Abort authentication/challenge/sign-out demands that are not handled by the system integration package

pull/2085/head
Kévin Chalet 2 years ago
parent
commit
38ede1020b
  1. 6
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 100
      src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs
  3. 63
      src/OpenIddict.Client/OpenIddictClientHandlers.cs

6
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1659,6 +1659,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0441" xml:space="preserve"> <data name="ID0441" xml:space="preserve">
<value>The specified format string cannot contain a '{0}' character when it is included as an allowed character in the charset.</value> <value>The specified format string cannot contain a '{0}' character when it is included as an allowed character in the charset.</value>
</data> </data>
<data name="ID0442" xml:space="preserve">
<value>The client registration corresponding to the specified nonce could not be resolved, which may indicate an invalid authentication demand or an invalid configuration. When using interactive user authentication flows in desktop or mobile applications, make sure the system integration is registered by calling 'services.AddOpenIddict().AddClient().UseSystemIntegration()'. When using interactive user authentication flows in an ASP.NET Core or OWIN application, use the authentication APIs exposed by 'IAuthenticationService' or 'IAuthenticationManager' instead of the 'OpenIddictClientService' class.</value>
</data>
<data name="ID0443" xml:space="preserve">
<value>The base URI could not be resolved from the context, which may indicate an invalid authentication demand or an invalid configuration. When using interactive user authentication flows in desktop or mobile applications, make sure the system integration is registered by calling 'services.AddOpenIddict().AddClient().UseSystemIntegration()'. When using interactive user authentication flows in an ASP.NET Core or OWIN application, use the authentication APIs exposed by 'IAuthenticationService' or 'IAuthenticationManager' instead of the 'OpenIddictClientService' class.</value>
</data>
<data name="ID2000" xml:space="preserve"> <data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value> <value>The security token is missing.</value>
</data> </data>

100
src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs

@ -46,10 +46,10 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
*/ */
WaitMarshalledAuthentication.Descriptor, WaitMarshalledAuthentication.Descriptor,
RestoreClientRegistrationFromMarshalledContext.Descriptor,
RestoreStateTokenFromMarshalledAuthentication.Descriptor, RestoreStateTokenFromMarshalledAuthentication.Descriptor,
RestoreStateTokenPrincipalFromMarshalledAuthentication.Descriptor, RestoreStateTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreHostAuthenticationPropertiesFromMarshalledAuthentication.Descriptor, RestoreHostAuthenticationPropertiesFromMarshalledAuthentication.Descriptor,
RestoreClientRegistrationFromMarshalledContext.Descriptor,
RedirectProtocolActivation.Descriptor, RedirectProtocolActivation.Descriptor,
ResolveRequestForgeryProtection.Descriptor, ResolveRequestForgeryProtection.Descriptor,
@ -537,7 +537,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>() .AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<WaitMarshalledAuthentication>() .UseSingletonHandler<WaitMarshalledAuthentication>()
.SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 500) .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 250)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -567,8 +567,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// returned to the redirection endpoint (materialized as a registered protocol activation URI) and handled // returned to the redirection endpoint (materialized as a registered protocol activation URI) and handled
// by OpenIddict via the ProcessRequest event. Since it is asynchronous by nature, this process requires // by OpenIddict via the ProcessRequest event. Since it is asynchronous by nature, this process requires
// using a signal mechanism to unblock the authentication operation once it is complete. For that, the // using a signal mechanism to unblock the authentication operation once it is complete. For that, the
// marshal uses a TaskCompletionSource (one per authentication) that will be automatically completed // marshal uses a TaskCompletionSource (one per authentication) that will be automatically completed or
// or aborted by a specialized event handler as part of the ProcessRequest/ProcessError events processing. // aborted by a specialized event handler as part of the ProcessRequest/ProcessError events processing.
try try
{ {
@ -616,6 +616,52 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for restoring the client registration and
/// configuration from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreClientRegistrationFromMarshalledContext : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreClientRegistrationFromMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreClientRegistrationFromMarshalledContext>()
.SetOrder(ResolveClientRegistrationFromAuthenticationContext.Descriptor.Order - 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
(context.Configuration, context.Registration) = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// issuer registration and configuration from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> (notification.Configuration, notification.Registration),
_ => (context.Configuration, context.Registration)
};
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for restoring the state token /// Contains the logic responsible for restoring the state token
/// from the marshalled authentication context, if applicable. /// from the marshalled authentication context, if applicable.
@ -756,52 +802,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for restoring the client registration and
/// configuration from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreClientRegistrationFromMarshalledContext : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreClientRegistrationFromMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreClientRegistrationFromMarshalledContext>()
.SetOrder(ResolveClientRegistrationFromStateToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
(context.Configuration, context.Registration) = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// issuer registration and configuration from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> (notification.Configuration, notification.Registration),
_ => (context.Configuration, context.Registration)
};
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for redirecting the protocol activation to /// Contains the logic responsible for redirecting the protocol activation to
/// the instance that initially started the authentication demand, if applicable. /// the instance that initially started the authentication demand, if applicable.

63
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -406,11 +406,21 @@ public static partial class OpenIddictClientHandlers
// //
// Client registrations/configurations that need to be resolved as part of authentication demands // Client registrations/configurations that need to be resolved as part of authentication demands
// triggered from the redirection or post-logout redirection requests are handled elsewhere. // triggered from the redirection or post-logout redirection requests are handled elsewhere.
if (!string.IsNullOrEmpty(context.Nonce) || context.EndpointType is not OpenIddictClientEndpointType.Unknown) if (context.EndpointType is OpenIddictClientEndpointType.PostLogoutRedirection or
OpenIddictClientEndpointType.Redirection)
{ {
return; return;
} }
// When using a user interactive flow with the system integration host, the client registration is expected
// to be attached by a dedicated event handler registered by the system integration package. If a nonce was
// attached but no client registration was resolved at this point, throw an exception to let the user know
// that the authentication demand is invalid or the system integration host is not correctly configured.
if (context.Registration is null && !string.IsNullOrEmpty(context.Nonce))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0442));
}
context.Registration ??= context switch context.Registration ??= context switch
{ {
// If specified, resolve the registration using the attached registration identifier. // If specified, resolve the registration using the attached registration identifier.
@ -785,8 +795,8 @@ public static partial class OpenIddictClientHandlers
.AddFilter<RequireStateTokenPrincipal>() .AddFilter<RequireStateTokenPrincipal>()
.AddFilter<RequireStateTokenValidated>() .AddFilter<RequireStateTokenValidated>()
.UseScopedHandler<RedeemStateTokenEntry>() .UseScopedHandler<RedeemStateTokenEntry>()
// Note: this handler is deliberately executed early in the pipeline to ensure // Note: this handler is deliberately executed early in the pipeline to ensure that
// that the state token entry is always marked as redeemed even if the authentication // the state token entry is always marked as redeemed even if the authentication
// demand is rejected later in the pipeline (e.g because an error was returned). // demand is rejected later in the pipeline (e.g because an error was returned).
.SetOrder(ResolveNonceFromStateToken.Descriptor.Order + 1_000) .SetOrder(ResolveNonceFromStateToken.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
@ -4905,15 +4915,32 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
// Don't overwrite the redirect_uri if one was already explicitly attached.
if (context.RedirectUri is not null)
{
return default;
}
// Unlike OpenID Connect, OAuth 2.0 and 2.1 don't require specifying a redirect_uri // Unlike OpenID Connect, OAuth 2.0 and 2.1 don't require specifying a redirect_uri
// but it is always considered mandatory in OpenIddict (independently of whether the // but it is always considered mandatory in OpenIddict (independently of whether the
// selected flow is an OpenID Connect flow) as it's later used to ensure the redirection // selected flow is an OpenID Connect flow) as it's later used to ensure the redirection
// URI the authorization response was sent to matches the expected endpoint, which helps // URI the authorization response was sent to matches the expected endpoint, which helps
// mitigate mix-up attacks when no standard issuer validation can be directly used. // mitigate mix-up attacks when no standard issuer validation can be directly used.
context.RedirectUri ??= OpenIddictHelpers.CreateAbsoluteUri( if (context.Registration.RedirectUri is null)
context.BaseUri, {
context.Registration.RedirectUri)?.AbsoluteUri ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0300)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0300));
}
// If the redirect_uri attached to the client registration is not an
// absolute URI and the base URI is not available, throw an exception.
if (context.BaseUri is null && !context.Registration.RedirectUri.IsAbsoluteUri)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0443));
}
context.RedirectUri = OpenIddictHelpers.CreateAbsoluteUri(
left : context.BaseUri,
right: context.Registration.RedirectUri).AbsoluteUri;
return default; return default;
} }
@ -7329,10 +7356,28 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
// Don't overwrite the post_logout_redirect_uri if one was already explicitly attached.
if (context.PostLogoutRedirectUri is not null)
{
return default;
}
// Note: the post_logout_redirect_uri parameter is optional. // Note: the post_logout_redirect_uri parameter is optional.
context.PostLogoutRedirectUri ??= OpenIddictHelpers.CreateAbsoluteUri( if (context.Registration.PostLogoutRedirectUri is null)
context.BaseUri, {
context.Registration.PostLogoutRedirectUri)?.AbsoluteUri; return default;
}
// If the post_logout_redirect_uri attached to the client registration is not
// an absolute URI and the base URI is not available, throw an exception.
if (context.BaseUri is null && !context.Registration.PostLogoutRedirectUri.IsAbsoluteUri)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0443));
}
context.PostLogoutRedirectUri = OpenIddictHelpers.CreateAbsoluteUri(
left : context.BaseUri,
right: context.Registration.PostLogoutRedirectUri).AbsoluteUri;
return default; return default;
} }

Loading…
Cancel
Save