Browse Source

Backport the authorization changes to OpenIddict 1.x

pull/670/head
Kévin Chalet 8 years ago
parent
commit
74de96c88c
  1. 5
      OpenIddict.sln
  2. 2
      korebuild-lock.txt
  3. 4
      korebuild.json
  4. 108
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Exchange.cs
  5. 9
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
  6. 41
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Introspection.cs
  7. 2
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Revocation.cs
  8. 12
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Serialization.cs
  9. 84
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs
  10. 20
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  11. 40
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  12. 11
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  13. 562
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs
  14. 289
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs
  15. 4
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs
  16. 101
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs
  17. 194
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
  18. 28
      test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs

5
OpenIddict.sln

@ -10,7 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{19E887E7
build\key.snk = build\key.snk
build\packages.props = build\packages.props
build\repo.props = build\repo.props
build\tests.props = build\tests.props
build\version.props = build\version.props
EndProjectSection
EndProject
@ -56,9 +55,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Validation", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFrameworkCore.Models", "src\OpenIddict.EntityFrameworkCore.Models\OpenIddict.EntityFrameworkCore.Models.csproj", "{B5371534-4C33-41FA-B3D3-7D70D632DB15}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.MongoDb", "src\OpenIddict.MongoDb\OpenIddict.MongoDb.csproj", "{BACF1DD4-8390-48D4-BD9B-DA1EC00C1F98}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.MongoDb", "src\OpenIddict.MongoDb\OpenIddict.MongoDb.csproj", "{BACF1DD4-8390-48D4-BD9B-DA1EC00C1F98}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.MongoDb.Models", "src\OpenIddict.MongoDb.Models\OpenIddict.MongoDb.Models.csproj", "{14C55FB6-9626-4BDE-8961-3BE91DDD6418}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.MongoDb.Models", "src\OpenIddict.MongoDb.Models\OpenIddict.MongoDb.Models.csproj", "{14C55FB6-9626-4BDE-8961-3BE91DDD6418}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

2
korebuild-lock.txt

@ -0,0 +1,2 @@
version:2.1.0-rtm-15783
commithash:5fc2b2f607f542a2ffde11c19825e786fc1a3774

4
korebuild.json

@ -0,0 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.1/tools/korebuild.schema.json",
"channel": "release/2.1"
}

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

@ -295,6 +295,7 @@ namespace OpenIddict.Server
var options = (OpenIddictServerOptions) context.Options;
var logger = GetLogger(context.HttpContext.RequestServices);
var authorizationManager = GetAuthorizationManager(context.HttpContext.RequestServices);
var tokenManager = GetTokenManager(context.HttpContext.RequestServices);
if (context.Ticket != null)
@ -303,8 +304,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.
@ -317,51 +317,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, context.HttpContext);
// 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, context.HttpContext);
await TryRevokeTokensAsync(context.Ticket, context.HttpContext);
}
// 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, context.HttpContext);
await TryRevokeTokensAsync(context.Ticket, context.HttpContext);
await TryRevokeTokenAsync(token, context.HttpContext);
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

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

