Browse Source

Automatically validate the authorization associated with authorization codes/refresh tokens

pull/619/head
Kévin Chalet 8 years ago
parent
commit
aa89d0c215
  1. 48
      src/OpenIddict.Server/Internal/OpenIddictServerInitializer.cs
  2. 107
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Exchange.cs
  3. 8
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
  4. 40
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Introspection.cs
  5. 2
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Revocation.cs
  6. 12
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Serialization.cs
  7. 84
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs
  8. 20
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  9. 9
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  10. 11
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  11. 44
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerInitializerTests.cs
  12. 562
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs
  13. 289
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs
  14. 4
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs
  15. 101
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs
  16. 22
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

48
src/OpenIddict.Server/Internal/OpenIddictServerInitializer.cs

@ -7,6 +7,7 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Text;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
@ -139,8 +140,7 @@ namespace OpenIddict.Server
if (!options.AuthorizationEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) ||
options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit)))
{
throw new InvalidOperationException("The authorization endpoint must be enabled to use " +
"the authorization code and implicit flows.");
throw new InvalidOperationException("The authorization endpoint must be enabled to use the authorization code and implicit flows.");
}
// Ensure the token endpoint has been enabled when the authorization code,
@ -150,51 +150,51 @@ namespace OpenIddict.Server
options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Password) ||
options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)))
{
throw new InvalidOperationException("The token endpoint must be enabled to use the authorization code, " +
"client credentials, password and refresh token flows.");
throw new InvalidOperationException(
"The token endpoint must be enabled to use the authorization code, client credentials, password and refresh token flows.");
}
if (options.RevocationEndpointPath.HasValue && options.DisableTokenRevocation)
if (options.RevocationEndpointPath.HasValue && options.DisableTokenStorage)
{
throw new InvalidOperationException("The revocation endpoint cannot be enabled when token revocation is disabled.");
throw new InvalidOperationException("The revocation endpoint cannot be enabled when token storage is disabled.");
}
if (options.UseReferenceTokens && options.DisableTokenRevocation)
if (options.UseReferenceTokens && options.DisableTokenStorage)
{
throw new InvalidOperationException(
"Reference tokens cannot be used when disabling token revocation.");
throw new InvalidOperationException("Reference tokens cannot be used when disabling token storage.");
}
if (options.UseReferenceTokens && options.AccessTokenHandler != null)
{
throw new InvalidOperationException(
"Reference tokens cannot be used when configuring JWT as the access token format.");
throw new InvalidOperationException("Reference tokens cannot be used when configuring JWT as the access token format.");
}
if (options.UseSlidingExpiration && options.DisableTokenRevocation && !options.UseRollingTokens)
if (options.UseSlidingExpiration && options.DisableTokenStorage && !options.UseRollingTokens)
{
throw new InvalidOperationException("Sliding expiration must be disabled when turning off " +
"token revocation if rolling tokens are not used.");
throw new InvalidOperationException(
"Sliding expiration must be disabled when turning off token storage if rolling tokens are not used.");
}
if (options.AccessTokenHandler != null && options.SigningCredentials.Count == 0)
{
throw new InvalidOperationException(
"At least one signing key must be registered when using JWT as the access token format. " +
"Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' " +
"or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.");
throw new InvalidOperationException(new StringBuilder()
.AppendLine("At least one signing key must be registered when using JWT as the access token format.")
.Append("Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' ")
.Append("or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call ")
.Append("'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.")
.ToString());
}
// Ensure at least one asymmetric signing certificate/key was registered if the implicit flow was enabled.
if (!options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey) &&
options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit))
{
throw new InvalidOperationException(
"At least one asymmetric signing key must be registered when enabling the implicit flow. " +
"Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' " +
"or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.");
throw new InvalidOperationException(new StringBuilder()
.AppendLine("At least one asymmetric signing key must be registered when enabling the implicit flow.")
.Append("Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' ")
.Append("or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call ")
.Append("'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.")
.ToString());
}
// Automatically add the offline_access scope if the refresh token grant has been enabled.

