From cb05ebc769e1b70221734e56d5337ae5f3c96dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Sat, 6 Jan 2018 03:37:22 +0100 Subject: [PATCH 1/2] Introduce scope permissions, add opt-in scope validation support and rework existing permissions --- .../Managers/OpenIddictApplicationManager.cs | 105 ++++++++++- .../Managers/OpenIddictScopeManager.cs | 105 +++++++++++ src/OpenIddict.Core/OpenIddictConstants.cs | 1 + .../Stores/IOpenIddictScopeStore.cs | 22 +++ .../Stores/OpenIddictScopeStore.cs | 49 +++++ src/OpenIddict/OpenIddictExtensions.cs | 16 ++ src/OpenIddict/OpenIddictOptions.cs | 6 + .../OpenIddictProvider.Authentication.cs | 40 ++++ src/OpenIddict/OpenIddictProvider.Exchange.cs | 42 +++++ .../OpenIddictProviderTests.Authentication.cs | 171 ++++++++++++++++++ .../OpenIddictProviderTests.Exchange.cs | 153 ++++++++++++++++ .../OpenIddictProviderTests.cs | 12 ++ 12 files changed, 721 insertions(+), 1 deletion(-) diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index eefa5676..f5e35ebb 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -483,7 +483,110 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(application)); } - return (await Store.GetPermissionsAsync(application, cancellationToken)).Contains(permission, StringComparer.OrdinalIgnoreCase); + if (string.IsNullOrEmpty(permission)) + { + throw new ArgumentException("The permission name cannot be null or empty.", nameof(permission)); + } + + var permissions = await Store.GetPermissionsAsync(application, cancellationToken); + + bool HasPermission(string name) + { + if (permissions.IsEmpty) + { + return false; + } + + return permissions.Contains(name); + } + + bool HasEndpointPermission(string name) + { + // If the requested permission is an "endpoint" permission, return true if it has been + // explicitly granted OR if no other endpoint permission has been explicitly registered. + + if (permissions.IsEmpty || HasPermission(name)) + { + return true; + } + + if (permissions.Any(element => element.StartsWith(OpenIddictConstants.Permissions.Prefixes.Endpoint))) + { + return false; + } + + return true; + } + + bool HasGrantTypePermission(string name) + { + // If the requested permission is a "grant_type" permission, return true if it has been + // explicitly granted OR if the application is allowed to use the corresponding endpoint + // AND no other grant type permission has been explicitly registered. + + if (permissions.IsEmpty || HasPermission(name)) + { + return true; + } + + if (permissions.Any(element => element.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))) + { + return false; + } + + switch (permission) + { + case OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode: + return HasEndpointPermission(OpenIddictConstants.Permissions.Endpoints.Authorization) && + HasEndpointPermission(OpenIddictConstants.Permissions.Endpoints.Token); + + case OpenIddictConstants.Permissions.GrantTypes.Implicit: + return HasEndpointPermission(OpenIddictConstants.Permissions.Endpoints.Authorization); + + default: + case OpenIddictConstants.Permissions.GrantTypes.ClientCredentials: + case OpenIddictConstants.Permissions.GrantTypes.Password: + case OpenIddictConstants.Permissions.GrantTypes.RefreshToken: + return HasEndpointPermission(OpenIddictConstants.Permissions.Endpoints.Token); + } + } + + bool HasScopePermission(string name) + { + // If the requested permission is a "scope" permission, return true if it has been + // explicitly granted OR if the application is allowed to use the authorization or + // token endpoints AND no other scope permission has been explicitly registered. + + if (permissions.IsEmpty || HasPermission(name)) + { + return true; + } + + if (permissions.Any(element => element.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))) + { + return false; + } + + return HasEndpointPermission(OpenIddictConstants.Permissions.Endpoints.Authorization) || + HasEndpointPermission(OpenIddictConstants.Permissions.Endpoints.Token); + } + + if (permission.StartsWith(OpenIddictConstants.Permissions.Prefixes.Endpoint)) + { + return HasEndpointPermission(permission); + } + + if (permission.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) + { + return HasGrantTypePermission(permission); + } + + if (permission.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)) + { + return HasScopePermission(permission); + } + + return HasPermission(permission); } /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index 6796b4e8..cb996ea8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -162,6 +162,45 @@ namespace OpenIddict.Core return Store.FindByIdAsync(identifier, cancellationToken); } + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + public virtual Task FindByNameAsync([NotNull] string name, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("The scope name cannot be null or empty.", nameof(name)); + } + + return Store.FindByNameAsync(name, cancellationToken); + } + + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes corresponding to the specified names. + /// + public virtual Task> FindByNamesAsync( + ImmutableArray names, CancellationToken cancellationToken = default) + { + if (names.Any(name => string.IsNullOrEmpty(name))) + { + throw new ArgumentException("Scope names cannot be null or empty.", nameof(names)); + } + + return Store.FindByNamesAsync(names, cancellationToken); + } + /// /// Executes the specified query and returns the first element. /// @@ -202,6 +241,25 @@ namespace OpenIddict.Core return Store.GetAsync(query, state, cancellationToken); } + /// + /// Retrieves the description associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the description associated with the specified scope. + /// + public virtual Task GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken = default) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + return Store.GetDescriptionAsync(scope, cancellationToken); + } + /// /// Retrieves the unique identifier associated with a scope. /// @@ -221,6 +279,25 @@ namespace OpenIddict.Core return Store.GetIdAsync(scope, cancellationToken); } + /// + /// Retrieves the name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the name associated with the specified scope. + /// + public virtual Task GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken = default) + { + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + return Store.GetIdAsync(scope, cancellationToken); + } + /// /// Executes the specified query and returns all the corresponding elements. /// @@ -356,6 +433,34 @@ namespace OpenIddict.Core return results.ToImmutable(); } + /// + /// Validates the list of scopes to ensure they correspond to existing elements in the database. + /// + /// The scopes. + /// The that can be used to abort the operation. + /// true if the list of scopes is valid, false otherwise. + public virtual async Task ValidateScopesAsync(ImmutableArray scopes, CancellationToken cancellationToken = default) + { + if (scopes.Length == 0) + { + return true; + } + + async Task> GetScopesAsync() + { + var names = ImmutableHashSet.CreateBuilder(StringComparer.Ordinal); + + foreach (var scope in await FindByNamesAsync(scopes, cancellationToken)) + { + names.Add(await GetNameAsync(scope, cancellationToken)); + } + + return names.ToImmutable(); + } + + return (await GetScopesAsync()).IsSupersetOf(scopes); + } + /// /// Populates the scope using the specified descriptor. /// diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs index 201f1e37..5ff54b19 100644 --- a/src/OpenIddict.Core/OpenIddictConstants.cs +++ b/src/OpenIddict.Core/OpenIddictConstants.cs @@ -61,6 +61,7 @@ namespace OpenIddict.Core { public const string Endpoint = "ept:"; public const string GrantType = "gt:"; + public const string Scope = "scp:"; } } diff --git a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs index 8c0a48d7..7cc9b41c 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs @@ -73,6 +73,28 @@ namespace OpenIddict.Core /// Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken); + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + Task FindByNameAsync([NotNull] string name, CancellationToken cancellationToken); + + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes corresponding to the specified names. + /// + Task> FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken); + /// /// Executes the specified query and returns the first element. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs index 902d707b..31128731 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs @@ -96,6 +96,55 @@ namespace OpenIddict.Core return GetAsync((scopes, key) => Query(scopes, key), ConvertIdentifierFromString(identifier), cancellationToken); } + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + public virtual Task FindByNameAsync([NotNull] string name, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("The scope name cannot be null or empty.", nameof(name)); + } + + IQueryable Query(IQueryable scopes, string state) + => from scope in scopes + where scope.Name == state + select scope; + + return GetAsync((scopes, state) => Query(scopes, state), name, cancellationToken); + } + + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes corresponding to the specified names. + /// + public virtual Task> FindByNamesAsync( + ImmutableArray names, CancellationToken cancellationToken) + { + if (names.Any(name => string.IsNullOrEmpty(name))) + { + throw new ArgumentException("Scope names cannot be null or empty.", nameof(names)); + } + + IQueryable Query(IQueryable scopes, string[] values) + => from scope in scopes + where values.Contains(scope.Name) + select scope; + + return ListAsync((scopes, values) => Query(scopes, values), names.ToArray(), cancellationToken); + } + /// /// Executes the specified query and returns the first element. /// diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 1b40612f..996d7c6d 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -21,6 +21,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using OpenIddict; +using OpenIddict.Core; namespace Microsoft.Extensions.DependencyInjection { @@ -730,6 +731,21 @@ namespace Microsoft.Extensions.DependencyInjection return builder.Configure(options => options.UserinfoEndpointPath = path); } + /// + /// Rejects authorization and token requests that specify scopes that have not been + /// registered in the database using . + /// + /// The services builder used by OpenIddict to register new services. + public static OpenIddictBuilder ValidateScopes([NotNull] this OpenIddictBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Configure(options => options.ValidateScopes = true); + } + /// /// Makes client identification mandatory so that token and revocation /// requests that don't specify a client_id are automatically rejected. diff --git a/src/OpenIddict/OpenIddictOptions.cs b/src/OpenIddict/OpenIddictOptions.cs index ca1265eb..8a8734c7 100644 --- a/src/OpenIddict/OpenIddictOptions.cs +++ b/src/OpenIddict/OpenIddictOptions.cs @@ -73,6 +73,12 @@ namespace OpenIddict /// public RandomNumberGenerator RandomNumberGenerator { get; set; } = RandomNumberGenerator.Create(); + /// + /// Gets or sets a boolean indicating whether scopes that are not explicitly registered + /// in the database are automatically rejected. This option is not enabled by default. + /// + public bool ValidateScopes { get; set; } + /// /// Gets or sets a boolean determining whether client identification is required. /// Enabling this option requires registering a client application and sending a diff --git a/src/OpenIddict/OpenIddictProvider.Authentication.cs b/src/OpenIddict/OpenIddictProvider.Authentication.cs index 0b20247e..5f1691ab 100644 --- a/src/OpenIddict/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict/OpenIddictProvider.Authentication.cs @@ -5,6 +5,7 @@ */ using System; +using System.Collections.Immutable; using System.IO; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; @@ -165,6 +166,22 @@ namespace OpenIddict return; } + // If the corresponding option was enabled, reject the request if scopes can't be validated. + if (options.ValidateScopes && !await Scopes.ValidateScopesAsync( + context.Request.GetScopes() + .ToImmutableArray() + .Remove(OpenIdConnectConstants.Scopes.OfflineAccess) + .Remove(OpenIdConnectConstants.Scopes.OpenId))) + { + Logger.LogError("The authorization request was rejected because an unregistered scope was specified."); + + context.Reject( + error: OpenIdConnectConstants.Errors.InvalidRequest, + description: "The specified 'scope' parameter is not valid."); + + return; + } + // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) @@ -369,6 +386,29 @@ namespace OpenIddict return; } + foreach (var scope in context.Request.GetScopes()) + { + // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. + if (string.Equals(scope, OpenIdConnectConstants.Scopes.OfflineAccess, StringComparison.Ordinal) || + string.Equals(scope, OpenIdConnectConstants.Scopes.OpenId, StringComparison.Ordinal)) + { + continue; + } + + // Reject the request if the application is not allowed to use the iterated scope. + if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Prefixes.Scope + scope)) + { + Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + + "was not allowed to use the scope {Scope}.", context.ClientId, scope); + + context.Reject( + error: OpenIdConnectConstants.Errors.InvalidRequest, + description: "This client application is not allowed to use the specified scope."); + + return; + } + } + context.Validate(); } diff --git a/src/OpenIddict/OpenIddictProvider.Exchange.cs b/src/OpenIddict/OpenIddictProvider.Exchange.cs index adcd590c..34b10159 100644 --- a/src/OpenIddict/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict/OpenIddictProvider.Exchange.cs @@ -4,6 +4,8 @@ * the license and the contributors participating to this project. */ +using System; +using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; @@ -60,6 +62,22 @@ namespace OpenIddict return; } + // If the corresponding option was enabled, reject the request if scopes can't be validated. + if (options.ValidateScopes && !await Scopes.ValidateScopesAsync( + context.Request.GetScopes() + .ToImmutableArray() + .Remove(OpenIdConnectConstants.Scopes.OfflineAccess) + .Remove(OpenIdConnectConstants.Scopes.OpenId))) + { + Logger.LogError("The token request was rejected because an unregistered scope was specified."); + + context.Reject( + error: OpenIdConnectConstants.Errors.InvalidRequest, + description: "The specified 'scope' parameter is not valid."); + + return; + } + // Note: the OpenID Connect server middleware allows returning a refresh token with grant_type=client_credentials, // though it's usually not recommended by the OAuth2 specification. To encourage developers to make a new // grant_type=client_credentials request instead of using refresh tokens, OpenIddict uses a stricter policy @@ -226,6 +244,30 @@ namespace OpenIddict return; } + foreach (var scope in context.Request.GetScopes()) + { + // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. + if (string.Equals(scope, OpenIdConnectConstants.Scopes.OfflineAccess, StringComparison.Ordinal) || + string.Equals(scope, OpenIdConnectConstants.Scopes.OpenId, StringComparison.Ordinal)) + { + continue; + } + + // Reject the request if the application is not allowed to use the iterated scope. + if (!await Applications.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + scope)) + { + Logger.LogError("The token request was rejected because the application '{ClientId}' " + + "was not allowed to use the scope {Scope}.", context.ClientId, scope); + + context.Reject( + error: OpenIdConnectConstants.Errors.InvalidRequest, + description: "This client application is not allowed to use the specified scope."); + + return; + } + } + context.Validate(); } diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs index eecc7ebc..3115cc14 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using System.Collections.Immutable; using System.IO; using System.Security.Cryptography; using System.Threading; @@ -192,6 +193,105 @@ namespace OpenIddict.Tests Assert.Equal("The specified 'response_type' parameter is not allowed.", response.ErrorDescription); } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + { + // Arrange + var manager = CreateScopeManager(instance => + { + instance.Setup(mock => mock.ValidateScopesAsync( + It.Is>(scopes => scopes[0] == "unregistered_scope"), + It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + + builder.ValidateScopes(); + }); + + 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, + Scope = "unregistered_scope" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); + Assert.Equal("The specified 'scope' parameter is not valid.", response.ErrorDescription); + } + + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + { + // Arrange + var manager = CreateScopeManager(instance => + { + instance.Setup(mock => mock.ValidateScopesAsync( + It.Is>(scopes => scopes[0] == "registered_scope"), + It.IsAny())) + .ReturnsAsync(true); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(CreateApplicationManager(instance => + { + var application = new OpenIddictApplication(); + + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Public); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Implicit, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + "registered_scope", It.IsAny())) + .ReturnsAsync(true); + })); + + builder.Services.AddSingleton(manager); + + builder.ValidateScopes(); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = OpenIdConnectConstants.ResponseTypes.Token, + Scope = "registered_scope" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + } + [Fact] public async Task ValidateAuthorizationRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() { @@ -578,6 +678,77 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once()); } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Profile, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Email, It.IsAny())) + .ReturnsAsync(false); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + 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, + Scope = "openid offline_access profile email" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); + Assert.Equal("This client application is not allowed to use the specified scope.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.OpenId, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.OfflineAccess, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Profile, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Email, It.IsAny()), Times.Once()); + } + [Fact] public async Task HandleAuthorizationRequest_RequestIsPersistedInDistributedCache() { diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs index b2c0753c..61d095b1 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs @@ -101,6 +101,68 @@ namespace OpenIddict.Tests Assert.Equal("The mandatory 'redirect_uri' parameter is missing.", response.ErrorDescription); } + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + { + // Arrange + var server = CreateAuthorizationServer(options => + { + options.ValidateScopes(); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest + { + GrantType = OpenIdConnectConstants.GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "unregistered_scope" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); + Assert.Equal("The specified 'scope' parameter is not valid.", response.ErrorDescription); + } + + [Fact] + public async Task ValidateTokenRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + { + // Arrange + var manager = CreateScopeManager(instance => + { + instance.Setup(mock => mock.ValidateScopesAsync( + It.Is>(scopes => scopes[0] == "registered_scope"), + It.IsAny())) + .ReturnsAsync(true); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + builder.ValidateScopes(); + builder.RegisterScopes("registered_scope"); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest + { + GrantType = OpenIdConnectConstants.GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "registered_scope" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + } + [Fact] public async Task ValidateTokenRequest_ClientCredentialsRequestWithOfflineAccessScopeIsRejected() { @@ -550,6 +612,85 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); } + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(instance => + { + instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny())) + .ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential); + + instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Profile, It.IsAny())) + .ReturnsAsync(true); + + instance.Setup(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Email, It.IsAny())) + .ReturnsAsync(false); + + instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); + }); + + var server = CreateAuthorizationServer(builder => + { + builder.Services.AddSingleton(manager); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = OpenIdConnectConstants.GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "openid offline_access profile email" + }); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); + Assert.Equal("This client application is not allowed to use the specified scope.", response.ErrorDescription); + + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.OpenId, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.OfflineAccess, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Profile, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.HasPermissionAsync(application, + OpenIddictConstants.Permissions.Prefixes.Scope + + OpenIdConnectConstants.Scopes.Email, It.IsAny()), Times.Once()); + } + [Fact] public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenRevocationIsDisabled() { @@ -1166,6 +1307,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("481FCAC6-06BC-43EE-92DB-37A78AA09B59"); + + instance.Setup(mock => mock.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + instance.Setup(mock => mock.GetAuthorizationIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); @@ -1263,6 +1410,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + instance.Setup(mock => mock.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("481FCAC6-06BC-43EE-92DB-37A78AA09B59"); + + instance.Setup(mock => mock.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + instance.Setup(mock => mock.GetAuthorizationIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.cs index d13cf8dd..b8da921b 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.cs @@ -789,6 +789,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("481FCAC6-06BC-43EE-92DB-37A78AA09B59"); + + instance.Setup(mock => mock.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + instance.Setup(mock => mock.GetAuthorizationIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); @@ -864,6 +870,12 @@ namespace OpenIddict.Tests instance.Setup(mock => mock.GetIdAsync(tokens[0], It.IsAny())) .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + instance.Setup(mock => mock.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("481FCAC6-06BC-43EE-92DB-37A78AA09B59"); + + instance.Setup(mock => mock.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny())) .ReturnsAsync(false); From f503fb7e7a10a8dd86b27111515dda813cdc939c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Sun, 11 Feb 2018 05:50:02 +0100 Subject: [PATCH 2/2] Cache permissions, redirect_uris and post_logout_redirect_uris in memory --- src/OpenIddict.Core/OpenIddict.Core.csproj | 1 + .../Stores/OpenIddictApplicationStore.cs | 58 ++++++++++++++++++- .../Stores/OpenIddictAuthorizationStore.cs | 16 +++++ .../Stores/OpenIddictScopeStore.cs | 16 +++++ .../Stores/OpenIddictTokenStore.cs | 16 +++++ .../Stores/OpenIddictApplicationStore.cs | 20 ++++++- .../Stores/OpenIddictAuthorizationStore.cs | 20 ++++++- .../Stores/OpenIddictScopeStore.cs | 20 ++++++- .../Stores/OpenIddictTokenStore.cs | 20 ++++++- .../Stores/OpenIddictApplicationStore.cs | 20 ++++++- .../Stores/OpenIddictAuthorizationStore.cs | 20 ++++++- .../Stores/OpenIddictScopeStore.cs | 20 ++++++- .../Stores/OpenIddictTokenStore.cs | 20 ++++++- 13 files changed, 240 insertions(+), 27 deletions(-) diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index 36bcb9b4..d283cf18 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -19,6 +19,7 @@ + diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs index a42ea82d..715bf9a7 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Models; @@ -31,6 +32,21 @@ namespace OpenIddict.Core where TToken : OpenIddictToken, new() where TKey : IEquatable { + protected OpenIddictApplicationStore([NotNull] IMemoryCache cache) + { + if (cache == null) + { + throw new ArgumentNullException(nameof(cache)); + } + + Cache = cache; + } + + /// + /// Gets the memory cached associated with the current store. + /// + protected IMemoryCache Cache { get; } + /// /// Determines the number of applications that exist in the database. /// @@ -350,7 +366,19 @@ namespace OpenIddict.Core return Task.FromResult(ImmutableArray.Create()); } - return Task.FromResult(JArray.Parse(application.Permissions).Select(element => (string) element).ToImmutableArray()); + // Note: parsing the stringified permissions is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat(nameof(GetPermissionsAsync), "\x1e", application.Permissions); + + var permissions = Cache.Get(key) as ImmutableArray?; + if (permissions == null) + { + permissions = Cache.Set(key, JArray.Parse(application.Permissions) + .Select(element => (string) element) + .ToImmutableArray()); + } + + return Task.FromResult(permissions.GetValueOrDefault()); } /// @@ -374,7 +402,19 @@ namespace OpenIddict.Core return Task.FromResult(ImmutableArray.Create()); } - return Task.FromResult(JArray.Parse(application.PostLogoutRedirectUris).Select(element => (string) element).ToImmutableArray()); + // Note: parsing the stringified addresses is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat(nameof(GetPostLogoutRedirectUrisAsync), "\x1e", application.PostLogoutRedirectUris); + + var addresses = Cache.Get(key) as ImmutableArray?; + if (addresses == null) + { + addresses = Cache.Set(key, JArray.Parse(application.PostLogoutRedirectUris) + .Select(element => (string) element) + .ToImmutableArray()); + } + + return Task.FromResult(addresses.GetValueOrDefault()); } /// @@ -422,7 +462,19 @@ namespace OpenIddict.Core return Task.FromResult(ImmutableArray.Create()); } - return Task.FromResult(JArray.Parse(application.RedirectUris).Select(element => (string) element).ToImmutableArray()); + // Note: parsing the stringified addresses is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat(nameof(GetRedirectUrisAsync), "\x1e", application.RedirectUris); + + var addresses = Cache.Get(key) as ImmutableArray?; + if (addresses == null) + { + addresses = Cache.Set(key, JArray.Parse(application.RedirectUris) + .Select(element => (string) element) + .ToImmutableArray()); + } + + return Task.FromResult(addresses.GetValueOrDefault()); } /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index 1b1e7fb2..147be8f1 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Models; @@ -32,6 +33,21 @@ namespace OpenIddict.Core where TToken : OpenIddictToken, new() where TKey : IEquatable { + protected OpenIddictAuthorizationStore([NotNull] IMemoryCache cache) + { + if (cache == null) + { + throw new ArgumentNullException(nameof(cache)); + } + + Cache = cache; + } + + /// + /// Gets the memory cached associated with the current store. + /// + protected IMemoryCache Cache { get; } + /// /// Determines the number of authorizations that exist in the database. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs index 31128731..31c1c1a2 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Models; @@ -27,6 +28,21 @@ namespace OpenIddict.Core where TScope : OpenIddictScope, new() where TKey : IEquatable { + protected OpenIddictScopeStore([NotNull] IMemoryCache cache) + { + if (cache == null) + { + throw new ArgumentNullException(nameof(cache)); + } + + Cache = cache; + } + + /// + /// Gets the memory cached associated with the current store. + /// + protected IMemoryCache Cache { get; } + /// /// Determines the number of scopes that exist in the database. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs index aaa782df..30db9297 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Models; @@ -31,6 +32,21 @@ namespace OpenIddict.Core where TAuthorization : OpenIddictAuthorization, new() where TKey : IEquatable { + protected OpenIddictTokenStore([NotNull] IMemoryCache cache) + { + if (cache == null) + { + throw new ArgumentNullException(nameof(cache)); + } + + Cache = cache; + } + + /// + /// Gets the memory cached associated with the current store. + /// + protected IMemoryCache Cache { get; } + /// /// Determines the number of tokens that exist in the database. /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index 057b8592..6a1dc4f3 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -27,7 +28,12 @@ namespace OpenIddict.EntityFramework OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { } + public OpenIddictApplicationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -42,7 +48,12 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { } + public OpenIddictApplicationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -62,7 +73,10 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] TContext context) + public OpenIddictApplicationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index bf6fe2ed..3430195a 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -27,7 +28,12 @@ namespace OpenIddict.EntityFramework OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { } + public OpenIddictAuthorizationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -42,7 +48,12 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { } + public OpenIddictAuthorizationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -62,7 +73,10 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] TContext context) + public OpenIddictAuthorizationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs index f1c6bb14..f7459fbd 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -24,7 +25,12 @@ namespace OpenIddict.EntityFramework public class OpenIddictScopeStore : OpenIddictScopeStore where TContext : DbContext { - public OpenIddictScopeStore([NotNull] TContext context) : base(context) { } + public OpenIddictScopeStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -37,7 +43,12 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] TContext context) : base(context) { } + public OpenIddictScopeStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -52,7 +63,10 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] TContext context) + public OpenIddictScopeStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index e140c420..14873f05 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -26,7 +27,12 @@ namespace OpenIddict.EntityFramework OpenIddictAuthorization, TContext, string> where TContext : DbContext { - public OpenIddictTokenStore([NotNull] TContext context) : base(context) { } + public OpenIddictTokenStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -41,7 +47,12 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] TContext context) : base(context) { } + public OpenIddictTokenStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -61,7 +72,10 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] TContext context) + public OpenIddictTokenStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index 52f03a75..90e723f3 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -27,7 +28,12 @@ namespace OpenIddict.EntityFrameworkCore OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { } + public OpenIddictApplicationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -42,7 +48,12 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { } + public OpenIddictApplicationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -62,7 +73,10 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] TContext context) + public OpenIddictApplicationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 3e9b2bed..db9e18c6 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -27,7 +28,12 @@ namespace OpenIddict.EntityFrameworkCore OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { } + public OpenIddictAuthorizationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -42,7 +48,12 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { } + public OpenIddictAuthorizationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -62,7 +73,10 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] TContext context) + public OpenIddictAuthorizationStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs index e206da52..018f1b34 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -24,7 +25,12 @@ namespace OpenIddict.EntityFrameworkCore public class OpenIddictScopeStore : OpenIddictScopeStore where TContext : DbContext { - public OpenIddictScopeStore([NotNull] TContext context) : base(context) { } + public OpenIddictScopeStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -37,7 +43,12 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] TContext context) : base(context) { } + public OpenIddictScopeStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -52,7 +63,10 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] TContext context) + public OpenIddictScopeStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 4fc1ed2b..92ab42a2 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; @@ -26,7 +27,12 @@ namespace OpenIddict.EntityFrameworkCore OpenIddictAuthorization, TContext, string> where TContext : DbContext { - public OpenIddictTokenStore([NotNull] TContext context) : base(context) { } + public OpenIddictTokenStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -41,7 +47,12 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] TContext context) : base(context) { } + public OpenIddictTokenStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(context, cache) + { + } } /// @@ -61,7 +72,10 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] TContext context) + public OpenIddictTokenStore( + [NotNull] TContext context, + [NotNull] IMemoryCache cache) + : base(cache) { if (context == null) {