@ -92,8 +92,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 ||
@ -116,7 +116,7 @@ namespace OpenIddict.Server
ticket.Properties.ExpiresUtc = properties.ExpiresUtc;
}
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return null;
}
@ -232,6 +232,9 @@ namespace OpenIddict.Server
var logger = GetLogger(context.RequestServices);
var tokenManager = GetTokenManager(context.RequestServices);
Debug.Assert(!(options.DisableTokenStorage && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken ||
type == OpenIdConnectConstants.TokenUsages.AuthorizationCode ||
type == OpenIdConnectConstants.TokenUsages.RefreshToken,

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

@ -103,6 +103,7 @@ namespace OpenIddict.Server
var options = (OpenIddictServerOptions) context.Options;
var logger = GetLogger(context.HttpContext.RequestServices);
var authorizationManager = GetAuthorizationManager(context.HttpContext.RequestServices);
var tokenManager = GetTokenManager(context.HttpContext.RequestServices);
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
@ -145,24 +146,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

@ -26,7 +26,7 @@ namespace OpenIddict.Server
var logger = GetLogger(context.HttpContext.RequestServices);
var applicationManager = GetApplicationManager(context.HttpContext.RequestServices);
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;
}
@ -105,7 +105,7 @@ namespace OpenIddict.Server
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context)
{
var options = (OpenIddictServerOptions) context.Options;
if (options.DisableTokenRevocation)
if (options.DisableTokenStorage)
{
return;
}
@ -134,7 +134,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

@ -93,56 +93,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, context.HttpContext))
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.HttpContext))
{
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, context.HttpContext);
}
// 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, context.HttpContext);
}
// 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, context.HttpContext, 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, context.HttpContext, options);
}
}
}
}
@ -150,7 +145,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.HttpContext, context.Request);

20
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -302,6 +302,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>
@ -325,7 +335,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>
@ -333,13 +343,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.

40
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -6,6 +6,7 @@
using System;
using System.Linq;
using System.Text;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
@ -156,40 +157,51 @@ namespace Microsoft.Extensions.DependencyInjection
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.");
}
if (options.UseSlidingExpiration && options.DisableTokenStorage && !options.UseRollingTokens)
{
throw new InvalidOperationException(
"Reference tokens cannot be used when configuring JWT as the access token format.");
"Sliding expiration must be disabled when turning off token storage if rolling tokens are not used.");
}
if (options.UseSlidingExpiration && options.DisableTokenRevocation && !options.UseRollingTokens)
if (options.AccessTokenHandler != null && options.SigningCredentials.Count == 0)
{
throw new InvalidOperationException("Sliding expiration must be disabled when turning off " +
"token revocation if rolling tokens are not used.");
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 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.

11
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -52,11 +52,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.

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

@ -738,7 +738,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenRevocationIsDisabled()
public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenStorageIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -779,7 +779,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();
});
@ -799,7 +799,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenRevocationIsDisabled()
public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenStorageIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -839,7 +839,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();
});
@ -1684,6 +1684,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)]
@ -1732,6 +2272,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 =>
@ -1774,6 +2317,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

@ -442,6 +442,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

@ -1793,7 +1793,7 @@ namespace OpenIddict.Server.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
builder.DisableSlidingExpiration();
});
@ -2197,7 +2197,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