107
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Exchange.cs

@ -295,8 +295,7 @@ namespace OpenIddict.Server
context.Request.SetProperty(OpenIddictConstants.Properties.AuthenticationTicket, context.Ticket);
}
if (options.DisableTokenRevocation || (!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsRefreshTokenGrantType()))
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType())
{
// Invoke the rest of the pipeline to allow
// the user code to handle the token request.
@ -309,51 +308,85 @@ namespace OpenIddict.Server
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
// Extract the token identifier from the authentication ticket.
var identifier = context.Ticket.GetTokenId();
Debug.Assert(!string.IsNullOrEmpty(identifier), "The authentication ticket should contain a token identifier.");
// Unless token revocation was explicitly disabled, ensure
// the authorization code/refresh token is still valid.
if (!options.DisableTokenStorage)
{
// Extract the token identifier from the authentication ticket.
var identifier = context.Ticket.GetTokenId();
Debug.Assert(!string.IsNullOrEmpty(identifier), "The authentication ticket should contain a token identifier.");
// Retrieve the authorization code/refresh token from the request properties.
var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{identifier}");
Debug.Assert(token != null, "The token shouldn't be null.");
// If the authorization code/refresh token is already marked as redeemed, this may indicate that
// it was compromised. In this case, revoke the authorization and all the associated tokens.
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await _tokenManager.IsRedeemedAsync(token))
{
await TryRevokeTokenAsync(token);
// Retrieve the authorization code/refresh token from the request properties.
var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{identifier}");
Debug.Assert(token != null, "The token shouldn't be null.");
// Try to revoke the authorization and the associated tokens.
// If the operation fails, the helpers will automatically log
// and swallow the exception to ensure that a valid error
// response will be returned to the client application.
if (!options.DisableAuthorizationStorage)
{
await TryRevokeAuthorizationAsync(context.Ticket);
await TryRevokeTokensAsync(context.Ticket);
}
// If the authorization code/refresh token is already marked as redeemed, this may indicate that
// it was compromised. In this case, revoke the authorization and all the associated tokens.
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await _tokenManager.IsRedeemedAsync(token))
{
// Try to revoke the authorization and the associated tokens.
// If the operation fails, the helpers will automatically log
// and swallow the exception to ensure that a valid error
// response will be returned to the client application.
await TryRevokeAuthorizationAsync(context.Ticket);
await TryRevokeTokensAsync(context.Ticket);
await TryRevokeTokenAsync(token);
_logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' has already been redeemed.", identifier);
_logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' has already been redeemed.", identifier);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code has already been redeemed." :
"The specified refresh token has already been redeemed.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code has already been redeemed." :
"The specified refresh token has already been redeemed.");
return;
}
return;
else if (!await _tokenManager.IsValidAsync(token))
{
_logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' was no longer valid.", identifier);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
return;
}
}
else if (!await _tokenManager.IsValidAsync(token))
// Unless authorization revocation was explicitly disabled, ensure the
// authorization associated with the code/refresh token is still valid.
if (!options.DisableAuthorizationStorage)
{
_logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' was no longer valid.", identifier);
// Extract the authorization identifier from the authentication ticket.
var identifier = context.Ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (!string.IsNullOrEmpty(identifier))
{
var authorization = await _authorizationManager.FindByIdAsync(identifier);
if (authorization == null || !await _authorizationManager.IsValidAsync(authorization))
{
_logger.LogError("The token '{Identifier}' was rejected because " +
"the associated authorization was no longer valid.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The authorization associated with the authorization code is no longer valid." :
"The authorization associated with the refresh token is no longer valid.");
return;
return;
}
}
}
// Invoke the rest of the pipeline to allow

8
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs

