You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
700 lines
28 KiB
700 lines
28 KiB
/*
|
|
* 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.Diagnostics;
|
|
using System.Security.Claims;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace OpenIddict.Client;
|
|
|
|
public class OpenIddictClientService
|
|
{
|
|
private readonly IServiceProvider _provider;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="OpenIddictClientService"/> class.
|
|
/// </summary>
|
|
/// <param name="provider">The service provider.</param>
|
|
public OpenIddictClientService(IServiceProvider provider!!)
|
|
=> _provider = provider;
|
|
|
|
/// <summary>
|
|
/// Retrieves the OpenID Connect server configuration from the specified address.
|
|
/// </summary>
|
|
/// <param name="address">The address of the remote metadata endpoint.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>The OpenID Connect server configuration retrieved from the remote server.</returns>
|
|
public async ValueTask<OpenIddictConfiguration> GetConfigurationAsync(Uri address!!, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!address.IsAbsoluteUri)
|
|
{
|
|
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Note: this service is registered as a singleton service. As such, it cannot
|
|
// directly depend on scoped services like the validation provider. To work around
|
|
// this limitation, a scope is manually created for each method to this service.
|
|
var scope = _provider.CreateScope();
|
|
|
|
// Note: a try/finally block is deliberately used here to ensure the service scope
|
|
// can be disposed of asynchronously if it implements IAsyncDisposable.
|
|
try
|
|
{
|
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
|
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
|
|
var transaction = await factory.CreateTransactionAsync();
|
|
|
|
var request = new OpenIddictRequest();
|
|
request = await PrepareConfigurationRequestAsync();
|
|
request = await ApplyConfigurationRequestAsync();
|
|
var response = await ExtractConfigurationResponseAsync();
|
|
|
|
return await HandleConfigurationResponseAsync() ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0145));
|
|
|
|
async ValueTask<OpenIddictRequest> PrepareConfigurationRequestAsync()
|
|
{
|
|
var context = new PrepareConfigurationRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0148(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictRequest> ApplyConfigurationRequestAsync()
|
|
{
|
|
var context = new ApplyConfigurationRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0149(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictResponse> ExtractConfigurationResponseAsync()
|
|
{
|
|
var context = new ExtractConfigurationResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0150(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007));
|
|
|
|
return context.Response;
|
|
}
|
|
|
|
async ValueTask<OpenIddictConfiguration> HandleConfigurationResponseAsync()
|
|
{
|
|
var context = new HandleConfigurationResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request,
|
|
Response = response
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0151(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Configuration;
|
|
}
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (scope is IAsyncDisposable disposable)
|
|
{
|
|
await disposable.DisposeAsync();
|
|
}
|
|
|
|
else
|
|
{
|
|
scope.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the security keys exposed by the specified JWKS endpoint.
|
|
/// </summary>
|
|
/// <param name="address">The address of the remote metadata endpoint.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>The security keys retrieved from the remote server.</returns>
|
|
public async ValueTask<JsonWebKeySet> GetSecurityKeysAsync(Uri address!!, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!address.IsAbsoluteUri)
|
|
{
|
|
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Note: this service is registered as a singleton service. As such, it cannot
|
|
// directly depend on scoped services like the validation provider. To work around
|
|
// this limitation, a scope is manually created for each method to this service.
|
|
var scope = _provider.CreateScope();
|
|
|
|
// Note: a try/finally block is deliberately used here to ensure the service scope
|
|
// can be disposed of asynchronously if it implements IAsyncDisposable.
|
|
try
|
|
{
|
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
|
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
|
|
var transaction = await factory.CreateTransactionAsync();
|
|
|
|
var request = new OpenIddictRequest();
|
|
request = await PrepareCryptographyRequestAsync();
|
|
request = await ApplyCryptographyRequestAsync();
|
|
|
|
var response = await ExtractCryptographyResponseAsync();
|
|
|
|
return await HandleCryptographyResponseAsync() ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0147));
|
|
|
|
async ValueTask<OpenIddictRequest> PrepareCryptographyRequestAsync()
|
|
{
|
|
var context = new PrepareCryptographyRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0152(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictRequest> ApplyCryptographyRequestAsync()
|
|
{
|
|
var context = new ApplyCryptographyRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0153(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictResponse> ExtractCryptographyResponseAsync()
|
|
{
|
|
var context = new ExtractCryptographyResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0154(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007));
|
|
|
|
return context.Response;
|
|
}
|
|
|
|
async ValueTask<JsonWebKeySet> HandleCryptographyResponseAsync()
|
|
{
|
|
var context = new HandleCryptographyResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Request = request,
|
|
Response = response
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0155(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.SecurityKeys;
|
|
}
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (scope is IAsyncDisposable disposable)
|
|
{
|
|
await disposable.DisposeAsync();
|
|
}
|
|
|
|
else
|
|
{
|
|
scope.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the user tokens using the specified refresh token.
|
|
/// </summary>
|
|
/// <param name="registration">The client registration.</param>
|
|
/// <param name="token">The refresh token to use.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>The response and a merged principal containing the claims extracted from the tokens and userinfo response.</returns>
|
|
public async ValueTask<(OpenIddictResponse Response, ClaimsPrincipal Principal)> RefreshTokensAsync(
|
|
OpenIddictClientRegistration registration!!, string token, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrEmpty(token))
|
|
{
|
|
throw new ArgumentException(SR.GetResourceString(SR.ID0156), nameof(token));
|
|
}
|
|
|
|
var configuration = await registration.ConfigurationManager.GetConfigurationAsync(default) ??
|
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
|
|
|
|
if (configuration.TokenEndpoint is not { IsAbsoluteUri: true } ||
|
|
!configuration.TokenEndpoint.IsWellFormedOriginalString())
|
|
{
|
|
throw new InvalidOperationException(SR.FormatID0301(Metadata.TokenEndpoint));
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Note: this service is registered as a singleton service. As such, it cannot
|
|
// directly depend on scoped services like the validation provider. To work around
|
|
// this limitation, a scope is manually created for each method to this service.
|
|
var scope = _provider.CreateScope();
|
|
|
|
// Note: a try/finally block is deliberately used here to ensure the service scope
|
|
// can be disposed of asynchronously if it implements IAsyncDisposable.
|
|
try
|
|
{
|
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
|
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
|
|
var transaction = await factory.CreateTransactionAsync();
|
|
|
|
var context = new ProcessAuthenticationContext(transaction)
|
|
{
|
|
GrantType = GrantTypes.RefreshToken,
|
|
Issuer = registration.Issuer,
|
|
RefreshToken = token,
|
|
Registration = registration
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0163(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
Debug.Assert(context.TokenResponse is not null, SR.GetResourceString(SR.ID4007));
|
|
|
|
// Create a composite principal containing claims resolved from the
|
|
// backchannel identity token and the userinfo token, if available.
|
|
return (context.TokenResponse, CreatePrincipal(
|
|
context.BackchannelIdentityTokenPrincipal,
|
|
context.UserinfoTokenPrincipal));
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (scope is IAsyncDisposable disposable)
|
|
{
|
|
await disposable.DisposeAsync();
|
|
}
|
|
|
|
else
|
|
{
|
|
scope.Dispose();
|
|
}
|
|
}
|
|
|
|
static ClaimsPrincipal CreatePrincipal(params ClaimsPrincipal?[] principals)
|
|
{
|
|
// Note: the OpenIddict client handler can be used as a pure OAuth 2.0-only stack for
|
|
// delegation scenarios where the identity of the user is not needed. In this case,
|
|
// since no principal can be resolved from a token or a userinfo response to construct
|
|
// a user identity, a fake one containing an "unauthenticated" identity (i.e with its
|
|
// AuthenticationType property deliberately left to null) is used to allow ASP.NET Core
|
|
// to return a "successful" authentication result for these delegation-only scenarios.
|
|
if (!principals.Any(principal => principal?.Identity is ClaimsIdentity { IsAuthenticated: true }))
|
|
{
|
|
return new ClaimsPrincipal(new ClaimsIdentity());
|
|
}
|
|
|
|
// Create a new composite identity containing the claims of all the principals.
|
|
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
|
|
|
|
foreach (var principal in principals)
|
|
{
|
|
// Note: the principal may be null if no value was extracted from the corresponding token.
|
|
if (principal is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var claim in principal.Claims)
|
|
{
|
|
// If a claim with the same type and the same value already exist, skip it.
|
|
if (identity.HasClaim(claim.Type, claim.Value))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
identity.AddClaim(claim);
|
|
}
|
|
}
|
|
|
|
return new ClaimsPrincipal(identity);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends the token request and retrieves the corresponding response.
|
|
/// </summary>
|
|
/// <param name="registration">The client registration.</param>
|
|
/// <param name="address">The address of the token endpoint.</param>
|
|
/// <param name="request">The token request.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>The token response.</returns>
|
|
public async ValueTask<OpenIddictResponse> SendTokenRequestAsync(
|
|
OpenIddictClientRegistration registration!!, Uri address!!, OpenIddictRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!address.IsAbsoluteUri || !address.IsWellFormedOriginalString())
|
|
{
|
|
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Note: this service is registered as a singleton service. As such, it cannot
|
|
// directly depend on scoped services like the validation provider. To work around
|
|
// this limitation, a scope is manually created for each method to this service.
|
|
var scope = _provider.CreateScope();
|
|
|
|
// Note: a try/finally block is deliberately used here to ensure the service scope
|
|
// can be disposed of asynchronously if it implements IAsyncDisposable.
|
|
try
|
|
{
|
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
|
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
|
|
var transaction = await factory.CreateTransactionAsync();
|
|
|
|
request = await PrepareTokenRequestAsync();
|
|
request = await ApplyTokenRequestAsync();
|
|
|
|
var response = await ExtractTokenResponseAsync();
|
|
|
|
return await HandleTokenResponseAsync();
|
|
|
|
async ValueTask<OpenIddictRequest> PrepareTokenRequestAsync()
|
|
{
|
|
var context = new PrepareTokenRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0152(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictRequest> ApplyTokenRequestAsync()
|
|
{
|
|
var context = new ApplyTokenRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0153(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictResponse> ExtractTokenResponseAsync()
|
|
{
|
|
var context = new ExtractTokenResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0154(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007));
|
|
|
|
return context.Response;
|
|
}
|
|
|
|
async ValueTask<OpenIddictResponse> HandleTokenResponseAsync()
|
|
{
|
|
var context = new HandleTokenResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request,
|
|
Response = response
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0155(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Response;
|
|
}
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (scope is IAsyncDisposable disposable)
|
|
{
|
|
await disposable.DisposeAsync();
|
|
}
|
|
|
|
else
|
|
{
|
|
scope.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends the userinfo request and retrieves the corresponding response.
|
|
/// </summary>
|
|
/// <param name="registration">The client registration.</param>
|
|
/// <param name="address">The address of the userinfo endpoint.</param>
|
|
/// <param name="request">The userinfo request.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>The response and the principal extracted from the userinfo response or the userinfo token.</returns>
|
|
public async ValueTask<(OpenIddictResponse Response, (ClaimsPrincipal? Principal, string? Token))> SendUserinfoRequestAsync(
|
|
OpenIddictClientRegistration registration!!, Uri address!!, OpenIddictRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!address.IsAbsoluteUri || !address.IsWellFormedOriginalString())
|
|
{
|
|
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Note: this service is registered as a singleton service. As such, it cannot
|
|
// directly depend on scoped services like the validation provider. To work around
|
|
// this limitation, a scope is manually created for each method to this service.
|
|
var scope = _provider.CreateScope();
|
|
|
|
// Note: a try/finally block is deliberately used here to ensure the service scope
|
|
// can be disposed of asynchronously if it implements IAsyncDisposable.
|
|
try
|
|
{
|
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>();
|
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>();
|
|
var transaction = await factory.CreateTransactionAsync();
|
|
|
|
request = await PrepareUserinfoRequestAsync();
|
|
request = await ApplyUserinfoRequestAsync();
|
|
|
|
var (response, token) = await ExtractUserinfoResponseAsync();
|
|
|
|
return await HandleUserinfoResponseAsync();
|
|
|
|
async ValueTask<OpenIddictRequest> PrepareUserinfoRequestAsync()
|
|
{
|
|
var context = new PrepareUserinfoRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0152(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<OpenIddictRequest> ApplyUserinfoRequestAsync()
|
|
{
|
|
var context = new ApplyUserinfoRequestContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0153(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return context.Request;
|
|
}
|
|
|
|
async ValueTask<(OpenIddictResponse, string?)> ExtractUserinfoResponseAsync()
|
|
{
|
|
var context = new ExtractUserinfoResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0154(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007));
|
|
|
|
return (context.Response, context.UserinfoToken);
|
|
}
|
|
|
|
async ValueTask<(OpenIddictResponse, (ClaimsPrincipal?, string?))> HandleUserinfoResponseAsync()
|
|
{
|
|
var context = new HandleUserinfoResponseContext(transaction)
|
|
{
|
|
Address = address,
|
|
Issuer = registration.Issuer,
|
|
Registration = registration,
|
|
Request = request,
|
|
Response = response,
|
|
UserinfoToken = token
|
|
};
|
|
|
|
await dispatcher.DispatchAsync(context);
|
|
|
|
if (context.IsRejected)
|
|
{
|
|
throw new OpenIddictExceptions.GenericException(
|
|
SR.FormatID0155(context.Error, context.ErrorDescription, context.ErrorUri),
|
|
context.Error, context.ErrorDescription, context.ErrorUri);
|
|
}
|
|
|
|
return (context.Response, (context.Principal, context.UserinfoToken));
|
|
}
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (scope is IAsyncDisposable disposable)
|
|
{
|
|
await disposable.DisposeAsync();
|
|
}
|
|
|
|
else
|
|
{
|
|
scope.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|