@ -766,7 +766,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>>();
@ -810,6 +809,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();
@ -888,6 +898,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);
@ -903,6 +924,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());
@ -1189,6 +1211,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()
{
@ -1352,10 +1443,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 =>

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

@ -33,11 +33,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("OpenIddict", options.Value.Description.DisplayName);
Assert.Equal("OpenIddict", options.Description.DisplayName);
}
[Fact]
@ -52,11 +51,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AddEphemeralSigningKey();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(1, options.Value.SigningCredentials.Count);
Assert.Equal(1, options.SigningCredentials.Count);
}
[Theory]
@ -79,9 +77,8 @@ namespace OpenIddict.Server.Tests
// Act
builder.AddEphemeralSigningKey(algorithm);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var credentials = options.Value.SigningCredentials[0];
var options = GetOptions(services);
var credentials = options.SigningCredentials[0];
// Assert
Assert.Equal(algorithm, credentials.Algorithm);
@ -111,11 +108,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AddSigningKey(key);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Same(key, options.Value.SigningCredentials[0].Key);
Assert.Same(key, options.SigningCredentials[0].Key);
}
[Fact]
@ -133,11 +129,10 @@ namespace OpenIddict.Server.Tests
resource: "OpenIddict.Server.Tests.Certificate.pfx",
password: "OpenIddict");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key);
Assert.IsType(typeof(X509SecurityKey), options.SigningCredentials[0].Key);
}
[Fact]
@ -152,11 +147,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AllowAuthorizationCodeFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes);
Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.GrantTypes);
}
[Fact]
@ -171,11 +165,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AllowClientCredentialsFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes);
Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.GrantTypes);
}
[Fact]
@ -190,11 +183,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes);
Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.GrantTypes);
}
[Fact]
@ -209,11 +201,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AllowImplicitFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes);
Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.GrantTypes);
}
[Fact]
@ -228,11 +219,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.AllowPasswordFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes);
Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.GrantTypes);
}
[Fact]
@ -247,11 +237,26 @@ namespace OpenIddict.Server.Tests
// Act
builder.AllowRefreshTokenFlow();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes);
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]
@ -266,11 +271,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.DisableConfigurationEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath);
Assert.Equal(PathString.Empty, options.ConfigurationEndpointPath);
}
[Fact]
@ -285,11 +289,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.DisableCryptographyEndpoint();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath);
Assert.Equal(PathString.Empty, options.CryptographyEndpointPath);
}
[Fact]
@ -304,15 +307,14 @@ namespace OpenIddict.Server.Tests
// Act
builder.DisableSlidingExpiration();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.False(options.Value.UseSlidingExpiration);
Assert.False(options.UseSlidingExpiration);
}
[Fact]
public void DisableTokenRevocation_TokenRevocationIsDisabled()
public void DisableTokenStorage_TokenStorageIsDisabled()
{
// Arrange
var services = new ServiceCollection();
@ -321,13 +323,12 @@ namespace OpenIddict.Server.Tests
var builder = CreateBuilder(services);
// Act
builder.DisableTokenRevocation();
builder.DisableTokenStorage();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.True(options.Value.DisableTokenRevocation);
Assert.True(options.DisableTokenStorage);
}
[Fact]
@ -342,11 +343,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableAuthorizationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath);
Assert.Equal("/endpoint-path", options.AuthorizationEndpointPath);
}
[Fact]
@ -361,11 +361,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableIntrospectionEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath);
Assert.Equal("/endpoint-path", options.IntrospectionEndpointPath);
}
[Fact]
@ -380,11 +379,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableLogoutEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath);
Assert.Equal("/endpoint-path", options.LogoutEndpointPath);
}
[Fact]
@ -399,11 +397,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableRequestCaching();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.True(options.Value.EnableRequestCaching);
Assert.True(options.EnableRequestCaching);
}
[Fact]
@ -418,11 +415,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableRevocationEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath);
Assert.Equal("/endpoint-path", options.RevocationEndpointPath);
}
[Fact]
@ -437,11 +433,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableScopeValidation();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.True(options.Value.EnableScopeValidation);
Assert.True(options.EnableScopeValidation);
}
[Fact]
@ -456,11 +451,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableTokenEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath);
Assert.Equal("/endpoint-path", options.TokenEndpointPath);
}
[Fact]
@ -475,11 +469,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.EnableUserinfoEndpoint("/endpoint-path");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath);
Assert.Equal("/endpoint-path", options.UserinfoEndpointPath);
}
[Fact]
@ -494,11 +487,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.RequireClientIdentification();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.True(options.Value.RequireClientIdentification);
Assert.True(options.RequireClientIdentification);
}
[Fact]
@ -513,11 +505,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime);
Assert.Equal(TimeSpan.FromMinutes(42), options.AccessTokenLifetime);
}
[Fact]
@ -532,11 +523,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime);
Assert.Equal(TimeSpan.FromMinutes(42), options.AuthorizationCodeLifetime);
}
[Fact]
@ -551,11 +541,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime);
Assert.Equal(TimeSpan.FromMinutes(42), options.IdentityTokenLifetime);
}
[Fact]
@ -570,11 +559,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime);
Assert.Equal(TimeSpan.FromMinutes(42), options.RefreshTokenLifetime);
}
[Fact]
@ -589,11 +577,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.SetIssuer(new Uri("http://www.fabrikam.com/"));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Value.Issuer);
Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Issuer);
}
[Fact]
@ -624,12 +611,11 @@ namespace OpenIddict.Server.Tests
// Act
builder.RegisterClaims("custom_claim_1", "custom_claim_2");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains("custom_claim_1", options.Value.Claims);
Assert.Contains("custom_claim_2", options.Value.Claims);
Assert.Contains("custom_claim_1", options.Claims);
Assert.Contains("custom_claim_2", options.Claims);
}
[Fact]
@ -644,12 +630,11 @@ namespace OpenIddict.Server.Tests
// Act
builder.RegisterScopes("custom_scope_1", "custom_scope_2");
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.Contains("custom_scope_1", options.Value.Scopes);
Assert.Contains("custom_scope_2", options.Value.Scopes);
Assert.Contains("custom_scope_1", options.Scopes);
Assert.Contains("custom_scope_2", options.Scopes);
}
[Fact]
@ -664,11 +649,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider());
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider);
Assert.IsType(typeof(EphemeralDataProtectionProvider), options.DataProtectionProvider);
}
[Fact]
@ -683,11 +667,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.UseJsonWebTokens();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.IsType<JwtSecurityTokenHandler>(options.Value.AccessTokenHandler);
Assert.IsType<JwtSecurityTokenHandler>(options.AccessTokenHandler);
}
[Fact]
@ -702,11 +685,10 @@ namespace OpenIddict.Server.Tests
// Act
builder.UseReferenceTokens();
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<OpenIddictServerOptions>>();
var options = GetOptions(services);
// Assert
Assert.True(options.Value.UseReferenceTokens);
Assert.True(options.UseReferenceTokens);
}
private static IServiceCollection CreateServices()