@ -83,8 +83,8 @@ namespace OpenIddict.Server
[NotNull] OpenIdConnectRequest request,
[NotNull] ISecureDataFormat<AuthenticationTicket> format)
{
Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
Debug.Assert(!(options.DisableTokenStorage && options.UseReferenceTokens),
"Token storage cannot be disabled when using reference tokens.");
Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken ||
type == OpenIdConnectConstants.TokenUsages.AuthorizationCode ||
@ -103,7 +103,7 @@ namespace OpenIddict.Server
ticket.Properties.ExpiresUtc = properties.ExpiresUtc;
}
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return null;
}
@ -216,7 +216,7 @@ namespace OpenIddict.Server
[NotNull] OpenIdConnectRequest request,
[NotNull] ISecureDataFormat<AuthenticationTicket> format)
{
Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens),
Debug.Assert(!(options.DisableTokenStorage && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken ||

40
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Introspection.cs

@ -138,24 +138,40 @@ namespace OpenIddict.Server
return;
}
// If the received token is not a reference access token,
// skip the additional reference token validation checks.
if (!options.UseReferenceTokens)
// If an authorization was attached to the access token, ensure it is still valid.
if (!options.DisableAuthorizationStorage &&
context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId))
{
return;
}
var authorization = await _authorizationManager.FindByIdAsync(
context.Ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId));
if (authorization == null || !await _authorizationManager.IsValidAsync(authorization))
{
_logger.LogError("The token '{Identifier}' was declared as inactive because " +
"the associated authorization was no longer valid.", identifier);
context.Active = false;
// Retrieve the token from the request properties. If it's marked as invalid, return active = false.
var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{identifier}");
Debug.Assert(token != null, "The token shouldn't be null.");
return;
}
}
if (!await _tokenManager.IsValidAsync(token))
// If the received token is a reference access token - i.e a token for
// which an entry exists in the database - ensure it is still valid.
if (options.UseReferenceTokens)
{
_logger.LogInformation("The token '{Identifier}' was declared as inactive because it was revoked.", identifier);
// Retrieve the token from the request properties. If it's marked as invalid, return active = false.
var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{identifier}");
Debug.Assert(token != null, "The token shouldn't be null.");
context.Active = false;
if (!await _tokenManager.IsValidAsync(token))
{
_logger.LogInformation("The token '{Identifier}' was declared as inactive because it was revoked.", identifier);
return;
context.Active = false;
return;
}
}
await base.HandleIntrospectionRequest(context);

2
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Revocation.cs

@ -22,7 +22,7 @@ namespace OpenIddict.Server
{
var options = (OpenIddictServerOptions) context.Options;
Debug.Assert(!options.DisableTokenRevocation, "Token revocation support shouldn't be disabled at this stage.");
Debug.Assert(!options.DisableTokenStorage, "Token storage support shouldn't be disabled at this stage.");
// When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token.
if (!string.IsNullOrEmpty(context.Request.TokenTypeHint))

12
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Serialization.cs

@ -17,7 +17,7 @@ namespace OpenIddict.Server
public override async Task DeserializeAccessToken([NotNull] DeserializeAccessTokenContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}
@ -40,7 +40,7 @@ namespace OpenIddict.Server
public override async Task DeserializeAuthorizationCode([NotNull] DeserializeAuthorizationCodeContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}
@ -59,7 +59,7 @@ namespace OpenIddict.Server
public override async Task DeserializeRefreshToken([NotNull] DeserializeRefreshTokenContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}
@ -78,7 +78,7 @@ namespace OpenIddict.Server
public override async Task SerializeAccessToken([NotNull] SerializeAccessTokenContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}
@ -104,7 +104,7 @@ namespace OpenIddict.Server
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}
@ -132,7 +132,7 @@ namespace OpenIddict.Server
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}

84
src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs

