diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
index f6e4981b..5a64369f 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
@@ -110,7 +110,7 @@ namespace OpenIddict.Abstractions
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
///
- Task> FindAsync([NotNull] string subject, [NotNull] string client, [NotNull] string status, CancellationToken cancellationToken);
+ Task> FindAsync([NotNull] string subject, [NotNull] string client, [NotNull] string status, CancellationToken cancellationToken = default);
///
/// Retrieves the authorizations matching the specified parameters.
@@ -357,7 +357,7 @@ namespace OpenIddict.Abstractions
Task PopulateAsync([NotNull] object authorization, [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default);
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
+ /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
index 3cd9825b..77324e8a 100644
--- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
@@ -279,7 +279,7 @@ namespace OpenIddict.Abstractions
[CanBeNull] TState state, CancellationToken cancellationToken);
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
+ /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
index 0306a2c6..ee9dbf30 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
@@ -90,7 +90,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation.
///
public virtual Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken = default)
- => CreateAsync(application, /* secret: */ null, cancellationToken);
+ => CreateAsync(application, secret: null, cancellationToken);
///
/// Creates a new application.
@@ -181,7 +181,7 @@ namespace OpenIddict.Core
var secret = await Store.GetClientSecretAsync(application, cancellationToken);
if (!string.IsNullOrEmpty(secret))
{
- await Store.SetClientSecretAsync(application, /* secret: */ null, cancellationToken);
+ await Store.SetClientSecretAsync(application, secret: null, cancellationToken);
await CreateAsync(application, secret, cancellationToken);
}
else
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index eed081b8..1daa77d4 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -887,7 +887,7 @@ namespace OpenIddict.Core
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
+ /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
index 96e27421..b0a76f9b 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
@@ -211,6 +211,11 @@ namespace OpenIddict.Core
public virtual async Task> FindByNamesAsync(
ImmutableArray names, CancellationToken cancellationToken = default)
{
+ if (names.IsDefaultOrEmpty)
+ {
+ return ImmutableArray.Create();
+ }
+
if (names.Any(name => string.IsNullOrEmpty(name)))
{
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
index 95b55f3d..6acf5468 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
@@ -699,7 +699,7 @@ namespace OpenIddict.EntityFramework
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
+ /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
index 243e0883..6607c226 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
@@ -752,7 +752,7 @@ namespace OpenIddict.EntityFrameworkCore
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
+ /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
index dcd92357..0da73d37 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
@@ -617,7 +617,7 @@ namespace OpenIddict.MongoDb
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
+ /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs
index 3787e61a..fc5a52ea 100644
--- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs
+++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs
@@ -15,8 +15,6 @@ using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.WebUtilities;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
@@ -479,11 +477,7 @@ namespace OpenIddict.Server.Internal
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.AuthorizationRequest + context.Request.RequestId;
- await options.Cache.SetAsync(key, stream.ToArray(), new DistributedCacheEntryOptions
- {
- AbsoluteExpiration = context.Options.SystemClock.UtcNow + TimeSpan.FromMinutes(30),
- SlidingExpiration = TimeSpan.FromMinutes(10)
- });
+ await options.Cache.SetAsync(key, stream.ToArray(), options.RequestCachingPolicy);
// Create a new authorization request containing only the request_id parameter.
var address = QueryHelpers.AddQueryString(
diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
index f76ee45b..d038223d 100644
--- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
+++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
@@ -591,8 +591,8 @@ namespace OpenIddict.Server.Internal
if (property.Key.EndsWith(OpenIddictConstants.PropertyTypes.Boolean))
{
var name = property.Key.Substring(
- /* index: */ 0,
- /* length: */ property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.Boolean));
+ startIndex: 0,
+ length: property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.Boolean));
bool value;
@@ -615,8 +615,8 @@ namespace OpenIddict.Server.Internal
else if (property.Key.EndsWith(OpenIddictConstants.PropertyTypes.Integer))
{
var name = property.Key.Substring(
- /* index: */ 0,
- /* length: */ property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.Integer));
+ startIndex: 0,
+ length: property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.Integer));
long value;
@@ -639,8 +639,8 @@ namespace OpenIddict.Server.Internal
else if (property.Key.EndsWith(OpenIddictConstants.PropertyTypes.Json))
{
var name = property.Key.Substring(
- /* index: */ 0,
- /* length: */ property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.Json));
+ startIndex: 0,
+ length: property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.Json));
if (request.IsAuthorizationRequest() || request.IsLogoutRequest())
{
@@ -671,8 +671,8 @@ namespace OpenIddict.Server.Internal
else if (property.Key.EndsWith(OpenIddictConstants.PropertyTypes.String))
{
var name = property.Key.Substring(
- /* index: */ 0,
- /* length: */ property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.String));
+ startIndex: 0,
+ length: property.Key.LastIndexOf(OpenIddictConstants.PropertyTypes.String));
yield return Tuple.Create(property.Key, name, new OpenIdConnectParameter(property.Value));
}
diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs
index 40ab52ef..228bd287 100644
--- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs
+++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs
@@ -12,8 +12,6 @@ using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.WebUtilities;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
@@ -196,11 +194,7 @@ namespace OpenIddict.Server.Internal
// to avoid collisions with the other types of cached requests.
var key = OpenIddictConstants.Environment.LogoutRequest + context.Request.RequestId;
- await options.Cache.SetAsync(key, stream.ToArray(), new DistributedCacheEntryOptions
- {
- AbsoluteExpiration = context.Options.SystemClock.UtcNow + TimeSpan.FromMinutes(30),
- SlidingExpiration = TimeSpan.FromMinutes(10)
- });
+ await options.Cache.SetAsync(key, stream.ToArray(), options.RequestCachingPolicy);
// Create a new logout request containing only the request_id parameter.
var address = QueryHelpers.AddQueryString(
diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
index 09bd923e..9754a454 100644
--- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs
+++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
@@ -19,6 +19,7 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Caching.Distributed;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Server;
@@ -699,6 +700,23 @@ namespace Microsoft.Extensions.DependencyInjection
public OpenIddictServerBuilder SetRefreshTokenLifetime(TimeSpan lifetime)
=> Configure(options => options.RefreshTokenLifetime = lifetime);
+ ///
+ /// Sets the caching policy used to determine how long the authorization and
+ /// end session requests should be cached by the distributed cache implementation.
+ /// Note: the specified policy is only used when request caching is explicitly enabled.
+ ///
+ /// The request caching policy.
+ /// The .
+ public OpenIddictServerBuilder SetRequestCachingPolicy([NotNull] DistributedCacheEntryOptions policy)
+ {
+ if (policy == null)
+ {
+ throw new ArgumentNullException(nameof(policy));
+ }
+
+ return Configure(options => options.RequestCachingPolicy = policy);
+ }
+
///
/// Sets the issuer address, which is used as the base address
/// for the endpoint URIs returned from the discovery endpoint.
diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs
index 80e29138..64c319ce 100644
--- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs
@@ -182,6 +182,11 @@ namespace Microsoft.Extensions.DependencyInjection
"The token endpoint must be enabled to use the authorization code, client credentials, password and refresh token flows.");
}
+ if (options.EnableRequestCaching && options.RequestCachingPolicy == null)
+ {
+ throw new InvalidOperationException("A caching policy must be specified when enabling request caching.");
+ }
+
if (options.RevocationEndpointPath.HasValue && options.DisableTokenStorage)
{
throw new InvalidOperationException("The revocation endpoint cannot be enabled when token storage is disabled.");
diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs
index 162052da..e94ad6ad 100644
--- a/src/OpenIddict.Server/OpenIddictServerOptions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs
@@ -112,6 +112,16 @@ namespace OpenIddict.Server
///
public RandomNumberGenerator RandomNumberGenerator { get; set; } = RandomNumberGenerator.Create();
+ ///
+ /// Gets or sets the caching policy used to determine how long the authorization
+ /// and end session requests should be cached by the distributed cache implementation.
+ ///
+ public DistributedCacheEntryOptions RequestCachingPolicy { get; set; } = new DistributedCacheEntryOptions
+ {
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
+ SlidingExpiration = TimeSpan.FromMinutes(30)
+ };
+
///
/// Gets the OAuth2/OpenID Connect scopes enabled for this application.
///
diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs
index 4bef3c37..83b5295d 100644
--- a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs
+++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs
@@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
+using System;
using System.Collections.Immutable;
using System.IO;
using System.Net.Http;
@@ -828,6 +829,12 @@ namespace OpenIddict.Server.Internal.Tests
builder.EnableRequestCaching();
+ builder.SetRequestCachingPolicy(new DistributedCacheEntryOptions
+ {
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(42),
+ SlidingExpiration = TimeSpan.FromSeconds(42)
+ });
+
builder.Configure(options => options.RandomNumberGenerator = generator.Object);
});
@@ -850,7 +857,9 @@ namespace OpenIddict.Server.Internal.Tests
cache.Verify(mock => mock.SetAsync(
OpenIddictConstants.Environment.AuthorizationRequest + identifier,
It.IsAny(),
- It.IsAny()), Times.Once());
+ It.Is(options =>
+ options.AbsoluteExpirationRelativeToNow == TimeSpan.FromDays(42) &&
+ options.SlidingExpiration == TimeSpan.FromSeconds(42))), Times.Once());
generator.Verify(mock => mock.GetBytes(It.Is(bytes => bytes.Length == 256 / 8)), Times.Once());
}
diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs
index fcb77b17..7430d376 100644
--- a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs
+++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs
@@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
+using System;
using System.Collections.Immutable;
using System.Net.Http;
using System.Security.Cryptography;
@@ -136,6 +137,12 @@ namespace OpenIddict.Server.Internal.Tests
builder.EnableRequestCaching();
+ builder.SetRequestCachingPolicy(new DistributedCacheEntryOptions
+ {
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(42),
+ SlidingExpiration = TimeSpan.FromSeconds(42)
+ });
+
builder.Configure(options => options.RandomNumberGenerator = generator.Object);
});
@@ -156,7 +163,9 @@ namespace OpenIddict.Server.Internal.Tests
cache.Verify(mock => mock.SetAsync(
OpenIddictConstants.Environment.LogoutRequest + identifier,
It.IsAny(),
- It.IsAny()), Times.Once());
+ It.Is(options =>
+ options.AbsoluteExpirationRelativeToNow == TimeSpan.FromDays(42) &&
+ options.SlidingExpiration == TimeSpan.FromSeconds(42))), Times.Once());
generator.Verify(mock => mock.GetBytes(It.Is(bytes => bytes.Length == 256 / 8)), Times.Once());
}
diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
index 96a28380..91740620 100644
--- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
+++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
@@ -630,6 +631,54 @@ namespace OpenIddict.Server.Tests
Assert.Equal(TimeSpan.FromMinutes(42), options.RefreshTokenLifetime);
}
+ [Fact]
+ public void SetRequestCachingPolicy_ThrowsAnExceptionForNullPolicy()
+ {
+ // Arrange
+ var services = CreateServices();
+ var builder = CreateBuilder(services);
+
+ // Act and assert
+ var exception = Assert.Throws(() => builder.SetRequestCachingPolicy(null));
+
+ Assert.Equal("policy", exception.ParamName);
+ }
+
+ [Fact]
+ public void SetRequestCachingPolicy_PolicyIsUpdated()
+ {
+ // Arrange
+ var services = CreateServices();
+ var builder = CreateBuilder(services);
+
+ var policy = new DistributedCacheEntryOptions
+ {
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(42),
+ SlidingExpiration = TimeSpan.FromSeconds(42)
+ };
+
+ // Act
+ builder.SetRequestCachingPolicy(policy);
+
+ var options = GetOptions(services);
+
+ // Assert
+ Assert.Same(policy, options.RequestCachingPolicy);
+ }
+
+ [Fact]
+ public void SetIssuer_ThrowsAnExceptionForNullIssuer()
+ {
+ // Arrange
+ var services = CreateServices();
+ var builder = CreateBuilder(services);
+
+ // Act and assert
+ var exception = Assert.Throws(() => builder.SetIssuer(null));
+
+ Assert.Equal("address", exception.ParamName);
+ }
+
[Fact]
public void SetIssuer_AddressIsReplaced()
{
diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs
index 3cae4e9e..8e1183ee 100644
--- a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs
+++ b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs
@@ -269,6 +269,35 @@ namespace OpenIddict.Server.Tests
"client credentials, password and refresh token flows.", exception.Message);
}
+ [Fact]
+ public void UseOpenIddictServer_ThrowsAnExceptionWhenCachingPolicyIsNullAndRequestCachingEnabled()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+
+ services.AddOpenIddict()
+ .AddCore(options =>
+ {
+ options.SetDefaultApplicationEntity()
+ .SetDefaultAuthorizationEntity()
+ .SetDefaultScopeEntity()
+ .SetDefaultTokenEntity();
+ })
+
+ .AddServer()
+ .EnableAuthorizationEndpoint("/connect/authorize")
+ .AllowImplicitFlow()
+ .EnableRequestCaching()
+ .Configure(options => options.RequestCachingPolicy = null);
+
+ var builder = new ApplicationBuilder(services.BuildServiceProvider());
+
+ // Act and assert
+ var exception = Assert.Throws(() => builder.UseOpenIddictServer());
+
+ Assert.Equal("A caching policy must be specified when enabling request caching.", exception.Message);
+ }
+
[Fact]
public void UseOpenIddictServer_ThrowsAnExceptionWhenTokenStorageIsDisabled()
{