28
test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs

@ -6,6 +6,7 @@
using System;
using System.Reflection;
using System.Text;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder.Internal;
@ -104,7 +105,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public void UseOpenIddictServer_ThrowsAnExceptionWhenTokenRevocationIsDisabled()
public void UseOpenIddictServer_ThrowsAnExceptionWhenTokenStorageIsDisabled()
{
// Arrange
var services = new ServiceCollection();
@ -122,18 +123,18 @@ namespace OpenIddict.Server.Tests
.EnableAuthorizationEndpoint("/connect/authorize")
.EnableRevocationEndpoint("/connect/revocation")
.AllowImplicitFlow()
.DisableTokenRevocation();
.DisableTokenStorage();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddictServer());
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 void UseOpenIddictServer_ThrowsAnExceptionWhenUsingReferenceTokensWithTokenRevocationDisabled()
public void UseOpenIddictServer_ThrowsAnExceptionWhenUsingReferenceTokensWithTokenStorageDisabled()
{
// Arrange
var services = new ServiceCollection();
@ -151,7 +152,7 @@ namespace OpenIddict.Server.Tests
.AddServer()
.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.DisableTokenRevocation()
.DisableTokenStorage()
.UseReferenceTokens();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
@ -159,7 +160,7 @@ namespace OpenIddict.Server.Tests
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddictServer());
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]
@ -193,7 +194,7 @@ namespace OpenIddict.Server.Tests
}
[Fact]
public void UseOpenIddictServer_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenRevocationDisabled()
public void UseOpenIddictServer_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenStorageDisabled()
{
// Arrange
var services = new ServiceCollection();
@ -211,7 +212,7 @@ namespace OpenIddict.Server.Tests
.AddServer()
.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.DisableTokenRevocation();
.DisableTokenStorage();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
@ -219,7 +220,7 @@ namespace OpenIddict.Server.Tests
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddictServer());
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]
@ -246,9 +247,12 @@ namespace OpenIddict.Server.Tests
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddictServer());
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 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);
}
[Fact]

Loading…
Cancel
Save