@ -117,56 +117,51 @@ namespace OpenIddict.Server
context.IncludeRefreshToken &= options.UseRollingTokens;
}
// If token revocation was explicitly disabled,
// none of the following security routines apply.
if (options.DisableTokenRevocation)
// If token revocation was explicitly disabled, none of the following security routines apply.
if (!options.DisableTokenStorage)
{
await base.ProcessSigninResponse(context);
return;
}
var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{context.Ticket.GetTokenId()}");
Debug.Assert(token != null, "The token shouldn't be null.");
// If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
// mark the authorization code or the refresh token as redeemed to prevent future reuses.
// If the operation fails, return an error indicating the code/token is no longer valid.
// See https://tools.ietf.org/html/rfc6749#section-6 for more information.
if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
{
if (!await TryRedeemTokenAsync(token))
var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{context.Ticket.GetTokenId()}");
Debug.Assert(token != null, "The token shouldn't be null.");
// If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
// mark the authorization code or the refresh token as redeemed to prevent future reuses.
// If the operation fails, return an error indicating the code/token is no longer valid.
// See https://tools.ietf.org/html/rfc6749#section-6 for more information.
if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
if (!await TryRedeemTokenAsync(token))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
return;
return;
}
}
}
if (context.Request.IsRefreshTokenGrantType())
{
// When rolling tokens are enabled, try to revoke all the previously issued tokens
// associated with the authorization if the request is a refresh_token request.
// If the operation fails, silently ignore the error and keep processing the request:
// this may indicate that one of the revoked tokens was modified by a concurrent request.
if (options.UseRollingTokens)
if (context.Request.IsRefreshTokenGrantType())
{
await TryRevokeTokensAsync(context.Ticket);
}
// When rolling tokens are enabled, try to revoke all the previously issued tokens
// associated with the authorization if the request is a refresh_token request.
// If the operation fails, silently ignore the error and keep processing the request:
// this may indicate that one of the revoked tokens was modified by a concurrent request.
if (options.UseRollingTokens)
{
await TryRevokeTokensAsync(context.Ticket);
}
// When rolling tokens are disabled, try to extend the expiration date
// of the existing token instead of returning a new refresh token
// with a new expiration date if sliding expiration was not disabled.
// If the operation fails, silently ignore the error and keep processing
// the request: this may indicate that a concurrent refresh token request
// already updated the expiration date associated with the refresh token.
if (!options.UseRollingTokens && options.UseSlidingExpiration)
{
await TryExtendTokenAsync(token, context.Ticket, options);
// When rolling tokens are disabled, try to extend the expiration date
// of the existing token instead of returning a new refresh token
// with a new expiration date if sliding expiration was not disabled.
// If the operation fails, silently ignore the error and keep processing
// the request: this may indicate that a concurrent refresh token request
// already updated the expiration date associated with the refresh token.
if (!options.UseRollingTokens && options.UseSlidingExpiration)
{
await TryExtendTokenAsync(token, context.Ticket, options);
}
}
}
}
@ -174,7 +169,8 @@ namespace OpenIddict.Server
// If no authorization was explicitly attached to the authentication ticket,
// create an ad hoc authorization if an authorization code or a refresh token
// is going to be returned to the client application as part of the response.
if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) &&
if (!options.DisableAuthorizationStorage &&
!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) &&
(context.IncludeAuthorizationCode || context.IncludeRefreshToken))
{
await CreateAuthorizationAsync(context.Ticket, options, context.Request);

20
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -341,6 +341,16 @@ namespace Microsoft.Extensions.DependencyInjection
public OpenIddictServerBuilder AllowRefreshTokenFlow()
=> Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken));
/// <summary>
/// Disables authorization storage so that ad-hoc authorizations are
/// not created when an authorization code or refresh token is issued
/// and can't be revoked to prevent associated tokens from being used.
/// Using this option is generally NOT recommended.
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder DisableAuthorizationStorage()
=> Configure(options => options.DisableAuthorizationStorage = true);
/// <summary>
/// Disables the configuration endpoint.
/// </summary>
@ -364,7 +374,7 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Disables sliding expiration. When using this option, refresh tokens
/// are issued with a fixed expiration date: when it expires, a complete
/// are issued with a fixed expiration date: when they expire, a complete
/// authorization flow must be started to retrieve a new refresh token.
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -372,13 +382,13 @@ namespace Microsoft.Extensions.DependencyInjection
=> Configure(options => options.UseSlidingExpiration = false);
/// <summary>
/// Disables token revocation, so that authorization code and
/// Disables token storage, so that authorization code and
/// refresh tokens are never stored and cannot be revoked.
/// Using this option is generally not recommended.
/// Using this option is generally NOT recommended.
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder DisableTokenRevocation()
=> Configure(options => options.DisableTokenRevocation = true);
public OpenIddictServerBuilder DisableTokenStorage()
=> Configure(options => options.DisableTokenStorage = true);
/// <summary>
/// Enables the authorization endpoint.

9
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -37,11 +37,10 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAddScoped<OpenIddictServerHandler>();
builder.Services.TryAddScoped(provider =>
{
InvalidOperationException CreateException()
=> new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the server handler.")
.Append("To register the OpenIddict core services, use 'services.AddOpenIddict().AddCore()'.")
.ToString());
InvalidOperationException CreateException() => new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the server handler.")
.Append("To register the OpenIddict core services, use 'services.AddOpenIddict().AddCore()'.")
.ToString());
return new OpenIddictServerProvider(
provider.GetRequiredService<ILogger<OpenIddictServerProvider>>(),

11
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -61,11 +61,18 @@ namespace OpenIddict.Server
};
/// <summary>
/// Gets or sets a boolean indicating whether token revocation should be disabled.
/// Gets or sets a boolean indicating whether authorization storage should be disabled.
/// When disabled, ad-hoc authorizations are not created when an authorization code or
/// refresh token is issued and can't be revoked to prevent associated tokens from being used.
/// </summary>
public bool DisableAuthorizationStorage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether token storage should be disabled.
/// When disabled, authorization code and refresh tokens are not stored
/// and cannot be revoked. Using this option is generally not recommended.
/// </summary>
public bool DisableTokenRevocation { get; set; }
public bool DisableTokenStorage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether request caching should be enabled.

44
test/OpenIddict.Server.Tests/Internal/OpenIddictServerInitializerTests.cs

@ -5,6 +5,7 @@
*/
using System;
using System.Text;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
@ -127,8 +128,7 @@ namespace OpenIddict.Server.Tests
return client.GetAsync("/");
});
Assert.Equal("The authorization endpoint must be enabled to use " +
"the authorization code and implicit flows.", exception.Message);
Assert.Equal("The authorization endpoint must be enabled to use the authorization code and implicit flows.", exception.Message);
}
[Theory]
@ -159,7 +159,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenTokenRevocationIsDisabled()
public async Task PostConfigure_ThrowsAnExceptionWhenTokenStorageIsDisabled()
{
// Arrange
var server = CreateAuthorizationServer(builder =>
@ -167,7 +167,7 @@ namespace OpenIddict.Server.Tests
builder.EnableAuthorizationEndpoint("/connect/authorize")
.EnableRevocationEndpoint("/connect/revocation")
.AllowImplicitFlow()
.DisableTokenRevocation();
.DisableTokenStorage();
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -178,18 +178,18 @@ namespace OpenIddict.Server.Tests
return client.GetAsync("/");
});
Assert.Equal("The revocation endpoint cannot be enabled when token revocation is disabled.", exception.Message);
Assert.Equal("The revocation endpoint cannot be enabled when token storage is disabled.", exception.Message);
}
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenUsingReferenceTokensWithTokenRevocationDisabled()
public async Task PostConfigure_ThrowsAnExceptionWhenUsingReferenceTokensWithTokenStorageDisabled()
{
// Arrange
var server = CreateAuthorizationServer(builder =>
{
builder.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.DisableTokenRevocation()
.DisableTokenStorage()
.UseReferenceTokens();
});
@ -201,18 +201,18 @@ namespace OpenIddict.Server.Tests
return client.GetAsync("/");
});
Assert.Equal("Reference tokens cannot be used when disabling token revocation.", exception.Message);
Assert.Equal("Reference tokens cannot be used when disabling token storage.", exception.Message);
}
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenRevocationDisabled()
public async Task PostConfigure_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenStorageDisabled()
{
// Arrange
var server = CreateAuthorizationServer(builder =>
{
builder.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.DisableTokenRevocation();
.DisableTokenStorage();
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -224,7 +224,7 @@ namespace OpenIddict.Server.Tests
});
Assert.Equal("Sliding expiration must be disabled when turning off " +
"token revocation if rolling tokens are not used.", exception.Message);
"token storage if rolling tokens are not used.", exception.Message);
}
[Fact]
@ -270,11 +270,12 @@ namespace OpenIddict.Server.Tests
return client.GetAsync("/");
});
Assert.Equal(
"At least one signing key must be registered when using JWT as the access token format. " +
"Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' " +
"or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.", exception.Message);
Assert.Equal(new StringBuilder()
.AppendLine("At least one signing key must be registered when using JWT as the access token format.")
.Append("Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' ")
.Append("or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call ")
.Append("'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.")
.ToString(), exception.Message);
}
[Fact]
@ -295,11 +296,12 @@ namespace OpenIddict.Server.Tests
return client.GetAsync("/");
});
Assert.Equal(
"At least one asymmetric signing key must be registered when enabling the implicit flow. " +
"Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' " +
"or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call " +
"'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.", exception.Message);
Assert.Equal(new StringBuilder()
.AppendLine("At least one asymmetric signing key must be registered when enabling the implicit flow.")
.Append("Consider registering a X.509 certificate using 'services.AddOpenIddict().AddSigningCertificate()' ")
.Append("or 'services.AddOpenIddict().AddDevelopmentSigningCertificate()' or call ")
.Append("'services.AddOpenIddict().AddEphemeralSigningKey()' to use an ephemeral key.")
.ToString(), exception.Message);
}
private static TestServer CreateAuthorizationServer(Action<OpenIddictServerBuilder> configuration = null)

562
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs

@ -736,7 +736,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenRevocationIsDisabled()
public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenStorageIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -777,7 +777,7 @@ namespace OpenIddict.Server.Tests
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
builder.DisableSlidingExpiration();
});
@ -797,7 +797,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenRevocationIsDisabled()
public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenStorageIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -837,7 +837,7 @@ namespace OpenIddict.Server.Tests
builder.Configure(options => options.RefreshTokenFormat = format.Object);
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
builder.DisableSlidingExpiration();
});
@ -1682,6 +1682,546 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_AuthorizationAssociatedWithCodeIsIgnoredWhenAuthorizationStorageIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.DisableAuthorizationStorage();
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task HandleTokenRequest_AuthorizationAssociatedWithRefreshTokenIsIgnoredWhenAuthorizationStorageIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var authorization = new OpenIddictAuthorization();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.DisableAuthorizationStorage();
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithAuthorizationCodeCannotBeFound()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The authorization associated with the authorization code is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithAuthorizationCodeIsInvalid()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var authorization = new OpenIddictAuthorization();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The authorization associated with the authorization code is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithRefreshTokenCannotBeFound()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("60FFF7EA-F98E-437B-937E-5073CC313103951EFBA23A56"));
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The authorization associated with the refresh token is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithRefreshTokenIsInvalid()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var authorization = new OpenIddictAuthorization();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("60FFF7EA-F98E-437B-937E-5073CC313103951EFBA23A56"));
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The authorization associated with the refresh token is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()), Times.Once());
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
@ -1730,6 +2270,9 @@ namespace OpenIddict.Server.Tests
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
});
var server = CreateAuthorizationServer(builder =>
@ -1772,6 +2315,17 @@ namespace OpenIddict.Server.Tests
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(CreateAuthorizationManager(instance =>
{
var authorization = new OpenIddictAuthorization();
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
builder.Services.AddSingleton(manager);

289
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs

@ -441,6 +441,295 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleIntrospectionRequest_AuthorizationIsIgnoredWhenAuthorizationStorageIsDisabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetAudiences("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
.Returns(ticket);
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Confidential));
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("2YotnFZFEjr1zCsicMWpAA"));
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.DisableAuthorizationStorage();
builder.UseReferenceTokens();
builder.Configure(options => options.AccessTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI"
});
// Assert
Assert.Single(response.GetParameters());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationCannotBeFound()
{
// Arrange
var identity = new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetAudiences("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
.Returns(ticket);
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Confidential));
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("2YotnFZFEjr1zCsicMWpAA"));
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AccessTokenFormat = format.Object);
builder.UseReferenceTokens();
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI"
});
// Assert
Assert.Single(response.GetParameters());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationIsInvalid()
{
// Arrange
var identity = new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme);
ticket.SetAudiences("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AccessToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
.Returns(ticket);
var authorization = new OpenIddictAuthorization();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Confidential));
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("2YotnFZFEjr1zCsicMWpAA"));
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"));
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AccessTokenFormat = format.Object);
builder.UseReferenceTokens();
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI"
});
// Assert
Assert.Single(response.GetParameters());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsInvalid()
{

4
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs

@ -1791,7 +1791,7 @@ namespace OpenIddict.Server.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
builder.DisableSlidingExpiration();
});
@ -2195,7 +2195,7 @@ namespace OpenIddict.Server.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
builder.DisableSlidingExpiration();
});

101
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs

@ -765,7 +765,6 @@ namespace OpenIddict.Server.Tests
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
@ -809,6 +808,17 @@ namespace OpenIddict.Server.Tests
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateAuthorizationManager(instance =>
{
var authorization = new OpenIddictAuthorization();
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
@ -887,6 +897,17 @@ namespace OpenIddict.Server.Tests
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateAuthorizationManager(instance =>
{
var authorization = new OpenIddictAuthorization();
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
instance.Setup(mock => mock.IsValidAsync(authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
@ -902,6 +923,7 @@ namespace OpenIddict.Server.Tests
});
// Assert
Assert.NotNull(response.AccessToken);
Assert.Null(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Once());
@ -1188,6 +1210,75 @@ namespace OpenIddict.Server.Tests
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_AdHocAuthorizationIsNotCreatedWhenAuthorizationStorageIsDisabled()
{
// Arrange
var token = new OpenIddictToken();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.HasPermissionAsync(application,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>(OpenIddictConstants.ClientTypes.Public));
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.Returns(new ValueTask<string>("3E228451-1555-46F7-A471-951EFBA23A56"));
}));
builder.Services.AddSingleton(manager);
builder.DisableAuthorizationStorage();
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code,
});
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(It.IsAny<OpenIddictAuthorizationDescriptor>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task ProcessSigninResponse_CustomPublicParametersAreAddedToAuthorizationResponse()
{
@ -1351,10 +1442,10 @@ namespace OpenIddict.Server.Tests
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
options.Services.AddSingleton(CreateApplicationManager());
options.Services.AddSingleton(CreateAuthorizationManager());
options.Services.AddSingleton(CreateScopeManager());
options.Services.AddSingleton(CreateTokenManager());
options.Services.AddSingleton(CreateApplicationManager())
.AddSingleton(CreateAuthorizationManager())
.AddSingleton(CreateScopeManager())
.AddSingleton(CreateTokenManager());
})
.AddServer(options =>

22
test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

@ -296,6 +296,22 @@ namespace OpenIddict.Server.Tests
Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.GrantTypes);
}
[Fact]
public void DisableAuthorizationStorage_AuthorizationStorageIsDisabled()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.DisableAuthorizationStorage();
var options = GetOptions(services);
// Assert
Assert.True(options.DisableAuthorizationStorage);
}
[Fact]
public void DisableConfigurationEndpoint_ConfigurationEndpointIsDisabled()
{
@ -345,19 +361,19 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public void DisableTokenRevocation_TokenRevocationIsDisabled()
public void DisableTokenStorage_TokenStorageIsDisabled()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
var options = GetOptions(services);
// Assert
Assert.True(options.DisableTokenRevocation);
Assert.True(options.DisableTokenStorage);
}
[Fact]

Loading…
Cancel
Save