diff --git a/OpenIddict.sln b/OpenIddict.sln index a3571712..538707b4 100644 --- a/OpenIddict.sln +++ b/OpenIddict.sln @@ -50,7 +50,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Stores", "src\Op EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Server", "src\OpenIddict.Server\OpenIddict.Server.csproj", "{21A7F241-CBE7-4F5C-9787-F2C50D135AEA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.Server.Tests", "test\OpenIddict.Server.Tests\OpenIddict.Server.Tests.csproj", "{07B02B98-8A68-432D-A932-48E6D52B221A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Server.Tests", "test\OpenIddict.Server.Tests\OpenIddict.Server.Tests.csproj", "{07B02B98-8A68-432D-A932-48E6D52B221A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.Abstractions", "src\OpenIddict.Abstractions\OpenIddict.Abstractions.csproj", "{886A16DA-C9CF-4979-9B38-D06DF8A714B6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -122,6 +124,10 @@ Global {07B02B98-8A68-432D-A932-48E6D52B221A}.Debug|Any CPU.Build.0 = Debug|Any CPU {07B02B98-8A68-432D-A932-48E6D52B221A}.Release|Any CPU.ActiveCfg = Release|Any CPU {07B02B98-8A68-432D-A932-48E6D52B221A}.Release|Any CPU.Build.0 = Release|Any CPU + {886A16DA-C9CF-4979-9B38-D06DF8A714B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {886A16DA-C9CF-4979-9B38-D06DF8A714B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {886A16DA-C9CF-4979-9B38-D06DF8A714B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {886A16DA-C9CF-4979-9B38-D06DF8A714B6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,6 +149,7 @@ Global {275D888A-B4C8-4E93-AC4B-B1AA25D98159} = {D544447C-D701-46BB-9A5B-C76C612A596B} {21A7F241-CBE7-4F5C-9787-F2C50D135AEA} = {D544447C-D701-46BB-9A5B-C76C612A596B} {07B02B98-8A68-432D-A932-48E6D52B221A} = {5FC71D6A-A994-4F62-977F-88A7D25379D7} + {886A16DA-C9CF-4979-9B38-D06DF8A714B6} = {D544447C-D701-46BB-9A5B-C76C612A596B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616} diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs index e7f3e7b8..64eed5ac 100644 --- a/samples/Mvc.Server/Controllers/AuthorizationController.cs +++ b/samples/Mvc.Server/Controllers/AuthorizationController.cs @@ -20,6 +20,7 @@ using Mvc.Server.Helpers; using Mvc.Server.Models; using Mvc.Server.ViewModels.Authorization; using Mvc.Server.ViewModels.Shared; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; @@ -67,7 +68,7 @@ namespace Mvc.Server // the original authorization request from the cache. return View(new AuthorizeViewModel { - ApplicationName = application.DisplayName, + ApplicationName = await _applicationManager.GetDisplayNameAsync(application), RequestId = request.RequestId, Scope = request.Scope }); diff --git a/samples/Mvc.Server/Controllers/UserinfoController.cs b/samples/Mvc.Server/Controllers/UserinfoController.cs index 129711b6..16dc1665 100644 --- a/samples/Mvc.Server/Controllers/UserinfoController.cs +++ b/samples/Mvc.Server/Controllers/UserinfoController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Mvc.Server.Models; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; namespace Mvc.Server.Controllers { diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index e5d9bb55..20c0195c 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Mvc.Server.Extensions; using Mvc.Server.Models; using Mvc.Server.Services; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; @@ -52,57 +53,66 @@ namespace Mvc.Server options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role; }); - // Register the OpenIddict services. - services.AddOpenIddict(options => - { - // Register the Entity Framework stores. - options.AddEntityFrameworkCoreStores(); - - // Register the ASP.NET Core MVC binder used by OpenIddict. - // Note: if you don't call this method, you won't be able to - // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. - options.AddMvcBinders(); - - // Enable the authorization, logout, token and userinfo endpoints. - options.EnableAuthorizationEndpoint("/connect/authorize") - .EnableLogoutEndpoint("/connect/logout") - .EnableTokenEndpoint("/connect/token") - .EnableUserinfoEndpoint("/api/userinfo"); - - // Note: the Mvc.Client sample only uses the code flow and the password flow, but you - // can enable the other flows if you need to support implicit or client credentials. - options.AllowAuthorizationCodeFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow(); - - // Mark the "email", "profile" and "roles" scopes as supported scopes. - options.RegisterScopes(OpenIdConnectConstants.Scopes.Email, - OpenIdConnectConstants.Scopes.Profile, - OpenIddictConstants.Scopes.Roles); - - // Make the "client_id" parameter mandatory when sending a token request. - options.RequireClientIdentification(); - - // When request caching is enabled, authorization and logout requests - // are stored in the distributed cache by OpenIddict and the user agent - // is redirected to the same page with a single parameter (request_id). - // This allows flowing large OpenID Connect requests even when using - // an external authentication provider like Google, Facebook or Twitter. - options.EnableRequestCaching(); - - // Enable scope validation, so that authorization and token requests - // that specify unregistered scopes are automatically rejected. - options.EnableScopeValidation(); - - // During development, you can disable the HTTPS requirement. - options.DisableHttpsRequirement(); - - // Note: to use JWT access tokens instead of the default - // encrypted format, the following lines are required: - // - // options.UseJsonWebTokens(); - // options.AddEphemeralSigningKey(); - }); + services.AddOpenIddict() + + // Register the OpenIddict core services. + .AddCore(options => + { + // Configure OpenIddict to use the default models. + options.UseDefaultModels(); + + // Register the Entity Framework stores. + options.AddEntityFrameworkCoreStores(); + }) + + // Register the OpenIddict server handler. + .AddServer(options => + { + // Register the ASP.NET Core MVC binder used by OpenIddict. + // Note: if you don't call this method, you won't be able to + // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. + options.AddMvcBinders(); + + // Enable the authorization, logout, token and userinfo endpoints. + options.EnableAuthorizationEndpoint("/connect/authorize") + .EnableLogoutEndpoint("/connect/logout") + .EnableTokenEndpoint("/connect/token") + .EnableUserinfoEndpoint("/api/userinfo"); + + // Note: the Mvc.Client sample only uses the code flow and the password flow, but you + // can enable the other flows if you need to support implicit or client credentials. + options.AllowAuthorizationCodeFlow() + .AllowPasswordFlow() + .AllowRefreshTokenFlow(); + + // Mark the "email", "profile" and "roles" scopes as supported scopes. + options.RegisterScopes(OpenIdConnectConstants.Scopes.Email, + OpenIdConnectConstants.Scopes.Profile, + OpenIddictConstants.Scopes.Roles); + + // Make the "client_id" parameter mandatory when sending a token request. + options.RequireClientIdentification(); + + // When request caching is enabled, authorization and logout requests + // are stored in the distributed cache by OpenIddict and the user agent + // is redirected to the same page with a single parameter (request_id). + // This allows flowing large OpenID Connect requests even when using + // an external authentication provider like Google, Facebook or Twitter. + options.EnableRequestCaching(); + + // Enable scope validation, so that authorization and token requests + // that specify unregistered scopes are automatically rejected. + options.EnableScopeValidation(); + + // During development, you can disable the HTTPS requirement. + options.DisableHttpsRequirement(); + + // Note: to use JWT access tokens instead of the default + // encrypted format, the following lines are required: + // + // options.UseJsonWebTokens(); + // options.AddEphemeralSigningKey(); + }); services.AddTransient(); services.AddTransient(); @@ -171,7 +181,7 @@ namespace Mvc.Server }); }); - app.UseOpenIddict(); + app.UseOpenIddictServer(); app.UseMvcWithDefaultRoute(); diff --git a/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj b/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj new file mode 100644 index 00000000..42180ddf --- /dev/null +++ b/src/OpenIddict.Abstractions/OpenIddict.Abstractions.csproj @@ -0,0 +1,23 @@ + + + + + + netstandard1.0 + + + + OpenIddict's abstractions. + Kévin Chalet + aspnetcore;authentication;jwt;openidconnect;openiddict;security + + + + + + + + + + + diff --git a/src/OpenIddict.Abstractions/OpenIddictBuilder.cs b/src/OpenIddict.Abstractions/OpenIddictBuilder.cs new file mode 100644 index 00000000..88acb436 --- /dev/null +++ b/src/OpenIddict.Abstractions/OpenIddictBuilder.cs @@ -0,0 +1,38 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.ComponentModel; +using JetBrains.Annotations; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Provides a shared entry point allowing to configure the OpenIddict services. + /// + public class OpenIddictBuilder + { + /// + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictBuilder([NotNull] IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + Services = services; + } + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs similarity index 99% rename from src/OpenIddict.Core/OpenIddictConstants.cs rename to src/OpenIddict.Abstractions/OpenIddictConstants.cs index c4185636..8f47c7fa 100644 --- a/src/OpenIddict.Core/OpenIddictConstants.cs +++ b/src/OpenIddict.Abstractions/OpenIddictConstants.cs @@ -4,7 +4,7 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Core +namespace OpenIddict.Abstractions { public static class OpenIddictConstants { diff --git a/src/OpenIddict.Abstractions/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/OpenIddictExtensions.cs new file mode 100644 index 00000000..b23473fe --- /dev/null +++ b/src/OpenIddict.Abstractions/OpenIddictExtensions.cs @@ -0,0 +1,56 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using JetBrains.Annotations; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class OpenIddictExtensions + { + /// + /// Provides a common entry point for registering the OpenIddict services. + /// + /// The services collection. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + return new OpenIddictBuilder(services); + } + + /// + /// Provides a common entry point for registering the OpenIddict services. + /// + /// The services collection. + /// The configuration delegate used to register new services. + /// This extension can be safely called multiple times. + /// The . + public static IServiceCollection AddOpenIddict( + [NotNull] this IServiceCollection services, + [NotNull] Action configuration) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + configuration(services.AddOpenIddict()); + + return services; + } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs new file mode 100644 index 00000000..1724a255 --- /dev/null +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs @@ -0,0 +1,18 @@ +using System; + +namespace OpenIddict.Abstractions +{ + /// + /// Exposes a method allowing to resolve an application store. + /// + public interface IOpenIddictApplicationStoreResolver + { + /// + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Application entity. + /// An . + IOpenIddictApplicationStore Get() where TApplication : class; + } +} diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs new file mode 100644 index 00000000..769ecd24 --- /dev/null +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs @@ -0,0 +1,18 @@ +using System; + +namespace OpenIddict.Abstractions +{ + /// + /// Exposes a method allowing to resolve an authorization store. + /// + public interface IOpenIddictAuthorizationStoreResolver + { + /// + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Authorization entity. + /// An . + IOpenIddictAuthorizationStore Get() where TAuthorization : class; + } +} diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs new file mode 100644 index 00000000..9da948de --- /dev/null +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs @@ -0,0 +1,18 @@ +using System; + +namespace OpenIddict.Abstractions +{ + /// + /// Exposes a method allowing to resolve a scope store. + /// + public interface IOpenIddictScopeStoreResolver + { + /// + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Scope entity. + /// An . + IOpenIddictScopeStore Get() where TScope : class; + } +} diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs new file mode 100644 index 00000000..2bd5f8de --- /dev/null +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs @@ -0,0 +1,18 @@ +using System; + +namespace OpenIddict.Abstractions +{ + /// + /// Exposes a method allowing to resolve a token store. + /// + public interface IOpenIddictTokenStoreResolver + { + /// + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Token entity. + /// An . + IOpenIddictTokenStore Get() where TToken : class; + } +} diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs rename to src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs index c9f023e7..11b5ed90 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json.Linq; -namespace OpenIddict.Core +namespace OpenIddict.Abstractions { /// /// Provides methods allowing to manage the applications stored in a database. diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs rename to src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs index 95dd042d..81a65523 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json.Linq; -namespace OpenIddict.Core +namespace OpenIddict.Abstractions { /// /// Provides methods allowing to manage the authorizations stored in a database. diff --git a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs rename to src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs index 996405db..899669cb 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictScopeStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json.Linq; -namespace OpenIddict.Core +namespace OpenIddict.Abstractions { /// /// Provides methods allowing to manage the scopes stored in a database. diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs rename to src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs index 59f4cef3..b4d718e4 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json.Linq; -namespace OpenIddict.Core +namespace OpenIddict.Abstractions { /// /// Provides methods allowing to manage the tokens stored in a database. diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 6f21b4b6..df0f2fec 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -13,6 +13,8 @@ using System.Threading.Tasks; using CryptoHelper; using JetBrains.Annotations; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; namespace OpenIddict.Core { @@ -23,11 +25,13 @@ namespace OpenIddict.Core public class OpenIddictApplicationManager where TApplication : class { public OpenIddictApplicationManager( - [NotNull] IOpenIddictApplicationStore store, - [NotNull] ILogger> logger) + [NotNull] IOpenIddictApplicationStoreResolver resolver, + [NotNull] ILogger> logger, + [NotNull] IOptions options) { - Store = store; + Store = resolver.Get(); Logger = logger; + Options = options; } /// @@ -35,6 +39,11 @@ namespace OpenIddict.Core /// protected ILogger Logger { get; } + /// + /// Gets the options associated with the current manager. + /// + protected IOptions Options { get; } + /// /// Gets the store associated with the current manager. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 2c6d9116..b93f7acd 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -5,7 +5,6 @@ */ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -14,6 +13,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; namespace OpenIddict.Core { @@ -24,11 +25,13 @@ namespace OpenIddict.Core public class OpenIddictAuthorizationManager where TAuthorization : class { public OpenIddictAuthorizationManager( - [NotNull] IOpenIddictAuthorizationStore store, - [NotNull] ILogger> logger) + [NotNull] IOpenIddictAuthorizationStoreResolver resolver, + [NotNull] ILogger> logger, + [NotNull] IOptions options) { + Store = resolver.Get(); Logger = logger; - Store = store; + Options = options; } /// @@ -36,6 +39,11 @@ namespace OpenIddict.Core /// protected ILogger Logger { get; } + /// + /// Gets the options associated with the current manager. + /// + protected IOptions Options { get; } + /// /// Gets the store associated with the current manager. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index de70b00c..138ae374 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -13,6 +13,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; namespace OpenIddict.Core { @@ -23,11 +25,13 @@ namespace OpenIddict.Core public class OpenIddictScopeManager where TScope : class { public OpenIddictScopeManager( - [NotNull] IOpenIddictScopeStore store, - [NotNull] ILogger> logger) + [NotNull] IOpenIddictScopeStoreResolver resolver, + [NotNull] ILogger> logger, + [NotNull] IOptions options) { + Store = resolver.Get(); Logger = logger; - Store = store; + Options = options; } /// @@ -35,6 +39,11 @@ namespace OpenIddict.Core /// protected ILogger Logger { get; } + /// + /// Gets the options associated with the current manager. + /// + protected IOptions Options { get; } + /// /// Gets the store associated with the current manager. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 0b2a97f3..dd9f57b7 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -5,7 +5,6 @@ */ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -15,6 +14,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; namespace OpenIddict.Core { @@ -25,11 +26,13 @@ namespace OpenIddict.Core public class OpenIddictTokenManager where TToken : class { public OpenIddictTokenManager( - [NotNull] IOpenIddictTokenStore store, - [NotNull] ILogger> logger) + [NotNull] IOpenIddictTokenStoreResolver resolver, + [NotNull] ILogger> logger, + [NotNull] IOptions options) { + Store = resolver.Get(); Logger = logger; - Store = store; + Options = options; } /// @@ -37,6 +40,11 @@ namespace OpenIddict.Core /// protected ILogger Logger { get; } + /// + /// Gets the options associated with the current manager. + /// + protected IOptions Options { get; } + /// /// Gets the store associated with the current manager. /// diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index 31b35cfd..8adc43d6 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -12,6 +12,10 @@ aspnetcore;authentication;jwt;openidconnect;openiddict;security + + + + diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs deleted file mode 100644 index 08802957..00000000 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using System.ComponentModel; -using JetBrains.Annotations; -using OpenIddict.Core; - -#if NETSTANDARD1_3 -using System.Reflection; -#endif - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Exposes the necessary methods required to configure OpenIddict. - /// - public class OpenIddictBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictBuilder([NotNull] IServiceCollection services) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - Services = services; - } - - /// - /// Gets or sets the type corresponding to the Application entity. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Type ApplicationType { get; set; } - - /// - /// Gets or sets the type corresponding to the Authorization entity. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Type AuthorizationType { get; set; } - - /// - /// Gets or sets the type corresponding to the Scope entity. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Type ScopeType { get; set; } - - /// - /// Gets or sets the type corresponding to the Token entity. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Type TokenType { get; set; } - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Adds a custom application manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public OpenIddictBuilder AddApplicationManager() where TManager : class - => AddApplicationManager(typeof(TManager)); - - /// - /// Adds a custom application manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public virtual OpenIddictBuilder AddApplicationManager([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(OpenIddictApplicationManager<>).MakeGenericType(ApplicationType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom application store derived from - /// . - /// - /// The type of the custom store. - /// The . - public OpenIddictBuilder AddApplicationStore() where TStore : class - => AddApplicationStore(typeof(TStore)); - - /// - /// Adds a custom application store derived from - /// . - /// - /// The type of the custom store. - /// The . - public virtual OpenIddictBuilder AddApplicationStore([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(IOpenIddictApplicationStore<>).MakeGenericType(ApplicationType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom authorization manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public OpenIddictBuilder AddAuthorizationManager() where TManager : class - => AddAuthorizationManager(typeof(TManager)); - - /// - /// Adds a custom authorization manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public virtual OpenIddictBuilder AddAuthorizationManager([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(OpenIddictAuthorizationManager<>).MakeGenericType(AuthorizationType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom authorization store derived from - /// . - /// - /// The type of the custom store. - /// The . - public OpenIddictBuilder AddAuthorizationStore() where TStore : class - => AddAuthorizationStore(typeof(TStore)); - - /// - /// Adds a custom authorization store derived from - /// . - /// - /// The type of the custom store. - /// The . - public virtual OpenIddictBuilder AddAuthorizationStore([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(IOpenIddictAuthorizationStore<>).MakeGenericType(AuthorizationType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom scope manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public OpenIddictBuilder AddScopeManager() where TManager : class - => AddScopeManager(typeof(TManager)); - - /// - /// Adds a custom scope manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public virtual OpenIddictBuilder AddScopeManager([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(OpenIddictScopeManager<>).MakeGenericType(ScopeType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom scope store derived from - /// . - /// - /// The type of the custom store. - /// The . - public OpenIddictBuilder AddScopeStore() where TStore : class - => AddScopeStore(typeof(TStore)); - - /// - /// Adds a custom scope store derived from - /// . - /// - /// The type of the custom store. - /// The . - public virtual OpenIddictBuilder AddScopeStore([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(IOpenIddictScopeStore<>).MakeGenericType(ScopeType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom token manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public OpenIddictBuilder AddTokenManager() where TManager : class - => AddTokenManager(typeof(TManager)); - - /// - /// Adds a custom token manager derived from - /// . - /// - /// The type of the custom manager. - /// The . - public virtual OpenIddictBuilder AddTokenManager([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(OpenIddictTokenManager<>).MakeGenericType(TokenType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - - /// - /// Adds a custom token store derived from - /// . - /// - /// The type of the custom store. - /// The . - public OpenIddictBuilder AddTokenStore() where TStore : class - => AddTokenStore(typeof(TStore)); - - /// - /// Adds a custom token store derived from - /// . - /// - /// The type of the custom store. - /// The . - public virtual OpenIddictBuilder AddTokenStore([NotNull] Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var contract = typeof(IOpenIddictTokenStore<>).MakeGenericType(TokenType); - if (!contract.IsAssignableFrom(type)) - { - throw new InvalidOperationException("The specified type is invalid."); - } - - Services.AddScoped(contract, type); - - return this; - } - } -} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs new file mode 100644 index 00000000..b25c0ba1 --- /dev/null +++ b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs @@ -0,0 +1,629 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.ComponentModel; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenIddict.Abstractions; +using OpenIddict.Core; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Exposes the necessary methods required to configure the OpenIddict core services. + /// + public class OpenIddictCoreBuilder + { + /// + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictCoreBuilder([NotNull] IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + Services = services; + } + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict core configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictCoreBuilder Configure([NotNull] Action configuration) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + Services.Configure(configuration); + + return this; + } + + /// + /// Adds a custom application store by a custom implementation derived + /// from . + /// Note: when using this overload, the application store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddApplicationStore() where TStore : class + => AddApplicationStore(typeof(TStore)); + + /// + /// Adds a custom application store by a custom implementation derived + /// from . + /// Note: when using this overload, the application store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddApplicationStore([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictApplicationStore<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictApplicationStore<>) + // or closed generics (e.g OpenIddictApplicationStore). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictApplicationStore<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictApplicationStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Adds a custom authorization store by a custom implementation derived + /// from . + /// Note: when using this overload, the authorization store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddAuthorizationStore() where TStore : class + => AddAuthorizationStore(typeof(TStore)); + + /// + /// Adds a custom authorization store by a custom implementation derived + /// from . + /// Note: when using this overload, the authorization store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddAuthorizationStore([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictAuthorizationStore<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictAuthorizationStore<>) + // or closed generics (e.g OpenIddictAuthorizationStore). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictAuthorizationStore<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictAuthorizationStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Adds a custom scope store by a custom implementation derived + /// from . + /// Note: when using this overload, the scope store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddScopeStore() where TStore : class + => AddScopeStore(typeof(TStore)); + + /// + /// Adds a custom scope store by a custom implementation derived + /// from . + /// Note: when using this overload, the scope store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddScopeStore([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictScopeStore<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictScopeStore<>) + // or closed generics (e.g OpenIddictScopeStore). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictScopeStore<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictScopeStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Adds a custom token store by a custom implementation derived + /// from . + /// Note: when using this overload, the token store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddTokenStore() where TStore : class + => AddTokenStore(typeof(TStore)); + + /// + /// Adds a custom token store by a custom implementation derived + /// from . + /// Note: when using this overload, the token store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder AddTokenStore([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictTokenStore<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictTokenStore<>) + // or closed generics (e.g OpenIddictTokenStore). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictTokenStore<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictTokenStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Replace the default application manager by a custom manager derived + /// from . + /// Note: when using this overload, the application manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationManager([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictApplicationManager<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictApplicationManager<>) + // or closed generics (e.g OpenIddictApplicationManager). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Replaces the default application store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationStoreResolver() + where TResolver : IOpenIddictApplicationStoreResolver + => ReplaceApplicationStoreResolver(typeof(TResolver)); + + /// + /// Replaces the default application store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationStoreResolver([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictApplicationStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictApplicationStoreResolver), type)); + + return this; + } + + /// + /// Replace the default application manager by a custom manager derived + /// from . + /// Note: when using this overload, the application manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationManager() where TManager : class + => ReplaceApplicationManager(typeof(TManager)); + + /// + /// Replace the default authorization manager by a custom manager derived + /// from . + /// Note: when using this overload, the authorization manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationManager() where TManager : class + => ReplaceAuthorizationManager(typeof(TManager)); + + /// + /// Replace the default authorization manager by a custom manager derived + /// from . + /// Note: when using this overload, the authorization manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationManager([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictAuthorizationManager<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictAuthorizationManager<>) + // or closed generics (e.g OpenIddictAuthorizationManager). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Replaces the default authorization store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver() + where TResolver : IOpenIddictAuthorizationStoreResolver + => ReplaceAuthorizationStoreResolver(typeof(TResolver)); + + /// + /// Replaces the default authorization store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictAuthorizationStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictAuthorizationStoreResolver), type)); + + return this; + } + + /// + /// Replace the default scope manager by a custom manager + /// derived from . + /// Note: when using this overload, the scope manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceScopeManager() where TManager : class + => ReplaceScopeManager(typeof(TManager)); + + /// + /// Replace the default scope manager by a custom manager + /// derived from . + /// Note: when using this overload, the scope manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceScopeManager([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictScopeManager<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictScopeManager<>) + // or closed generics (e.g OpenIddictScopeManager). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Replaces the default scope store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceScopeStoreResolver() + where TResolver : IOpenIddictScopeStoreResolver + => ReplaceScopeStoreResolver(typeof(TResolver)); + + /// + /// Replaces the default scope store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceScopeStoreResolver([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictScopeStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictScopeStoreResolver), type)); + + return this; + } + + /// + /// Replace the default token manager by a custom manager + /// derived from . + /// Note: when using this overload, the token manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceTokenManager() where TManager : class + => ReplaceTokenManager(typeof(TManager)); + + /// + /// Replace the default token manager by a custom manager + /// derived from . + /// Note: when using this overload, the token manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceTokenManager([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictTokenManager<>)); + if (root == null) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictTokenManager<>) + // or closed generics (e.g OpenIddictTokenManager). + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + if (type.GetGenericArguments().Length != 1) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>), type)); + } + + else + { + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Replaces the default token store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceTokenStoreResolver() + where TResolver : IOpenIddictTokenStoreResolver + => ReplaceTokenStoreResolver(typeof(TResolver)); + + /// + /// Replaces the default token store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The . + public OpenIddictCoreBuilder ReplaceTokenStoreResolver([NotNull] Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictTokenStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictTokenStoreResolver), type)); + + return this; + } + + /// + /// Configures OpenIddict to use the specified entities. + /// + /// The type corresponding to the Application entity. + /// The type corresponding to the Authorization entity. + /// The type corresponding to the Scope entity. + /// The type corresponding to the Token entity. + /// The . + public OpenIddictCoreBuilder UseCustomModels() + where TApplication : class + where TAuthorization : class + where TScope : class + where TToken : class + => Configure(options => + { + options.DefaultApplicationType = typeof(TApplication); + options.DefaultAuthorizationType = typeof(TAuthorization); + options.DefaultScopeType = typeof(TScope); + options.DefaultTokenType = typeof(TToken); + }); + } +} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictCoreExtensions.cs b/src/OpenIddict.Core/OpenIddictCoreExtensions.cs new file mode 100644 index 00000000..774da9c3 --- /dev/null +++ b/src/OpenIddict.Core/OpenIddictCoreExtensions.cs @@ -0,0 +1,73 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenIddict.Abstractions; +using OpenIddict.Core; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class OpenIddictCoreExtensions + { + /// + /// Registers the OpenIddict core services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder AddCore([NotNull] this OpenIddictBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Services.AddDistributedMemoryCache(); + builder.Services.AddMemoryCache(); + builder.Services.AddOptions(); + + builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>)); + + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + + return new OpenIddictCoreBuilder(builder.Services); + } + + /// + /// Registers the OpenIddict core services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the core services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddCore( + [NotNull] this OpenIddictBuilder builder, + [NotNull] Action configuration) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + configuration(builder.AddCore()); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictCoreHelpers.cs b/src/OpenIddict.Core/OpenIddictCoreHelpers.cs new file mode 100644 index 00000000..f51bfedc --- /dev/null +++ b/src/OpenIddict.Core/OpenIddictCoreHelpers.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace OpenIddict.Core +{ + /// + /// Exposes common helpers used by the OpenIddict assemblies. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class OpenIddictCoreHelpers + { + /// + /// Finds the base type that matches the specified generic type definition. + /// + /// The type to introspect. + /// The generic type definition. + /// A instance if the base type was found, null otherwise. + public static Type FindGenericBaseType(Type type, Type definition) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (definition == null) + { + throw new ArgumentNullException(nameof(definition)); + } + + if (!definition.GetTypeInfo().IsGenericTypeDefinition) + { + throw new ArgumentException("The second parameter must be a generic type definition.", nameof(definition)); + } + + for (var candidate = type.GetTypeInfo(); candidate != null; candidate = candidate.BaseType?.GetTypeInfo()) + { + if (!candidate.IsGenericType && !candidate.AsType().IsConstructedGenericType) + { + continue; + } + + if (candidate.GetGenericTypeDefinition() == definition) + { + return candidate.AsType(); + } + + if (definition.GetTypeInfo().IsInterface) + { + foreach (var contract in candidate.AsType().GetInterfaces().Select(contract => contract.GetTypeInfo())) + { + if (!contract.IsGenericType && !contract.AsType().IsConstructedGenericType) + { + continue; + } + + if (contract.GetGenericTypeDefinition() == definition) + { + return contract.AsType(); + } + } + } + } + + return null; + } + } +} diff --git a/src/OpenIddict.Core/OpenIddictCoreOptions.cs b/src/OpenIddict.Core/OpenIddictCoreOptions.cs new file mode 100644 index 00000000..5f728591 --- /dev/null +++ b/src/OpenIddict.Core/OpenIddictCoreOptions.cs @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; + +namespace OpenIddict.Core +{ + public class OpenIddictCoreOptions + { + /// + /// Gets or sets the type corresponding to the Application entity. + /// + public Type DefaultApplicationType { get; set; } + + /// + /// Gets or sets the type corresponding to the Authorization entity. + /// + public Type DefaultAuthorizationType { get; set; } + + /// + /// Gets or sets the type corresponding to the Scope entity. + /// + public Type DefaultScopeType { get; set; } + + /// + /// Gets or sets the type corresponding to the Token entity. + /// + public Type DefaultTokenType { get; set; } + } +} \ No newline at end of file diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs deleted file mode 100644 index 25424759..00000000 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection.Extensions; -using OpenIddict.Core; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class OpenIddictExtensions - { - /// - /// Registers the default OpenIddict services in the DI container, using the specified entities. - /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Scope entity. - /// The type of the Token entity. - /// The services collection. - /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - where TApplication : class - where TAuthorization : class - where TScope : class - where TToken : class - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - services.AddDistributedMemoryCache(); - services.AddMemoryCache(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(TApplication), - AuthorizationType = typeof(TAuthorization), - ScopeType = typeof(TScope), - TokenType = typeof(TToken) - }; - - // Register the OpenIddict core services in the DI container. - builder.Services.TryAddSingleton(builder); - builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); - builder.Services.TryAddScoped>(); - - return builder; - } - - /// - /// Registers the default OpenIddict services in the DI container, using the specified entities. - /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Scope entity. - /// The type of the Token entity. - /// The services collection. - /// The configuration delegate used to register new services. - /// The . - public static IServiceCollection AddOpenIddict( - [NotNull] this IServiceCollection services, - [NotNull] Action configuration) - where TApplication : class - where TAuthorization : class - where TScope : class - where TToken : class - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - // Register the OpenIddict core services and invoke the configuration delegate. - configuration(services.AddOpenIddict()); - - return services; - } - } -} \ No newline at end of file diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs new file mode 100644 index 00000000..41353685 --- /dev/null +++ b/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; + +namespace OpenIddict.Core +{ + /// + /// Exposes a method allowing to resolve an application store. + /// + public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver + { + private readonly IServiceProvider _provider; + + public OpenIddictApplicationStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class + { + var store = _provider.GetService>(); + if (store == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("No application store has been registered in the dependency injection container.") + .Append("To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' ") + .AppendLine("package and call 'services.AddOpenIddict().AddCore().AddEntityFrameworkCoreStores()'.") + .Append("To register a custom store, create an implementation of 'IOpenIddictApplicationStore' and ") + .Append("use 'services.AddOpenIddict().AddCore().AddApplicationStore()' to add it to the DI container.") + .ToString()); + } + + return store; + } + } +} diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs new file mode 100644 index 00000000..051ac7d5 --- /dev/null +++ b/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; + +namespace OpenIddict.Core +{ + /// + /// Exposes a method allowing to resolve an authorization store. + /// + public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver + { + private readonly IServiceProvider _provider; + + public OpenIddictAuthorizationStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class + { + var store = _provider.GetService>(); + if (store == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("No authorization store has been registered in the dependency injection container.") + .Append("To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' ") + .AppendLine("package and call 'services.AddOpenIddict().AddCore().AddEntityFrameworkCoreStores()'.") + .Append("To register a custom store, create an implementation of 'IOpenIddictAuthorizationStore' and ") + .Append("use 'services.AddOpenIddict().AddCore().AddAuthorizationStore()' to add it to the DI container.") + .ToString()); + } + + return store; + } + } +} diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs new file mode 100644 index 00000000..622ba7ff --- /dev/null +++ b/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; + +namespace OpenIddict.Core +{ + /// + /// Exposes a method allowing to resolve a scope store. + /// + public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver + { + private readonly IServiceProvider _provider; + + public OpenIddictScopeStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class + { + var store = _provider.GetService>(); + if (store == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("No scope store has been registered in the dependency injection container.") + .Append("To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' ") + .AppendLine("package and call 'services.AddOpenIddict().AddCore().AddEntityFrameworkCoreStores()'.") + .Append("To register a custom store, create an implementation of 'IOpenIddictScopeStore' and ") + .Append("use 'services.AddOpenIddict().AddCore().AddScopeStore()' to add it to the DI container.") + .ToString()); + } + + return store; + } + } +} diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs new file mode 100644 index 00000000..c79152cc --- /dev/null +++ b/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; + +namespace OpenIddict.Core +{ + /// + /// Exposes a method allowing to resolve a token store. + /// + public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver + { + private readonly IServiceProvider _provider; + + public OpenIddictTokenStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class + { + var store = _provider.GetService>(); + if (store == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("No token store factory has been registered in the dependency injection container.") + .Append("To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' ") + .AppendLine("package and call 'services.AddOpenIddict().AddCore().AddEntityFrameworkCoreStores()'.") + .Append("To register a custom store, create an implementation of 'IOpenIddictTokenStore' and ") + .Append("use 'services.AddOpenIddict().AddCore().AddTokenStore()' to add it to the DI container.") + .ToString()); + } + + return store; + } + } +} diff --git a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj index be54d1e2..ed03ec0a 100644 --- a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj +++ b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj @@ -13,6 +13,7 @@ + diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs index 5e2cc25d..fbf0dcea 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs @@ -5,15 +5,12 @@ */ using System; -using System.ComponentModel; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Data.Entity.Infrastructure.Annotations; -using System.Diagnostics; -using System.Reflection; +using System.Text; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection.Extensions; -using OpenIddict.Core; using OpenIddict.EntityFramework; using OpenIddict.Models; @@ -27,8 +24,9 @@ namespace Microsoft.Extensions.DependencyInjection /// the entities MUST be derived from the models contained in the OpenIddict.Models package. /// /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AddEntityFrameworkStores([NotNull] this OpenIddictBuilder builder) + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder AddEntityFrameworkStores([NotNull] this OpenIddictCoreBuilder builder) where TContext : DbContext { if (builder == null) @@ -36,85 +34,15 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(builder)); } - Debug.Assert(builder.ApplicationType != null && - builder.AuthorizationType != null && - builder.ScopeType != null && - builder.TokenType != null, "The entity types exposed by OpenIddictBuilder shouldn't be null."); + builder.Services.TryAddScoped(typeof(OpenIddictApplicationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictScopeStore<,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictTokenStore<,,,,>)); - var application = FindGenericBaseType(builder.ApplicationType, typeof(OpenIddictApplication<,,>)); - if (application == null) - { - throw new InvalidOperationException("The Entity Framework stores can only be used " + - "with the built-in OpenIddictApplication entity."); - } - - var authorization = FindGenericBaseType(builder.AuthorizationType, typeof(OpenIddictAuthorization<,,>)); - if (authorization == null) - { - throw new InvalidOperationException("The Entity Framework stores can only be used " + - "with the built-in OpenIddictAuthorization entity."); - } - - var scope = FindGenericBaseType(builder.ScopeType, typeof(OpenIddictScope<>)); - if (scope == null) - { - throw new InvalidOperationException("The Entity Framework stores can only be used " + - "with the built-in OpenIddictScope entity."); - } - - var token = FindGenericBaseType(builder.TokenType, typeof(OpenIddictToken<,,>)); - if (token == null) - { - throw new InvalidOperationException("The Entity Framework stores can only be used " + - "with the built-in OpenIddictToken entity."); - } - - var converter = TypeDescriptor.GetConverter(application.GenericTypeArguments[0]); - if (converter == null || !converter.CanConvertFrom(typeof(string)) || - !converter.CanConvertTo(typeof(string))) - { - throw new InvalidOperationException("The specified entity key type is not supported."); - } - - // Register the application store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictApplicationStore<>).MakeGenericType(builder.ApplicationType), - typeof(OpenIddictApplicationStore<,,,,>).MakeGenericType( - /* TApplication: */ builder.ApplicationType, - /* TAuthorization: */ builder.AuthorizationType, - /* TToken: */ builder.TokenType, - /* TContext: */ typeof(TContext), - /* TKey: */ application.GenericTypeArguments[0])); - - // Register the authorization store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictAuthorizationStore<>).MakeGenericType(builder.AuthorizationType), - typeof(OpenIddictAuthorizationStore<,,,,>).MakeGenericType( - /* TAuthorization: */ builder.AuthorizationType, - /* TApplication: */ builder.ApplicationType, - /* TToken: */ builder.TokenType, - /* TContext: */ typeof(TContext), - /* TKey: */ authorization.GenericTypeArguments[0])); - - // Register the scope store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictScopeStore<>).MakeGenericType(builder.ScopeType), - typeof(OpenIddictScopeStore<,,>).MakeGenericType( - /* TScope: */ builder.ScopeType, - /* TContext: */ typeof(TContext), - /* TKey: */ scope.GenericTypeArguments[0])); - - // Register the token store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictTokenStore<>).MakeGenericType(builder.TokenType), - typeof(OpenIddictTokenStore<,,,,>).MakeGenericType( - /* TToken: */ builder.TokenType, - /* TApplication: */ builder.ApplicationType, - /* TAuthorization: */ builder.AuthorizationType, - /* TContext: */ typeof(TContext), - /* TKey: */ token.GenericTypeArguments[0])); - - return builder; + return builder.ReplaceApplicationStoreResolver>() + .ReplaceAuthorizationStoreResolver>() + .ReplaceScopeStoreResolver>() + .ReplaceTokenStoreResolver>(); } /// @@ -124,12 +52,10 @@ namespace Microsoft.Extensions.DependencyInjection /// The builder used to configure the Entity Framework context. /// The Entity Framework context builder. public static DbModelBuilder UseOpenIddict([NotNull] this DbModelBuilder builder) - { - return builder.UseOpenIddict(); - } + => builder.UseOpenIddict(); /// /// Registers the OpenIddict entity sets in the Entity Framework context @@ -154,28 +80,36 @@ namespace Microsoft.Extensions.DependencyInjection // Note: unlike Entity Framework Core 1.x/2.x, Entity Framework 6.x // always throws an exception when using generic types as entity types. // To ensure a better exception is thrown, a manual check is made here. - if (typeof(TApplication).GetTypeInfo().IsGenericType) + if (typeof(TApplication).IsGenericType) { - throw new InvalidOperationException("The application entity cannot be a generic type. " + - "Consider creating a non-generic derived class."); + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The application entity cannot be a generic type.") + .Append("Consider creating a non-generic derived class.") + .ToString()); } - if (typeof(TAuthorization).GetTypeInfo().IsGenericType) + if (typeof(TAuthorization).IsGenericType) { - throw new InvalidOperationException("The authorization entity cannot be a generic type. " + - "Consider creating a non-generic derived class."); + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The authorization entity cannot be a generic type.") + .Append("Consider creating a non-generic derived class.") + .ToString()); } - if (typeof(TScope).GetTypeInfo().IsGenericType) + if (typeof(TScope).IsGenericType) { - throw new InvalidOperationException("The scope entity cannot be a generic type. " + - "Consider creating a non-generic derived class."); + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The scope entity cannot be a generic type.") + .Append("Consider creating a non-generic derived class.") + .ToString()); } - if (typeof(TToken).GetTypeInfo().IsGenericType) + if (typeof(TToken).IsGenericType) { - throw new InvalidOperationException("The scope entity cannot be a generic type. " + - "Consider creating a non-generic derived class."); + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The token entity cannot be a generic type.") + .Append("Consider creating a non-generic derived class.") + .ToString()); } // Warning: optional foreign keys MUST NOT be added as CLR properties because @@ -285,28 +219,5 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } - - private static TypeInfo FindGenericBaseType(Type type, Type definition) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (definition == null) - { - throw new ArgumentNullException(nameof(definition)); - } - - for (var candidate = type.GetTypeInfo(); candidate != null; candidate = candidate.BaseType?.GetTypeInfo()) - { - if (candidate.IsGenericType && candidate.GetGenericTypeDefinition() == definition) - { - return candidate; - } - } - - return null; - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs new file mode 100644 index 00000000..b235657c --- /dev/null +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Entity; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFramework +{ + /// + /// Exposes a method allowing to resolve an application store. + /// + public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictApplicationStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TApplication), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified application type is not compatible with the Entity Framework 6.x stores.") + .Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ") + .Append("'OpenIddictApplication' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictApplication' entity.") + .ToString()); + } + + return typeof(OpenIddictApplicationStore<,,,,>).MakeGenericType( + /* TApplication: */ key, + /* TAuthorization: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs new file mode 100644 index 00000000..942dc910 --- /dev/null +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Entity; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFramework +{ + /// + /// Exposes a method allowing to resolve an authorization store. + /// + public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictAuthorizationStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TAuthorization), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified authorization type is not compatible with the Entity Framework 6.x stores.") + .Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ") + .Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictAuthorization' entity.") + .ToString()); + } + + return typeof(OpenIddictAuthorizationStore<,,,,>).MakeGenericType( + /* TAuthorization: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs new file mode 100644 index 00000000..1744e2d0 --- /dev/null +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Entity; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFramework +{ + /// + /// Exposes a method allowing to resolve a scope store. + /// + public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictScopeStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TScope), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified scope type is not compatible with the Entity Framework 6.x stores.") + .Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ") + .Append("'OpenIdScope' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictScope' entity.") + .ToString()); + } + + return typeof(OpenIddictScopeStore<,,>).MakeGenericType( + /* TScope: */ key, + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictScopeStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs new file mode 100644 index 00000000..aefd8ddd --- /dev/null +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Entity; +using System.Text; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFramework +{ + /// + /// Exposes a method allowing to resolve a token store. + /// + public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictTokenStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TToken), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified token type is not compatible with the Entity Framework 6.x stores.") + .Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ") + .Append("'OpenIddictToken' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictToken' entity.") + .ToString()); + } + + return typeof(OpenIddictTokenStore<,,,,>).MakeGenericType( + /* TToken: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TAuthorization: */ root.GenericTypeArguments[2], + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictTokenStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index 75d0f3bc..8a330466 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -14,7 +14,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; using OpenIddict.Stores; diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 34b6629a..d1a141e3 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -14,7 +14,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; using OpenIddict.Stores; diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj index d0878369..99e6e93b 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj @@ -13,6 +13,7 @@ + diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs index 27b1944d..292a19d6 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs @@ -5,15 +5,11 @@ */ using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection.Extensions; -using OpenIddict.Core; using OpenIddict.EntityFrameworkCore; using OpenIddict.Models; @@ -26,8 +22,9 @@ namespace Microsoft.Extensions.DependencyInjection /// the entities MUST be derived from the models contained in the OpenIddict.Models package. /// /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AddEntityFrameworkCoreStores([NotNull] this OpenIddictBuilder builder) + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder AddEntityFrameworkCoreStores([NotNull] this OpenIddictCoreBuilder builder) where TContext : DbContext { if (builder == null) @@ -35,85 +32,15 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(builder)); } - Debug.Assert(builder.ApplicationType != null && - builder.AuthorizationType != null && - builder.ScopeType != null && - builder.TokenType != null, "The entity types exposed by OpenIddictBuilder shouldn't be null."); + builder.Services.TryAddScoped(typeof(OpenIddictApplicationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictScopeStore<,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictTokenStore<,,,,>)); - var application = FindGenericBaseType(builder.ApplicationType, typeof(OpenIddictApplication<,,>)); - if (application == null) - { - throw new InvalidOperationException("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictApplication entity."); - } - - var authorization = FindGenericBaseType(builder.AuthorizationType, typeof(OpenIddictAuthorization<,,>)); - if (authorization == null) - { - throw new InvalidOperationException("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictAuthorization entity."); - } - - var scope = FindGenericBaseType(builder.ScopeType, typeof(OpenIddictScope<>)); - if (scope == null) - { - throw new InvalidOperationException("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictScope entity."); - } - - var token = FindGenericBaseType(builder.TokenType, typeof(OpenIddictToken<,,>)); - if (token == null) - { - throw new InvalidOperationException("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictToken entity."); - } - - var converter = TypeDescriptor.GetConverter(application.GenericTypeArguments[0]); - if (converter == null || !converter.CanConvertFrom(typeof(string)) || - !converter.CanConvertTo(typeof(string))) - { - throw new InvalidOperationException("The specified entity key type is not supported."); - } - - // Register the application store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictApplicationStore<>).MakeGenericType(builder.ApplicationType), - typeof(OpenIddictApplicationStore<,,,,>).MakeGenericType( - /* TApplication: */ builder.ApplicationType, - /* TAuthorization: */ builder.AuthorizationType, - /* TToken: */ builder.TokenType, - /* TContext: */ typeof(TContext), - /* TKey: */ application.GenericTypeArguments[0])); - - // Register the authorization store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictAuthorizationStore<>).MakeGenericType(builder.AuthorizationType), - typeof(OpenIddictAuthorizationStore<,,,,>).MakeGenericType( - /* TAuthorization: */ builder.AuthorizationType, - /* TApplication: */ builder.ApplicationType, - /* TToken: */ builder.TokenType, - /* TContext: */ typeof(TContext), - /* TKey: */ authorization.GenericTypeArguments[0])); - - // Register the scope store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictScopeStore<>).MakeGenericType(builder.ScopeType), - typeof(OpenIddictScopeStore<,,>).MakeGenericType( - /* TScope: */ builder.ScopeType, - /* TContext: */ typeof(TContext), - /* TKey: */ scope.GenericTypeArguments[0])); - - // Register the token store in the DI container. - builder.Services.TryAddScoped( - typeof(IOpenIddictTokenStore<>).MakeGenericType(builder.TokenType), - typeof(OpenIddictTokenStore<,,,,>).MakeGenericType( - /* TToken: */ builder.TokenType, - /* TApplication: */ builder.ApplicationType, - /* TAuthorization: */ builder.AuthorizationType, - /* TContext: */ typeof(TContext), - /* TKey: */ token.GenericTypeArguments[0])); - - return builder; + return builder.ReplaceApplicationStoreResolver>() + .ReplaceAuthorizationStoreResolver>() + .ReplaceScopeStoreResolver>() + .ReplaceTokenStoreResolver>(); } /// @@ -123,12 +50,10 @@ namespace Microsoft.Extensions.DependencyInjection /// The builder used to configure the Entity Framework context. /// The Entity Framework context builder. public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) - { - return builder.UseOpenIddict(); - } + => builder.UseOpenIddict(); /// /// Registers the OpenIddict entity sets in the Entity Framework context @@ -136,13 +61,12 @@ namespace Microsoft.Extensions.DependencyInjection /// /// The builder used to configure the Entity Framework context. /// The Entity Framework context builder. - public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) where TKey : IEquatable - { - return builder.UseOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken, TKey>(); - } + public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) + where TKey : IEquatable + => builder.UseOpenIddict, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken, TKey>(); /// /// Registers the OpenIddict entity sets in the Entity Framework context @@ -175,12 +99,10 @@ namespace Microsoft.Extensions.DependencyInjection /// The builder used to configure the Entity Framework context. /// The Entity Framework context builder. public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) - { - return builder.UseOpenIddict(); - } + => builder.UseOpenIddict(); /// /// Registers the OpenIddict entity sets in the Entity Framework context @@ -189,12 +111,10 @@ namespace Microsoft.Extensions.DependencyInjection /// The builder used to configure the Entity Framework context. /// The Entity Framework context builder. public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) where TKey : IEquatable - { - return builder.UseOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken, TKey>(); - } + => builder.UseOpenIddict, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken, TKey>(); /// /// Registers the OpenIddict entity sets in the Entity Framework context @@ -313,28 +233,5 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } - - private static TypeInfo FindGenericBaseType(Type type, Type definition) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (definition == null) - { - throw new ArgumentNullException(nameof(definition)); - } - - for (var candidate = type.GetTypeInfo(); candidate != null; candidate = candidate.BaseType?.GetTypeInfo()) - { - if (candidate.IsGenericType && candidate.GetGenericTypeDefinition() == definition) - { - return candidate; - } - } - - return null; - } } } \ No newline at end of file diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs new file mode 100644 index 00000000..9f5c5e46 --- /dev/null +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Concurrent; +using System.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFrameworkCore +{ + /// + /// Exposes a method allowing to resolve an application store. + /// + public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictApplicationStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TApplication), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified application type is not compatible with the Entity Framework Core stores.") + .Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ") + .Append("'OpenIddictApplication' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictApplication' entity.") + .ToString()); + } + + return typeof(OpenIddictApplicationStore<,,,,>).MakeGenericType( + /* TApplication: */ key, + /* TAuthorization: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs new file mode 100644 index 00000000..a0fae2c4 --- /dev/null +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Concurrent; +using System.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFrameworkCore +{ + /// + /// Exposes a method allowing to resolve an authorization store. + /// + public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictAuthorizationStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TAuthorization), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified authorization type is not compatible with the Entity Framework Core stores.") + .Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ") + .Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictAuthorization' entity.") + .ToString()); + } + + return typeof(OpenIddictAuthorizationStore<,,,,>).MakeGenericType( + /* TAuthorization: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs new file mode 100644 index 00000000..d7dc2ed1 --- /dev/null +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Concurrent; +using System.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFrameworkCore +{ + /// + /// Exposes a method allowing to resolve a scope store. + /// + public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictScopeStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TScope), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified scope type is not compatible with the Entity Framework Core stores.") + .Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ") + .Append("'OpenIdScope' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictScope' entity.") + .ToString()); + } + + return typeof(OpenIddictScopeStore<,,>).MakeGenericType( + /* TScope: */ key, + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictScopeStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs new file mode 100644 index 00000000..f8f5da53 --- /dev/null +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Concurrent; +using System.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using OpenIddict.Models; + +namespace OpenIddict.EntityFrameworkCore +{ + /// + /// Exposes a method allowing to resolve a token store. + /// + public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver + where TContext : DbContext + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictTokenStoreResolver([NotNull] IServiceProvider provider) + { + _provider = provider; + } + + /// + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class + { + var store = _provider.GetService>(); + if (store != null) + { + return store; + } + + var type = _cache.GetOrAdd(typeof(TToken), key => + { + var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>)); + if (root == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified token type is not compatible with the Entity Framework Core stores.") + .Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ") + .Append("'OpenIddictToken' entity (from the 'OpenIddict.Models' package) or a custom entity ") + .Append("that inherits from the generic 'OpenIddictToken' entity.") + .ToString()); + } + + return typeof(OpenIddictTokenStore<,,,,>).MakeGenericType( + /* TToken: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TAuthorization: */ root.GenericTypeArguments[2], + /* TContext: */ typeof(TContext), + /* TKey: */ root.GenericTypeArguments[0]); + }); + + return (IOpenIddictTokenStore) _provider.GetRequiredService(type); + } + } +} diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 060d4352..74365a4d 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -16,7 +16,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; using OpenIddict.Stores; diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 31e1a500..a2a2c41a 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -16,6 +16,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using OpenIddict.Stores; diff --git a/src/OpenIddict.Mvc/OpenIddictExtensions.cs b/src/OpenIddict.Mvc/OpenIddictExtensions.cs index ca0a44fc..abe2f8d4 100644 --- a/src/OpenIddict.Mvc/OpenIddictExtensions.cs +++ b/src/OpenIddict.Mvc/OpenIddictExtensions.cs @@ -17,8 +17,9 @@ namespace Microsoft.Extensions.DependencyInjection /// Registers the ASP.NET Core MVC model binders used by OpenIddict. /// /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AddMvcBinders([NotNull] this OpenIddictBuilder builder) + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder AddMvcBinders([NotNull] this OpenIddictServerBuilder builder) { if (builder == null) { diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs similarity index 97% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Authentication.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs index 60949384..c78c3150 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs @@ -20,18 +20,19 @@ using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Bson; using Newtonsoft.Json.Linq; +using OpenIddict.Abstractions; using OpenIddict.Core; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ExtractAuthorizationRequest([NotNull] ExtractAuthorizationRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); // Reject requests using the unsupported request parameter. if (!string.IsNullOrEmpty(context.Request.Request)) @@ -112,10 +113,10 @@ namespace OpenIddict public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; var applications = context.HttpContext.RequestServices.GetRequiredService>(); - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); var scopes = context.HttpContext.RequestServices.GetRequiredService>(); // Note: the OpenID Connect server middleware supports authorization code, implicit, hybrid, @@ -422,7 +423,7 @@ namespace OpenIddict public override async Task HandleAuthorizationRequest([NotNull] HandleAuthorizationRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; // If no request_id parameter can be found in the current request, assume the OpenID Connect request // was not serialized yet and store the entire payload in the distributed cache to make it easier @@ -474,7 +475,7 @@ namespace OpenIddict public override async Task ApplyAuthorizationResponse([NotNull] ApplyAuthorizationResponseContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; // Remove the authorization request from the distributed cache. if (options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Discovery.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Discovery.cs similarity index 92% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Discovery.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Discovery.cs index 1913b03e..bc1d943f 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Discovery.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Discovery.cs @@ -10,16 +10,16 @@ using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task HandleConfigurationRequest([NotNull] HandleConfigurationRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; // Note: though it's natively supported by the OpenID Connect server middleware, // OpenIddict disallows the use of the unsecure code_challenge_method=plain method, diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Exchange.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Exchange.cs similarity index 97% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Exchange.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Exchange.cs index e402fdc9..b8a87b68 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Exchange.cs @@ -5,7 +5,6 @@ */ using System; -using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; @@ -14,19 +13,20 @@ using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using OpenIddict.Abstractions; using OpenIddict.Core; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; var applications = context.HttpContext.RequestServices.GetRequiredService>(); - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); var scopes = context.HttpContext.RequestServices.GetRequiredService>(); // Reject token requests that don't specify a supported grant type. @@ -279,9 +279,9 @@ namespace OpenIddict public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); var tokens = context.HttpContext.RequestServices.GetRequiredService>(); if (context.Ticket != null) diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Helpers.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs similarity index 96% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Helpers.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs index 0ac4a608..44fa02f8 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Helpers.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs @@ -20,20 +20,21 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json.Linq; +using OpenIddict.Abstractions; using OpenIddict.Core; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { private async Task CreateAuthorizationAsync( - [NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictOptions options, + [NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictServerOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request) { var applications = context.RequestServices.GetRequiredService>(); var authorizations = context.RequestServices.GetRequiredService>(); - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); var descriptor = new OpenIddictAuthorizationDescriptor { @@ -88,7 +89,7 @@ namespace OpenIddict private async Task CreateTokenAsync( [NotNull] string type, [NotNull] AuthenticationTicket ticket, - [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, + [NotNull] OpenIddictServerOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, [NotNull] ISecureDataFormat format) { @@ -101,7 +102,7 @@ namespace OpenIddict "Only authorization codes, access and refresh tokens should be created using this method."); var applications = context.RequestServices.GetRequiredService>(); - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed @@ -226,11 +227,11 @@ namespace OpenIddict private async Task ReceiveTokenAsync( [NotNull] string type, [NotNull] string value, - [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, + [NotNull] OpenIddictServerOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, [NotNull] ISecureDataFormat format) { - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken || @@ -380,7 +381,7 @@ namespace OpenIddict private async Task TryRevokeAuthorizationAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context) { var authorizations = context.RequestServices.GetRequiredService>(); - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); // Note: if the authorization identifier or the authorization itself // cannot be found, return true as the authorization doesn't need @@ -419,7 +420,7 @@ namespace OpenIddict private async Task TryRevokeTokenAsync([NotNull] TToken token, [NotNull] HttpContext context) { - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); var identifier = await tokens.GetIdAsync(token); @@ -474,7 +475,7 @@ namespace OpenIddict private async Task TryRedeemTokenAsync([NotNull] TToken token, [NotNull] HttpContext context) { - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); var identifier = await tokens.GetIdAsync(token); @@ -503,9 +504,9 @@ namespace OpenIddict private async Task TryExtendTokenAsync( [NotNull] TToken token, [NotNull] AuthenticationTicket ticket, - [NotNull] HttpContext context, [NotNull] OpenIddictOptions options) + [NotNull] HttpContext context, [NotNull] OpenIddictServerOptions options) { - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); var identifier = ticket.GetTokenId(); @@ -540,7 +541,7 @@ namespace OpenIddict [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, [NotNull] AuthenticationProperties properties) { - var logger = context.RequestServices.GetRequiredService>>(); + var logger = context.RequestServices.GetRequiredService>>(); Debug.Assert(properties != null, "The authentication properties shouldn't be null."); diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Introspection.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Introspection.cs similarity index 94% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Introspection.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Introspection.cs index 150d18c5..896f1618 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Introspection.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Introspection.cs @@ -13,11 +13,12 @@ using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using OpenIddict.Abstractions; using OpenIddict.Core; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task ExtractIntrospectionRequest([NotNull] ExtractIntrospectionRequestContext context) @@ -39,7 +40,7 @@ namespace OpenIddict public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) { var applications = context.HttpContext.RequestServices.GetRequiredService>(); - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); // Note: the OpenID Connect server middleware supports unauthenticated introspection requests // but OpenIddict uses a stricter policy preventing unauthenticated/public applications @@ -116,9 +117,9 @@ namespace OpenIddict public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); var tokens = context.HttpContext.RequestServices.GetRequiredService>(); Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Revocation.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Revocation.cs similarity index 95% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Revocation.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Revocation.cs index 64e1ed4e..aedfac50 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Revocation.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Revocation.cs @@ -13,19 +13,20 @@ using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using OpenIddict.Abstractions; using OpenIddict.Core; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ValidateRevocationRequest([NotNull] ValidateRevocationRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; var applications = context.HttpContext.RequestServices.GetRequiredService>(); - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); Debug.Assert(!options.DisableTokenRevocation, "Token revocation support shouldn't be disabled at this stage."); @@ -167,9 +168,9 @@ namespace OpenIddict public override async Task HandleRevocationRequest([NotNull] HandleRevocationRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); var tokens = context.HttpContext.RequestServices.GetRequiredService>(); Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Serialization.cs similarity index 90% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Serialization.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Serialization.cs index a45f3119..55b08392 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Serialization.cs @@ -10,14 +10,14 @@ using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task DeserializeAccessToken([NotNull] DeserializeAccessTokenContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; if (options.DisableTokenRevocation) { return; @@ -38,7 +38,7 @@ namespace OpenIddict public override async Task DeserializeAuthorizationCode([NotNull] DeserializeAuthorizationCodeContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; if (options.DisableTokenRevocation) { return; @@ -55,7 +55,7 @@ namespace OpenIddict public override async Task DeserializeRefreshToken([NotNull] DeserializeRefreshTokenContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; if (options.DisableTokenRevocation) { return; @@ -72,7 +72,7 @@ namespace OpenIddict public override async Task SerializeAccessToken([NotNull] SerializeAccessTokenContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; if (options.DisableTokenRevocation) { return; @@ -97,7 +97,7 @@ namespace OpenIddict public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; if (options.DisableTokenRevocation) { return; @@ -124,7 +124,7 @@ namespace OpenIddict public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; if (options.DisableTokenRevocation) { return; diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Session.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs similarity index 94% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Session.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs index 25d97c17..160a4d85 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Session.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs @@ -19,18 +19,19 @@ using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Bson; using Newtonsoft.Json.Linq; +using OpenIddict.Abstractions; using OpenIddict.Core; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override async Task ExtractLogoutRequest([NotNull] ExtractLogoutRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); // If a request_id parameter can be found in the logout request, // restore the complete logout request from the distributed cache. @@ -86,7 +87,7 @@ namespace OpenIddict public override async Task ValidateLogoutRequest([NotNull] ValidateLogoutRequestContext context) { var applications = context.HttpContext.RequestServices.GetRequiredService>(); - var logger = context.HttpContext.RequestServices.GetRequiredService>>(); + var logger = context.HttpContext.RequestServices.GetRequiredService>>(); // If an optional post_logout_redirect_uri was provided, validate it. if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri)) @@ -133,7 +134,7 @@ namespace OpenIddict public override async Task HandleLogoutRequest([NotNull] HandleLogoutRequestContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; // If no request_id parameter can be found in the current request, assume the OpenID Connect // request was not serialized yet and store the entire payload in the distributed cache @@ -183,7 +184,7 @@ namespace OpenIddict public override async Task ApplyLogoutResponse([NotNull] ApplyLogoutResponseContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; // Remove the logout request from the distributed cache. if (options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.Userinfo.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Userinfo.cs similarity index 89% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.Userinfo.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.Userinfo.cs index 214cf1b7..7a72921a 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.Userinfo.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Userinfo.cs @@ -8,9 +8,9 @@ using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; -namespace OpenIddict +namespace OpenIddict.Server { - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task ExtractUserinfoRequest([NotNull] ExtractUserinfoRequestContext context) diff --git a/src/OpenIddict.Server/Internal/OpenIddictProvider.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs similarity index 97% rename from src/OpenIddict.Server/Internal/OpenIddictProvider.cs rename to src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs index 3e0115e8..9e264241 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictProvider.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs @@ -13,12 +13,12 @@ using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.AspNetCore.Authentication; -using OpenIddict.Core; +using OpenIddict.Abstractions; -namespace OpenIddict +namespace OpenIddict.Server { [EditorBrowsable(EditorBrowsableState.Never)] - public partial class OpenIddictProvider : OpenIdConnectServerProvider + public partial class OpenIddictServerProvider : OpenIdConnectServerProvider where TApplication : class where TAuthorization : class where TScope : class where TToken : class { public override Task ProcessChallengeResponse([NotNull] ProcessChallengeResponseContext context) @@ -40,7 +40,7 @@ namespace OpenIddict public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context) { - var options = (OpenIddictOptions) context.Options; + var options = (OpenIddictServerOptions) context.Options; Debug.Assert(context.Request.IsAuthorizationRequest() || context.Request.IsTokenRequest(), diff --git a/src/OpenIddict.Server/OpenIddictExtensions.cs b/src/OpenIddict.Server/OpenIddictExtensions.cs deleted file mode 100644 index 9b604b48..00000000 --- a/src/OpenIddict.Server/OpenIddictExtensions.cs +++ /dev/null @@ -1,1069 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using AspNet.Security.OpenIdConnect.Primitives; -using AspNet.Security.OpenIdConnect.Server; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Authentication; -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; -using OpenIddict; -using OpenIddict.Core; - -namespace Microsoft.AspNetCore.Builder -{ - public static class OpenIddictExtensions - { - /// - /// Registers OpenIddict in the ASP.NET Core pipeline. - /// - /// The application builder used to register middleware instances. - /// The . - public static IApplicationBuilder UseOpenIddict([NotNull] this IApplicationBuilder app) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - // Resolve the OpenIddict builder from the DI container. - // If it cannot be found, throw an invalid operation exception. - var builder = app.ApplicationServices.GetService(); - if (builder == null) - { - throw new InvalidOperationException("The OpenIddict services cannot be resolved from the dependency injection container. " + - "Make sure 'services.AddOpenIddict()' is correctly called from 'ConfigureServices()'."); - } - - // Resolve the OpenIddict options from the DI container. - var options = app.ApplicationServices.GetRequiredService>().Value; - - // When no authorization provider has been registered in the options, - // create a new OpenIddictProvider instance using the specified entities. - if (options.Provider == null) - { - options.Provider = (OpenIdConnectServerProvider) Activator.CreateInstance( - typeof(OpenIddictProvider<,,,>).MakeGenericType( - /* TApplication: */ builder.ApplicationType, - /* TAuthorization: */ builder.AuthorizationType, - /* TScope: */ builder.ScopeType, - /* TToken: */ builder.TokenType)); - } - - // When no distributed cache has been registered in the options, use the - // global instance registered in the dependency injection container. - if (options.Cache == null) - { - options.Cache = app.ApplicationServices.GetRequiredService(); - } - - // If OpenIddict was configured to use reference tokens, replace the default access tokens/ - // authorization codes/refresh tokens formats using a specific data protector to ensure - // that encrypted tokens stored in the database cannot be treated as valid tokens if the - // reference tokens option is later turned off by the developer. - if (options.UseReferenceTokens) - { - // Note: a default data protection provider is always registered by - // the OpenID Connect server handler when none is explicitly set but - // this initializer is registered to be invoked before ASOS' initializer. - // To ensure the provider property is never null, it's manually set here. - if (options.DataProtectionProvider == null) - { - options.DataProtectionProvider = app.ApplicationServices.GetDataProtectionProvider(); - } - - if (options.AccessTokenFormat == null) - { - var protector = options.DataProtectionProvider.CreateProtector( - nameof(OpenIdConnectServerHandler), - nameof(options.AccessTokenFormat), - nameof(options.UseReferenceTokens), - options.AuthenticationScheme); - - options.AccessTokenFormat = new TicketDataFormat(protector); - } - - if (options.AuthorizationCodeFormat == null) - { - var protector = options.DataProtectionProvider.CreateProtector( - nameof(OpenIdConnectServerHandler), - nameof(options.AuthorizationCodeFormat), - nameof(options.UseReferenceTokens), - options.AuthenticationScheme); - - options.AuthorizationCodeFormat = new TicketDataFormat(protector); - } - - if (options.RefreshTokenFormat == null) - { - var protector = options.DataProtectionProvider.CreateProtector( - nameof(OpenIdConnectServerHandler), - nameof(options.RefreshTokenFormat), - nameof(options.UseReferenceTokens), - options.AuthenticationScheme); - - options.RefreshTokenFormat = new TicketDataFormat(protector); - } - } - - // Ensure at least one flow has been enabled. - if (options.GrantTypes.Count == 0) - { - throw new InvalidOperationException("At least one OAuth2/OpenID Connect flow must be enabled."); - } - - // Ensure the authorization endpoint has been enabled when - // the authorization code or implicit grants are supported. - if (!options.AuthorizationEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || - options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit))) - { - throw new InvalidOperationException("The authorization endpoint must be enabled to use " + - "the authorization code and implicit flows."); - } - - // Ensure the token endpoint has been enabled when the authorization code, - // client credentials, password or refresh token grants are supported. - if (!options.TokenEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || - options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials) || - 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."); - } - - if (options.RevocationEndpointPath.HasValue && options.DisableTokenRevocation) - { - throw new InvalidOperationException("The revocation endpoint cannot be enabled when token revocation is disabled."); - } - - if (options.UseReferenceTokens && options.DisableTokenRevocation) - { - throw new InvalidOperationException( - "Reference tokens cannot be used when disabling token revocation."); - } - - 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.DisableTokenRevocation && !options.UseRollingTokens) - { - throw new InvalidOperationException("Sliding expiration must be disabled when turning off " + - "token revocation if rolling tokens are not used."); - } - - // 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."); - } - - // Automatically add the offline_access scope if the refresh token grant has been enabled. - if (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) - { - options.Scopes.Add(OpenIdConnectConstants.Scopes.OfflineAccess); - } - - return app.UseOpenIdConnectServer(options); - } - - /// - /// Amends the default OpenIddict configuration. - /// - /// The services builder used by OpenIddict to register new services. - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictBuilder Configure( - [NotNull] this OpenIddictBuilder builder, - [NotNull] Action configuration) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - builder.Services.Configure(configuration); - - return builder; - } - - /// - /// Registers a new ephemeral key used to sign the JWT tokens issued by OpenIddict: the key - /// is discarded when the application shuts down and tokens signed using this key are - /// automatically invalidated. This method should only be used during development. - /// On production, using a X.509 certificate stored in the machine store is recommended. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AddEphemeralSigningKey([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.SigningCredentials.AddEphemeralKey()); - } - - /// - /// Registers a new ephemeral key used to sign the JWT tokens issued by OpenIddict: the key - /// is discarded when the application shuts down and tokens signed using this key are - /// automatically invalidated. This method should only be used during development. - /// On production, using a X.509 certificate stored in the machine store is recommended. - /// - /// The services builder used by OpenIddict to register new services. - /// The algorithm associated with the signing key. - /// The . - public static OpenIddictBuilder AddEphemeralSigningKey( - [NotNull] this OpenIddictBuilder builder, [NotNull] string algorithm) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (string.IsNullOrEmpty(algorithm)) - { - throw new ArgumentException("The algorithm cannot be null or empty.", nameof(algorithm)); - } - - return builder.Configure(options => options.SigningCredentials.AddEphemeralKey(algorithm)); - } - - /// - /// Registers a that is used to sign the JWT tokens issued by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// The certificate used to sign the security tokens issued by the server. - /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, - [NotNull] X509Certificate2 certificate) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (certificate == null) - { - throw new ArgumentNullException(nameof(certificate)); - } - - if (!certificate.HasPrivateKey) - { - throw new InvalidOperationException("The certificate doesn't contain the required private key."); - } - - return builder.Configure(options => options.SigningCredentials.AddCertificate(certificate)); - } - - /// - /// Registers a retrieved from an - /// embedded resource and used to sign the JWT tokens issued by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, [NotNull] Assembly assembly, - [NotNull] string resource, [NotNull] string password) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (assembly == null) - { - throw new ArgumentNullException(nameof(assembly)); - } - - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentNullException(nameof(resource)); - } - - if (string.IsNullOrEmpty(password)) - { - throw new ArgumentNullException(nameof(password)); - } - - return builder.Configure(options => options.SigningCredentials.AddCertificate(assembly, resource, password)); - } - - /// - /// Registers a extracted from a - /// stream and used to sign the JWT tokens issued by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// The stream containing the certificate. - /// The password used to open the certificate. - /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, - [NotNull] Stream stream, [NotNull] string password) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (string.IsNullOrEmpty(password)) - { - throw new ArgumentNullException(nameof(password)); - } - - return builder.Configure(options => options.SigningCredentials.AddCertificate(stream, password)); - } - - /// - /// Registers a extracted from a - /// stream and used to sign the JWT tokens issued by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// The stream containing the certificate. - /// The password used to open the certificate. - /// - /// An enumeration of flags indicating how and where - /// to store the private key of the certificate. - /// - /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, [NotNull] Stream stream, - [NotNull] string password, X509KeyStorageFlags flags) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (string.IsNullOrEmpty(password)) - { - throw new ArgumentNullException(nameof(password)); - } - - return builder.Configure(options => options.SigningCredentials.AddCertificate(stream, password, flags)); - } - - /// - /// Registers a retrieved from the X.509 - /// machine store and used to sign the JWT tokens issued by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, [NotNull] string thumbprint) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentNullException(nameof(thumbprint)); - } - - return builder.Configure(options => options.SigningCredentials.AddCertificate(thumbprint)); - } - - /// - /// Registers a retrieved from the given - /// X.509 store and used to sign the JWT tokens issued by OpenIddict. - /// - /// The services builder used by OpenIddict to register new services. - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The name of the X.509 store. - /// The location of the X.509 store. - /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, - [NotNull] string thumbprint, StoreName name, StoreLocation location) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentNullException(nameof(thumbprint)); - } - - return builder.Configure(options => options.SigningCredentials.AddCertificate(thumbprint, name, location)); - } - - /// - /// Registers a used to sign the JWT tokens issued by OpenIddict. - /// Note: using asymmetric keys is recommended on production. - /// - /// The services builder used by OpenIddict to register new services. - /// The security key. - /// The . - public static OpenIddictBuilder AddSigningKey( - [NotNull] this OpenIddictBuilder builder, [NotNull] SecurityKey key) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - return builder.Configure(options => options.SigningCredentials.AddKey(key)); - } - - /// - /// Enables authorization code flow support. For more information - /// about this specific OAuth2/OpenID Connect flow, visit - /// https://tools.ietf.org/html/rfc6749#section-4.1 and - /// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AllowAuthorizationCodeFlow([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode)); - } - - /// - /// Enables client credentials flow support. For more information about this - /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AllowClientCredentialsFlow([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials)); - } - - /// - /// Enables custom grant type support. - /// - /// The services builder used by OpenIddict to register new services. - /// The grant type associated with the flow. - /// The . - public static OpenIddictBuilder AllowCustomFlow( - [NotNull] this OpenIddictBuilder builder, [NotNull] string type) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException("The grant type cannot be null or empty.", nameof(type)); - } - - return builder.Configure(options => options.GrantTypes.Add(type)); - } - - /// - /// Enables implicit flow support. For more information - /// about this specific OAuth2/OpenID Connect flow, visit - /// https://tools.ietf.org/html/rfc6749#section-4.2 and - /// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AllowImplicitFlow([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit)); - } - - /// - /// Enables password flow support. For more information about this specific - /// OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AllowPasswordFlow([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password)); - } - - /// - /// Enables refresh token flow support. For more information about this - /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-6. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder AllowRefreshTokenFlow([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken)); - } - - /// - /// Disables the configuration endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder DisableConfigurationEndpoint([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.ConfigurationEndpointPath = PathString.Empty); - } - - /// - /// Disables the cryptography endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder DisableCryptographyEndpoint([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.CryptographyEndpointPath = PathString.Empty); - } - - /// - /// Disables the HTTPS requirement during development. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder DisableHttpsRequirement([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.AllowInsecureHttp = true); - } - - /// - /// Disables sliding expiration. When using this option, refresh tokens - /// are issued with a fixed expiration date: when it expires, a complete - /// authorization flow must be started to retrieve a new refresh token. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder DisableSlidingExpiration([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.UseSlidingExpiration = false); - } - - /// - /// Disables token revocation, so that authorization code and - /// refresh tokens are never stored and cannot be revoked. - /// Using this option is generally not recommended. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder DisableTokenRevocation([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.DisableTokenRevocation = true); - } - - /// - /// Enables the authorization endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The relative path of the authorization endpoint. - /// The . - public static OpenIddictBuilder EnableAuthorizationEndpoint( - [NotNull] this OpenIddictBuilder builder, PathString path) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (!path.HasValue) - { - throw new ArgumentException("The path cannot be empty.", nameof(path)); - } - - return builder.Configure(options => options.AuthorizationEndpointPath = path); - } - - /// - /// Enables the introspection endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The relative path of the logout endpoint. - /// The . - public static OpenIddictBuilder EnableIntrospectionEndpoint( - [NotNull] this OpenIddictBuilder builder, PathString path) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (!path.HasValue) - { - throw new ArgumentException("The path cannot be empty.", nameof(path)); - } - - return builder.Configure(options => options.IntrospectionEndpointPath = path); - } - - /// - /// Enables the logout endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The relative path of the logout endpoint. - /// The . - public static OpenIddictBuilder EnableLogoutEndpoint( - [NotNull] this OpenIddictBuilder builder, PathString path) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (!path.HasValue) - { - throw new ArgumentException("The path cannot be empty.", nameof(path)); - } - - return builder.Configure(options => options.LogoutEndpointPath = path); - } - - /// - /// Enables request caching, so that both authorization and logout requests - /// are automatically stored in the distributed cache, which allows flowing - /// large payloads across requests. Enabling this option is recommended - /// when using external authentication providers or when large GET or POST - /// OpenID Connect authorization requests support is required. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder EnableRequestCaching([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.EnableRequestCaching = true); - } - - /// - /// Enables the revocation endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The relative path of the revocation endpoint. - /// The . - public static OpenIddictBuilder EnableRevocationEndpoint( - [NotNull] this OpenIddictBuilder builder, PathString path) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (!path.HasValue) - { - throw new ArgumentException("The path cannot be empty.", nameof(path)); - } - - return builder.Configure(options => options.RevocationEndpointPath = path); - } - - /// - /// Rejects authorization and token requests that specify scopes that have not been - /// registered using or - /// . - /// - /// The services builder used by OpenIddict to register new services. - public static OpenIddictBuilder EnableScopeValidation([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.EnableScopeValidation = true); - } - - /// - /// Enables the token endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The relative path of the token endpoint. - /// The . - public static OpenIddictBuilder EnableTokenEndpoint( - [NotNull] this OpenIddictBuilder builder, PathString path) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (!path.HasValue) - { - throw new ArgumentException("The path cannot be empty.", nameof(path)); - } - - return builder.Configure(options => options.TokenEndpointPath = path); - } - - /// - /// Enables the userinfo endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The relative path of the userinfo endpoint. - /// The . - public static OpenIddictBuilder EnableUserinfoEndpoint( - [NotNull] this OpenIddictBuilder builder, PathString path) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (!path.HasValue) - { - throw new ArgumentException("The path cannot be empty.", nameof(path)); - } - - return builder.Configure(options => options.UserinfoEndpointPath = path); - } - - /// - /// Makes client identification mandatory so that token and revocation - /// requests that don't specify a client_id are automatically rejected. - /// Note: enabling this option doesn't prevent public clients from using - /// the token and revocation endpoints, but specifying a client_id is required. - /// - /// The services builder used by OpenIddict to register new services. - public static OpenIddictBuilder RequireClientIdentification([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.RequireClientIdentification = true); - } - - /// - /// Sets the access token lifetime, after which client applications must retrieve - /// a new access token by making a grant_type=refresh_token token request - /// or a prompt=none authorization request, depending on the selected flow. - /// Using long-lived access tokens or tokens that never expire is not recommended. - /// - /// The services builder used by OpenIddict to register new services. - /// The access token lifetime. - /// The . - public static OpenIddictBuilder SetAccessTokenLifetime( - [NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.AccessTokenLifetime = lifetime); - } - - /// - /// Sets the authorization code lifetime, after which client applications - /// are unable to send a grant_type=authorization_code token request. - /// Using short-lived authorization codes is strongly recommended. - /// - /// The services builder used by OpenIddict to register new services. - /// The authorization code lifetime. - /// The . - public static OpenIddictBuilder SetAuthorizationCodeLifetime( - [NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.AuthorizationCodeLifetime = lifetime); - } - - /// - /// Sets the identity token lifetime, after which client - /// applications should refuse processing identity tokens. - /// - /// The services builder used by OpenIddict to register new services. - /// The identity token lifetime. - /// The . - public static OpenIddictBuilder SetIdentityTokenLifetime( - [NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.IdentityTokenLifetime = lifetime); - } - - /// - /// Sets the refresh token lifetime, after which client applications must get - /// a new authorization from the user. When sliding expiration is enabled, - /// a new refresh token is always issued to the client application, - /// which prolongs the validity period of the refresh token. - /// - /// The services builder used by OpenIddict to register new services. - /// The refresh token lifetime. - /// The . - public static OpenIddictBuilder SetRefreshTokenLifetime( - [NotNull] this OpenIddictBuilder builder, TimeSpan lifetime) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.RefreshTokenLifetime = lifetime); - } - - /// - /// Sets the issuer address, which is used as the base address - /// for the endpoint URIs returned from the discovery endpoint. - /// - /// The services builder used by OpenIddict to register new services. - /// The issuer address. - /// The . - public static OpenIddictBuilder SetIssuer( - [NotNull] this OpenIddictBuilder builder, [NotNull] Uri address) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } - - return builder.Configure(options => options.Issuer = address); - } - - /// - /// Registers the specified claims as supported claims so - /// they can be returned as part of the discovery document. - /// - /// The services builder used by OpenIddict to register new services. - /// The supported claims. - /// The . - public static OpenIddictBuilder RegisterClaims( - [NotNull] this OpenIddictBuilder builder, [NotNull] params string[] claims) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (claims == null) - { - throw new ArgumentNullException(nameof(claims)); - } - - if (claims.Any(claim => string.IsNullOrEmpty(claim))) - { - throw new ArgumentException("Claims cannot be null or empty.", nameof(claims)); - } - - return builder.Configure(options => options.Claims.UnionWith(claims)); - } - - /// - /// Registers the specified scopes as supported scopes so - /// they can be returned as part of the discovery document. - /// - /// The services builder used by OpenIddict to register new services. - /// The supported scopes. - /// The . - public static OpenIddictBuilder RegisterScopes( - [NotNull] this OpenIddictBuilder builder, [NotNull] params string[] scopes) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (scopes == null) - { - throw new ArgumentNullException(nameof(scopes)); - } - - if (scopes.Any(scope => string.IsNullOrEmpty(scope))) - { - throw new ArgumentException("Scopes cannot be null or empty.", nameof(scopes)); - } - - return builder.Configure(options => options.Scopes.UnionWith(scopes)); - } - - /// - /// Configures OpenIddict to use a specific data protection provider - /// instead of relying on the default instance provided by the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The data protection provider used to create token protectors. - /// The . - public static OpenIddictBuilder UseDataProtectionProvider( - [NotNull] this OpenIddictBuilder builder, [NotNull] IDataProtectionProvider provider) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (provider == null) - { - throw new ArgumentNullException(nameof(provider)); - } - - return builder.Configure(options => options.DataProtectionProvider = provider); - } - - /// - /// Sets JWT as the default token format for access tokens. - /// Note: this option cannot be used when using reference tokens. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder UseJsonWebTokens([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => - { - options.AccessTokenHandler = new JwtSecurityTokenHandler - { - InboundClaimTypeMap = new Dictionary(), - OutboundClaimTypeMap = new Dictionary() - }; - }); - } - - /// - /// Configures OpenIddict to use reference tokens, so that authorization codes, - /// access tokens and refresh tokens are stored as ciphertext in the database - /// (only an identifier is returned to the client application). Enabling this option - /// is useful to keep track of all the issued tokens, when storing a very large - /// number of claims in the authorization codes, access tokens and refresh tokens - /// or when immediate revocation of reference access tokens is desired. - /// Note: this option cannot be used when configuring JWT as the access token format. - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder UseReferenceTokens([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.UseReferenceTokens = true); - } - - /// - /// Configures OpenIddict to use rolling refresh tokens. When this option is enabled, - /// a new refresh token is always issued for each refresh token request (and the previous - /// one is automatically revoked unless token revocation was explicitly disabled). - /// - /// The services builder used by OpenIddict to register new services. - /// The . - public static OpenIddictBuilder UseRollingTokens([NotNull] this OpenIddictBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.Configure(options => options.UseRollingTokens = true); - } - } -} diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs new file mode 100644 index 00000000..366134fd --- /dev/null +++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs @@ -0,0 +1,614 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IdentityModel.Tokens.Jwt; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using AspNet.Security.OpenIdConnect.Primitives; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Tokens; +using OpenIddict.Core; +using OpenIddict.Server; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Exposes the necessary methods required to configure the OpenIddict server services. + /// + public class OpenIddictServerBuilder + { + /// + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictServerBuilder([NotNull] IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + Services = services; + } + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict server configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictServerBuilder Configure([NotNull] Action configuration) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + Services.Configure(configuration); + + return this; + } + + /// + /// Registers a new ephemeral key used to sign the JWT tokens issued by OpenIddict: the key + /// is discarded when the application shuts down and tokens signed using this key are + /// automatically invalidated. This method should only be used during development. + /// On production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The . + public OpenIddictServerBuilder AddEphemeralSigningKey() + => Configure(options => options.SigningCredentials.AddEphemeralKey()); + + /// + /// Registers a new ephemeral key used to sign the JWT tokens issued by OpenIddict: the key + /// is discarded when the application shuts down and tokens signed using this key are + /// automatically invalidated. This method should only be used during development. + /// On production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The algorithm associated with the signing key. + /// The . + public OpenIddictServerBuilder AddEphemeralSigningKey([NotNull] string algorithm) + { + if (string.IsNullOrEmpty(algorithm)) + { + throw new ArgumentException("The algorithm cannot be null or empty.", nameof(algorithm)); + } + + return Configure(options => options.SigningCredentials.AddEphemeralKey(algorithm)); + } + + /// + /// Registers a that is used to sign the JWT tokens issued by OpenIddict. + /// + /// The certificate used to sign the security tokens issued by the server. + /// The . + public OpenIddictServerBuilder AddSigningCertificate([NotNull] X509Certificate2 certificate) + { + if (certificate == null) + { + throw new ArgumentNullException(nameof(certificate)); + } + + if (!certificate.HasPrivateKey) + { + throw new InvalidOperationException("The certificate doesn't contain the required private key."); + } + + return Configure(options => options.SigningCredentials.AddCertificate(certificate)); + } + + /// + /// Registers a retrieved from an + /// embedded resource and used to sign the JWT tokens issued by OpenIddict. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// The . + public OpenIddictServerBuilder AddSigningCertificate( + [NotNull] Assembly assembly, [NotNull] string resource, [NotNull] string password) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (string.IsNullOrEmpty(resource)) + { + throw new ArgumentNullException(nameof(resource)); + } + + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentNullException(nameof(password)); + } + + return Configure(options => options.SigningCredentials.AddCertificate(assembly, resource, password)); + } + + /// + /// Registers a extracted from a + /// stream and used to sign the JWT tokens issued by OpenIddict. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// The . + public OpenIddictServerBuilder AddSigningCertificate([NotNull] Stream stream, [NotNull] string password) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentNullException(nameof(password)); + } + + return Configure(options => options.SigningCredentials.AddCertificate(stream, password)); + } + + /// + /// Registers a extracted from a + /// stream and used to sign the JWT tokens issued by OpenIddict. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// + /// An enumeration of flags indicating how and where + /// to store the private key of the certificate. + /// + /// The . + public OpenIddictServerBuilder AddSigningCertificate( + [NotNull] Stream stream, [NotNull] string password, X509KeyStorageFlags flags) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentNullException(nameof(password)); + } + + return Configure(options => options.SigningCredentials.AddCertificate(stream, password, flags)); + } + + /// + /// Registers a retrieved from the X.509 + /// machine store and used to sign the JWT tokens issued by OpenIddict. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The . + public OpenIddictServerBuilder AddSigningCertificate([NotNull] string thumbprint) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentNullException(nameof(thumbprint)); + } + + return Configure(options => options.SigningCredentials.AddCertificate(thumbprint)); + } + + /// + /// Registers a retrieved from the given + /// X.509 store and used to sign the JWT tokens issued by OpenIddict. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The name of the X.509 store. + /// The location of the X.509 store. + /// The . + public OpenIddictServerBuilder AddSigningCertificate( + [NotNull] string thumbprint, StoreName name, StoreLocation location) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentNullException(nameof(thumbprint)); + } + + return Configure(options => options.SigningCredentials.AddCertificate(thumbprint, name, location)); + } + + /// + /// Registers a used to sign the JWT tokens issued by OpenIddict. + /// Note: using asymmetric keys is recommended on production. + /// + /// The security key. + /// The . + public OpenIddictServerBuilder AddSigningKey([NotNull] SecurityKey key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return Configure(options => options.SigningCredentials.AddKey(key)); + } + + /// + /// Enables authorization code flow support. For more information + /// about this specific OAuth2/OpenID Connect flow, visit + /// https://tools.ietf.org/html/rfc6749#section-4.1 and + /// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. + /// + /// The . + public OpenIddictServerBuilder AllowAuthorizationCodeFlow() + => Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode)); + + /// + /// Enables client credentials flow support. For more information about this + /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. + /// + /// The . + public OpenIddictServerBuilder AllowClientCredentialsFlow() + => Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials)); + + /// + /// Enables custom grant type support. + /// + /// The grant type associated with the flow. + /// The . + public OpenIddictServerBuilder AllowCustomFlow([NotNull] string type) + { + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException("The grant type cannot be null or empty.", nameof(type)); + } + + return Configure(options => options.GrantTypes.Add(type)); + } + + /// + /// Enables implicit flow support. For more information + /// about this specific OAuth2/OpenID Connect flow, visit + /// https://tools.ietf.org/html/rfc6749#section-4.2 and + /// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth. + /// + /// The . + public OpenIddictServerBuilder AllowImplicitFlow() + => Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit)); + + /// + /// Enables password flow support. For more information about this specific + /// OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. + /// + /// The . + public OpenIddictServerBuilder AllowPasswordFlow() + => Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password)); + + /// + /// Enables refresh token flow support. For more information about this + /// specific OAuth2 flow, visit https://tools.ietf.org/html/rfc6749#section-6. + /// + /// The . + public OpenIddictServerBuilder AllowRefreshTokenFlow() + => Configure(options => options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken)); + + /// + /// Disables the configuration endpoint. + /// + /// The . + public OpenIddictServerBuilder DisableConfigurationEndpoint() + => Configure(options => options.ConfigurationEndpointPath = PathString.Empty); + + /// + /// Disables the cryptography endpoint. + /// + /// The . + public OpenIddictServerBuilder DisableCryptographyEndpoint() + => Configure(options => options.CryptographyEndpointPath = PathString.Empty); + + /// + /// Disables the HTTPS requirement during development. + /// + /// The . + public OpenIddictServerBuilder DisableHttpsRequirement() + => Configure(options => options.AllowInsecureHttp = true); + + /// + /// Disables sliding expiration. When using this option, refresh tokens + /// are issued with a fixed expiration date: when it expires, a complete + /// authorization flow must be started to retrieve a new refresh token. + /// + /// The . + public OpenIddictServerBuilder DisableSlidingExpiration() + => Configure(options => options.UseSlidingExpiration = false); + + /// + /// Disables token revocation, so that authorization code and + /// refresh tokens are never stored and cannot be revoked. + /// Using this option is generally not recommended. + /// + /// The . + public OpenIddictServerBuilder DisableTokenRevocation() + => Configure(options => options.DisableTokenRevocation = true); + + /// + /// Enables the authorization endpoint. + /// + /// The relative path of the authorization endpoint. + /// The . + public OpenIddictServerBuilder EnableAuthorizationEndpoint(PathString path) + { + if (!path.HasValue) + { + throw new ArgumentException("The path cannot be empty.", nameof(path)); + } + + return Configure(options => options.AuthorizationEndpointPath = path); + } + + /// + /// Enables the introspection endpoint. + /// + /// The relative path of the logout endpoint. + /// The . + public OpenIddictServerBuilder EnableIntrospectionEndpoint(PathString path) + { + if (!path.HasValue) + { + throw new ArgumentException("The path cannot be empty.", nameof(path)); + } + + return Configure(options => options.IntrospectionEndpointPath = path); + } + + /// + /// Enables the logout endpoint. + /// + /// The relative path of the logout endpoint. + /// The . + public OpenIddictServerBuilder EnableLogoutEndpoint(PathString path) + { + if (!path.HasValue) + { + throw new ArgumentException("The path cannot be empty.", nameof(path)); + } + + return Configure(options => options.LogoutEndpointPath = path); + } + + /// + /// Enables request caching, so that both authorization and logout requests + /// are automatically stored in the distributed cache, which allows flowing + /// large payloads across requests. Enabling this option is recommended + /// when using external authentication providers or when large GET or POST + /// OpenID Connect authorization requests support is required. + /// + /// The . + public OpenIddictServerBuilder EnableRequestCaching() + => Configure(options => options.EnableRequestCaching = true); + + /// + /// Enables the revocation endpoint. + /// + /// The relative path of the revocation endpoint. + /// The . + public OpenIddictServerBuilder EnableRevocationEndpoint( PathString path) + { + if (!path.HasValue) + { + throw new ArgumentException("The path cannot be empty.", nameof(path)); + } + + return Configure(options => options.RevocationEndpointPath = path); + } + + /// + /// Rejects authorization and token requests that specify scopes that have not been + /// registered using or + /// . + /// + /// The . + public OpenIddictServerBuilder EnableScopeValidation() + => Configure(options => options.EnableScopeValidation = true); + + /// + /// Enables the token endpoint. + /// + /// The relative path of the token endpoint. + /// The . + public OpenIddictServerBuilder EnableTokenEndpoint(PathString path) + { + if (!path.HasValue) + { + throw new ArgumentException("The path cannot be empty.", nameof(path)); + } + + return Configure(options => options.TokenEndpointPath = path); + } + + /// + /// Enables the userinfo endpoint. + /// + /// The relative path of the userinfo endpoint. + /// The . + public OpenIddictServerBuilder EnableUserinfoEndpoint(PathString path) + { + if (!path.HasValue) + { + throw new ArgumentException("The path cannot be empty.", nameof(path)); + } + + return Configure(options => options.UserinfoEndpointPath = path); + } + + /// + /// Makes client identification mandatory so that token and revocation + /// requests that don't specify a client_id are automatically rejected. + /// Note: enabling this option doesn't prevent public clients from using + /// the token and revocation endpoints, but specifying a client_id is required. + /// + /// The . + public OpenIddictServerBuilder RequireClientIdentification() + => Configure(options => options.RequireClientIdentification = true); + + /// + /// Sets the access token lifetime, after which client applications must retrieve + /// a new access token by making a grant_type=refresh_token token request + /// or a prompt=none authorization request, depending on the selected flow. + /// Using long-lived access tokens or tokens that never expire is not recommended. + /// + /// The access token lifetime. + /// The . + public OpenIddictServerBuilder SetAccessTokenLifetime(TimeSpan lifetime) + => Configure(options => options.AccessTokenLifetime = lifetime); + + /// + /// Sets the authorization code lifetime, after which client applications + /// are unable to send a grant_type=authorization_code token request. + /// Using short-lived authorization codes is strongly recommended. + /// + /// The authorization code lifetime. + /// The . + public OpenIddictServerBuilder SetAuthorizationCodeLifetime(TimeSpan lifetime) + => Configure(options => options.AuthorizationCodeLifetime = lifetime); + + /// + /// Sets the identity token lifetime, after which client + /// applications should refuse processing identity tokens. + /// + /// The identity token lifetime. + /// The . + public OpenIddictServerBuilder SetIdentityTokenLifetime(TimeSpan lifetime) + => Configure(options => options.IdentityTokenLifetime = lifetime); + + /// + /// Sets the refresh token lifetime, after which client applications must get + /// a new authorization from the user. When sliding expiration is enabled, + /// a new refresh token is always issued to the client application, + /// which prolongs the validity period of the refresh token. + /// + /// The refresh token lifetime. + /// The . + public OpenIddictServerBuilder SetRefreshTokenLifetime(TimeSpan lifetime) + => Configure(options => options.RefreshTokenLifetime = lifetime); + + /// + /// Sets the issuer address, which is used as the base address + /// for the endpoint URIs returned from the discovery endpoint. + /// + /// The issuer address. + /// The . + public OpenIddictServerBuilder SetIssuer([NotNull] Uri address) + { + if (address == null) + { + throw new ArgumentNullException(nameof(address)); + } + + return Configure(options => options.Issuer = address); + } + + /// + /// Registers the specified claims as supported claims so + /// they can be returned as part of the discovery document. + /// + /// The supported claims. + /// The . + public OpenIddictServerBuilder RegisterClaims([NotNull] params string[] claims) + { + if (claims == null) + { + throw new ArgumentNullException(nameof(claims)); + } + + if (claims.Any(claim => string.IsNullOrEmpty(claim))) + { + throw new ArgumentException("Claims cannot be null or empty.", nameof(claims)); + } + + return Configure(options => options.Claims.UnionWith(claims)); + } + + /// + /// Registers the specified scopes as supported scopes so + /// they can be returned as part of the discovery document. + /// + /// The supported scopes. + /// The . + public OpenIddictServerBuilder RegisterScopes([NotNull] params string[] scopes) + { + if (scopes == null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + if (scopes.Any(scope => string.IsNullOrEmpty(scope))) + { + throw new ArgumentException("Scopes cannot be null or empty.", nameof(scopes)); + } + + return Configure(options => options.Scopes.UnionWith(scopes)); + } + + /// + /// Configures OpenIddict to use a specific data protection provider + /// instead of relying on the default instance provided by the DI container. + /// + /// The data protection provider used to create token protectors. + /// The . + public OpenIddictServerBuilder UseDataProtectionProvider([NotNull] IDataProtectionProvider provider) + { + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + return Configure(options => options.DataProtectionProvider = provider); + } + + /// + /// Sets JWT as the default token format for access tokens. + /// Note: this option cannot be used when using reference tokens. + /// + /// The . + public OpenIddictServerBuilder UseJsonWebTokens() + => Configure(options => options.AccessTokenHandler = new JwtSecurityTokenHandler + { + InboundClaimTypeMap = new Dictionary(), + OutboundClaimTypeMap = new Dictionary() + }); + + /// + /// Configures OpenIddict to use reference tokens, so that authorization codes, + /// access tokens and refresh tokens are stored as ciphertext in the database + /// (only an identifier is returned to the client application). Enabling this option + /// is useful to keep track of all the issued tokens, when storing a very large + /// number of claims in the authorization codes, access tokens and refresh tokens + /// or when immediate revocation of reference access tokens is desired. + /// Note: this option cannot be used when configuring JWT as the access token format. + /// + /// The . + public OpenIddictServerBuilder UseReferenceTokens() + => Configure(options => options.UseReferenceTokens = true); + + /// + /// Configures OpenIddict to use rolling refresh tokens. When this option is enabled, + /// a new refresh token is always issued for each refresh token request (and the previous + /// one is automatically revoked unless token revocation was explicitly disabled). + /// + /// The . + public OpenIddictServerBuilder UseRollingTokens() + => Configure(options => options.UseRollingTokens = true); + } +} \ No newline at end of file diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs new file mode 100644 index 00000000..7c5d01d9 --- /dev/null +++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs @@ -0,0 +1,251 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Linq; +using System.Text; +using AspNet.Security.OpenIdConnect.Primitives; +using AspNet.Security.OpenIdConnect.Server; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using OpenIddict.Core; +using OpenIddict.Server; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class OpenIddictServerExtensions + { + /// + /// Registers the OpenIddict token server services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder AddServer([NotNull] this OpenIddictBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Services.AddAuthentication(); + + return new OpenIddictServerBuilder(builder.Services); + } + + /// + /// Registers the OpenIddict token server services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the server services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddServer( + [NotNull] this OpenIddictBuilder builder, + [NotNull] Action configuration) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + configuration(builder.AddServer()); + + return builder; + } + + /// + /// Registers the OpenIddict server middleware in the ASP.NET Core pipeline. + /// + /// The application builder used to register middleware instances. + /// The . + public static IApplicationBuilder UseOpenIddictServer([NotNull] this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + var configuration = app.ApplicationServices.GetRequiredService>().Value; + + // Resolve the OpenIddict options from the DI container. + var options = app.ApplicationServices.GetRequiredService>().Value; + if (options.ApplicationType == null) + { + options.ApplicationType = configuration.DefaultApplicationType; + } + + if (options.AuthorizationType == null) + { + options.AuthorizationType = configuration.DefaultAuthorizationType; + } + + if (options.ScopeType == null) + { + options.ScopeType = configuration.DefaultScopeType; + } + + if (options.TokenType == null) + { + options.TokenType = configuration.DefaultTokenType; + } + + if (options.ApplicationType == null || options.AuthorizationType == null || + options.ScopeType == null || options.TokenType == null) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The entity types must be configured for the token server services to work correctly.") + .Append("To configure the entities, use either 'services.AddOpenIddict().AddCore().UseDefaultModels()' ") + .Append("or 'services.AddOpenIddict().AddCore().UseCustomModels()'.") + .ToString()); + } + + // When no authorization provider has been registered in the options, + // create a new OpenIddictProvider instance using the specified entities. + if (options.Provider == null) + { + options.Provider = (OpenIdConnectServerProvider) Activator.CreateInstance( + typeof(OpenIddictServerProvider<,,,>).MakeGenericType( + /* TApplication: */ options.ApplicationType, + /* TAuthorization: */ options.AuthorizationType, + /* TScope: */ options.ScopeType, + /* TToken: */ options.TokenType)); + } + + // When no distributed cache has been registered in the options, use the + // global instance registered in the dependency injection container. + if (options.Cache == null) + { + options.Cache = app.ApplicationServices.GetRequiredService(); + } + + // If OpenIddict was configured to use reference tokens, replace the default access tokens/ + // authorization codes/refresh tokens formats using a specific data protector to ensure + // that encrypted tokens stored in the database cannot be treated as valid tokens if the + // reference tokens option is later turned off by the developer. + if (options.UseReferenceTokens) + { + // Note: a default data protection provider is always registered by + // the OpenID Connect server handler when none is explicitly set but + // this initializer is registered to be invoked before ASOS' initializer. + // To ensure the provider property is never null, it's manually set here. + if (options.DataProtectionProvider == null) + { + options.DataProtectionProvider = app.ApplicationServices.GetDataProtectionProvider(); + } + + if (options.AccessTokenFormat == null) + { + var protector = options.DataProtectionProvider.CreateProtector( + nameof(OpenIdConnectServerHandler), + nameof(options.AccessTokenFormat), + nameof(options.UseReferenceTokens), + options.AuthenticationScheme); + + options.AccessTokenFormat = new TicketDataFormat(protector); + } + + if (options.AuthorizationCodeFormat == null) + { + var protector = options.DataProtectionProvider.CreateProtector( + nameof(OpenIdConnectServerHandler), + nameof(options.AuthorizationCodeFormat), + nameof(options.UseReferenceTokens), + options.AuthenticationScheme); + + options.AuthorizationCodeFormat = new TicketDataFormat(protector); + } + + if (options.RefreshTokenFormat == null) + { + var protector = options.DataProtectionProvider.CreateProtector( + nameof(OpenIdConnectServerHandler), + nameof(options.RefreshTokenFormat), + nameof(options.UseReferenceTokens), + options.AuthenticationScheme); + + options.RefreshTokenFormat = new TicketDataFormat(protector); + } + } + + // Ensure at least one flow has been enabled. + if (options.GrantTypes.Count == 0) + { + throw new InvalidOperationException("At least one OAuth2/OpenID Connect flow must be enabled."); + } + + // Ensure the authorization endpoint has been enabled when + // the authorization code or implicit grants are supported. + if (!options.AuthorizationEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || + options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit))) + { + throw new InvalidOperationException("The authorization endpoint must be enabled to use " + + "the authorization code and implicit flows."); + } + + // Ensure the token endpoint has been enabled when the authorization code, + // client credentials, password or refresh token grants are supported. + if (!options.TokenEndpointPath.HasValue && (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || + options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials) || + 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."); + } + + if (options.RevocationEndpointPath.HasValue && options.DisableTokenRevocation) + { + throw new InvalidOperationException("The revocation endpoint cannot be enabled when token revocation is disabled."); + } + + if (options.UseReferenceTokens && options.DisableTokenRevocation) + { + throw new InvalidOperationException( + "Reference tokens cannot be used when disabling token revocation."); + } + + 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.DisableTokenRevocation && !options.UseRollingTokens) + { + throw new InvalidOperationException("Sliding expiration must be disabled when turning off " + + "token revocation if rolling tokens are not used."); + } + + // 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."); + } + + // Automatically add the offline_access scope if the refresh token grant has been enabled. + if (options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) + { + options.Scopes.Add(OpenIdConnectConstants.Scopes.OfflineAccess); + } + + return app.UseOpenIdConnectServer(options); + } + } +} diff --git a/src/OpenIddict.Server/OpenIddictOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs similarity index 84% rename from src/OpenIddict.Server/OpenIddictOptions.cs rename to src/OpenIddict.Server/OpenIddictServerOptions.cs index 8bfe1891..27b984eb 100644 --- a/src/OpenIddict.Server/OpenIddictOptions.cs +++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs @@ -11,18 +11,31 @@ using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; using Microsoft.Extensions.Caching.Distributed; -namespace OpenIddict +namespace OpenIddict.Server { /// /// Provides various settings needed to configure OpenIddict. /// - public class OpenIddictOptions : OpenIdConnectServerOptions + public class OpenIddictServerOptions : OpenIdConnectServerOptions { - public OpenIddictOptions() + /// + /// Creates a new instance of the class. + /// + public OpenIddictServerOptions() { Provider = null; } + /// + /// Gets or sets the type corresponding to the Application entity. + /// + public Type ApplicationType { get; set; } + + /// + /// Gets or sets the type corresponding to the Authorization entity. + /// + public Type AuthorizationType { get; set; } + /// /// Gets or sets the distributed cache used by OpenIddict. If no cache is explicitly /// provided, the cache registered in the dependency injection container is used. @@ -87,6 +100,16 @@ namespace OpenIddict OpenIdConnectConstants.Scopes.OpenId }; + /// + /// Gets or sets the type corresponding to the Scope entity. + /// + public Type ScopeType { get; set; } + + /// + /// Gets or sets the type corresponding to the Token entity. + /// + public Type TokenType { get; set; } + /// /// Gets or sets a boolean indicating whether reference tokens should be used. /// When set to true, authorization codes, access tokens and refresh tokens diff --git a/src/OpenIddict.Stores/OpenIddict.Stores.csproj b/src/OpenIddict.Stores/OpenIddict.Stores.csproj index aed6ec69..8d25b899 100644 --- a/src/OpenIddict.Stores/OpenIddict.Stores.csproj +++ b/src/OpenIddict.Stores/OpenIddict.Stores.csproj @@ -13,12 +13,18 @@ - + + + + + + + diff --git a/src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs index 8ea5f450..34900246 100644 --- a/src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs @@ -15,7 +15,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; namespace OpenIddict.Stores diff --git a/src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs index 30d4d477..a8683670 100644 --- a/src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs @@ -15,7 +15,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; namespace OpenIddict.Stores diff --git a/src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs index 4ebd8b83..379c8d8a 100644 --- a/src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs @@ -15,7 +15,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; namespace OpenIddict.Stores diff --git a/src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs index 83831467..68caacc8 100644 --- a/src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs @@ -15,7 +15,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; using OpenIddict.Models; namespace OpenIddict.Stores diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 1338e89e..d7ea853a 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -13,69 +13,45 @@ namespace Microsoft.Extensions.DependencyInjection public static class OpenIddictExtensions { /// - /// Registers the default OpenIddict services in the DI container, - /// using the default entities and the default entity key type. + /// Configures OpenIddict to use the default entities, with the default entity key type (string). + /// The default entities are , , + /// and . /// - /// The services collection. - /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) + /// The services builder used by OpenIddict to register new services + /// The . + public static OpenIddictCoreBuilder UseDefaultModels([NotNull] this OpenIddictCoreBuilder builder) { - return services.AddOpenIddict(); - } - - /// - /// Registers the default OpenIddict services in the DI container, - /// using the default entities and the specified entity key type. - /// - /// The type of the entity primary keys. - /// The services collection. - /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - where TKey : IEquatable - { - return services.AddOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken>(); - } + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } - /// - /// Registers the default OpenIddict services in the DI container, - /// using the default entities and the default entity key type. - /// - /// The services collection. - /// The configuration delegate used to register new services. - /// The . - public static IServiceCollection AddOpenIddict( - [NotNull] this IServiceCollection services, - [NotNull] Action configuration) - { - return services.AddOpenIddict(configuration); + return builder.UseCustomModels(); } /// - /// Registers the default OpenIddict services in the DI container, - /// using the default entities and the specified entity key type. + /// Configures OpenIddict to use the default entities, with the specified entity key type. + /// The default entities are , , + /// and . /// /// The type of the entity primary keys. - /// The services collection. - /// The configuration delegate used to register new services. - /// The . - public static IServiceCollection AddOpenIddict( - [NotNull] this IServiceCollection services, - [NotNull] Action configuration) + /// The services builder used by OpenIddict to register new services + /// The . + public static OpenIddictCoreBuilder UseDefaultModels([NotNull] this OpenIddictCoreBuilder builder) where TKey : IEquatable { - return services.AddOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken>(configuration); + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.UseCustomModels, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken>(); } } -} \ No newline at end of file +} diff --git a/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs b/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs deleted file mode 100644 index c547ad8e..00000000 --- a/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Xunit; - -namespace OpenIddict.Core.Tests -{ - public class OpenIddictBuilderTests - { - [Fact] - public void AddApplicationManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ApplicationType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddApplicationManager(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddApplicationManager_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ApplicationType = typeof(object); - - var type = new Mock>( - Mock.Of>(), - Mock.Of>>()).Object.GetType(); - - // Act - builder.AddApplicationManager(type); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, manager); - } - - [Fact] - public void AddApplicationStore_ThrowsAnExceptionForInvalidStore() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ApplicationType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddApplicationStore(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddApplicationStore_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ApplicationType = typeof(object); - - var type = Mock.Of>().GetType(); - - // Act - builder.AddApplicationStore(type); - - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, store); - } - - [Fact] - public void AddAuthorizationManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.AuthorizationType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddAuthorizationManager(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddAuthorizationManager_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.AuthorizationType = typeof(object); - - var type = new Mock>( - Mock.Of>(), - Mock.Of>>()).Object.GetType(); - - // Act - builder.AddAuthorizationManager(type); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, manager); - } - - [Fact] - public void AddAuthorizationStore_ThrowsAnExceptionForInvalidStore() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.AuthorizationType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddAuthorizationStore(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddAuthorizationStore_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.AuthorizationType = typeof(object); - - var type = Mock.Of>().GetType(); - - // Act - builder.AddAuthorizationStore(type); - - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, store); - } - - [Fact] - public void AddScopeManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ScopeType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddScopeManager(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddScopeManager_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ScopeType = typeof(object); - - var type = new Mock>( - Mock.Of>(), - Mock.Of>>()).Object.GetType(); - - // Act - builder.AddScopeManager(type); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, manager); - } - - [Fact] - public void AddScopeStore_ThrowsAnExceptionForInvalidStore() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ScopeType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddScopeStore(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddScopeStore_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.ScopeType = typeof(object); - - var type = Mock.Of>().GetType(); - - // Act - builder.AddScopeStore(type); - - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, store); - } - - [Fact] - public void AddTokenManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.TokenType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddTokenManager(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddTokenManager_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.TokenType = typeof(object); - - var type = new Mock>( - Mock.Of>(), - Mock.Of>>()).Object.GetType(); - - // Act - builder.AddTokenManager(type); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, manager); - } - - [Fact] - public void AddTokenStore_ThrowsAnExceptionForInvalidStore() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.TokenType = typeof(object); - - // Act and assert - var exception = Assert.Throws(() => builder.AddTokenStore(typeof(object))); - - Assert.Equal("The specified type is invalid.", exception.Message); - } - - [Fact] - public void AddTokenStore_OverridesDefaultManager() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - builder.TokenType = typeof(object); - - var type = Mock.Of>().GetType(); - - // Act - builder.AddTokenStore(type); - - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService>(); - - // Assert - Assert.IsType(type, store); - } - } -} diff --git a/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs b/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs new file mode 100644 index 00000000..8377d7a7 --- /dev/null +++ b/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs @@ -0,0 +1,487 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using OpenIddict.Abstractions; +using Xunit; + +namespace OpenIddict.Core.Tests +{ + public class OpenIddictCoreBuilderTests + { + [Fact] + public void ReplaceApplicationManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceApplicationManager(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceApplicationManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceApplicationManager(typeof(OpenGenericApplicationManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager<>) && + service.ImplementationType == typeof(OpenGenericApplicationManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager<>) && + service.ImplementationType == typeof(OpenIddictApplicationManager<>)); + } + + [Fact] + public void ReplaceApplicationManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceApplicationManager(typeof(ClosedGenericApplicationManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager) && + service.ImplementationType == typeof(ClosedGenericApplicationManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager<>) && + service.ImplementationType == typeof(OpenIddictApplicationManager<>)); + } + + [Fact] + public void ReplaceApplicationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceApplicationStoreResolver(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceApplicationStoreResolver_OverridesDefaultManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + var type = Mock.Of().GetType(); + + // Act + builder.ReplaceApplicationStoreResolver(type); + + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); + + // Assert + Assert.IsType(type, store); + } + + [Fact] + public void ReplaceAuthorizationManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceAuthorizationManager(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceAuthorizationManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceAuthorizationManager(typeof(OpenGenericAuthorizationManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && + service.ImplementationType == typeof(OpenGenericAuthorizationManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && + service.ImplementationType == typeof(OpenIddictAuthorizationManager<>)); + } + + [Fact] + public void ReplaceAuthorizationManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceAuthorizationManager(typeof(ClosedGenericAuthorizationManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager) && + service.ImplementationType == typeof(ClosedGenericAuthorizationManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && + service.ImplementationType == typeof(OpenIddictAuthorizationManager<>)); + } + [Fact] + public void ReplaceAuthorizationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceAuthorizationStoreResolver(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceAuthorizationStoreResolver_OverridesDefaultManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + var type = Mock.Of().GetType(); + + // Act + builder.ReplaceAuthorizationStoreResolver(type); + + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); + + // Assert + Assert.IsType(type, store); + } + + [Fact] + public void ReplaceScopeManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceScopeManager(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceScopeManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceScopeManager(typeof(OpenGenericScopeManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictScopeManager<>) && + service.ImplementationType == typeof(OpenGenericScopeManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictScopeManager<>) && + service.ImplementationType == typeof(OpenIddictScopeManager<>)); + } + + [Fact] + public void ReplaceScopeManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceScopeManager(typeof(ClosedGenericScopeManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictScopeManager) && + service.ImplementationType == typeof(ClosedGenericScopeManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictScopeManager<>) && + service.ImplementationType == typeof(OpenIddictScopeManager<>)); + } + + [Fact] + public void ReplaceScopeStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceScopeStoreResolver(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceScopeStoreResolver_OverridesDefaultManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + var type = Mock.Of().GetType(); + + // Act + builder.ReplaceScopeStoreResolver(type); + + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); + + // Assert + Assert.IsType(type, store); + } + + [Fact] + public void ReplaceTokenManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceTokenManager(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceTokenManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceTokenManager(typeof(OpenGenericTokenManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictTokenManager<>) && + service.ImplementationType == typeof(OpenGenericTokenManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictTokenManager<>) && + service.ImplementationType == typeof(OpenIddictTokenManager<>)); + } + + [Fact] + public void ReplaceTokenManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceTokenManager(typeof(ClosedGenericTokenManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictTokenManager) && + service.ImplementationType == typeof(ClosedGenericTokenManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictTokenManager<>) && + service.ImplementationType == typeof(OpenIddictTokenManager<>)); + } + + [Fact] + public void ReplaceTokenStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceTokenStoreResolver(typeof(object))); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void ReplaceTokenStoreResolver_OverridesDefaultManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + var type = Mock.Of().GetType(); + + // Act + builder.ReplaceTokenStoreResolver(type); + + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); + + // Assert + Assert.IsType(type, store); + } + + [Fact] + public void UseCustomModels_CustomEntitiesAreCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + services.AddOpenIddict().AddCore() + .UseCustomModels(); + + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); + Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(CustomScope), options.DefaultScopeType); + Assert.Equal(typeof(CustomToken), options.DefaultTokenType); + } + + private static OpenIddictCoreBuilder CreateBuilder(IServiceCollection services) + => services.AddOpenIddict().AddCore(); + + private static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddOptions(); + + return services; + } + + public class CustomApplication { } + public class CustomAuthorization { } + public class CustomScope { } + public class CustomToken { } + + private class ClosedGenericApplicationManager : OpenIddictApplicationManager + { + public ClosedGenericApplicationManager( + IOpenIddictApplicationStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class OpenGenericApplicationManager : OpenIddictApplicationManager + where TApplication : class + { + public OpenGenericApplicationManager( + IOpenIddictApplicationStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class ClosedGenericAuthorizationManager : OpenIddictAuthorizationManager + { + public ClosedGenericAuthorizationManager( + IOpenIddictAuthorizationStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class OpenGenericAuthorizationManager : OpenIddictAuthorizationManager + where TAuthorization : class + { + public OpenGenericAuthorizationManager( + IOpenIddictAuthorizationStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class ClosedGenericScopeManager : OpenIddictScopeManager + { + public ClosedGenericScopeManager( + IOpenIddictScopeStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class OpenGenericScopeManager : OpenIddictScopeManager + where TScope : class + { + public OpenGenericScopeManager( + IOpenIddictScopeStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class ClosedGenericTokenManager : OpenIddictTokenManager + { + public ClosedGenericTokenManager( + IOpenIddictTokenStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + + private class OpenGenericTokenManager : OpenIddictTokenManager + where TToken : class + { + public OpenGenericTokenManager( + IOpenIddictTokenStoreResolver resolver, + ILogger> logger, + IOptions options) + : base(resolver, logger, options) + { + } + } + } +} diff --git a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs deleted file mode 100644 index b3467ae8..00000000 --- a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace OpenIddict.Core.Tests -{ - public class OpenIddictExtensionsTests - { - [Fact] - public void AddOpenIddict_CustomEntitiesAreCorrectlySet() - { - // Arrange - var services = new ServiceCollection(); - - // Act - var builder = services.AddOpenIddict(); - - // Assert - Assert.Equal(typeof(object), builder.ApplicationType); - Assert.Equal(typeof(object), builder.AuthorizationType); - Assert.Equal(typeof(object), builder.ScopeType); - Assert.Equal(typeof(object), builder.TokenType); - } - - [Theory] - [InlineData(typeof(OpenIddictApplicationManager))] - [InlineData(typeof(OpenIddictAuthorizationManager))] - [InlineData(typeof(OpenIddictScopeManager))] - [InlineData(typeof(OpenIddictTokenManager))] - public void AddOpenIddict_ManagersForCustomEntitiesAreCorrectlyRegistered(Type type) - { - // Arrange - var services = new ServiceCollection(); - - // Act - services.AddOpenIddict(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type); - } - } -} diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs index 874bd0b3..a0521f3e 100644 --- a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs @@ -8,117 +8,22 @@ using System; using System.Data.Entity; using Microsoft.Extensions.DependencyInjection; using OpenIddict.EntityFramework; -using OpenIddict.Models; using Xunit; namespace OpenIddict.EntityFrameworkCore.Tests { public class OpenIddictExtensionsTests { - [Fact] - public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidApplicationEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(object), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkStores(); - }); - - Assert.Equal("The Entity Framework stores can only be used " + - "with the built-in OpenIddictApplication entity.", exception.Message); - } - - [Fact] - public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidAuthorizationEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(object), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkStores(); - }); - - Assert.Equal("The Entity Framework stores can only be used " + - "with the built-in OpenIddictAuthorization entity.", exception.Message); - } - - [Fact] - public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidScopeEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(object), - TokenType = typeof(OpenIddictToken) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkStores(); - }); - - Assert.Equal("The Entity Framework stores can only be used " + - "with the built-in OpenIddictScope entity.", exception.Message); - } - - [Fact] - public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidTokenEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(object) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkStores(); - }); - - Assert.Equal("The Entity Framework stores can only be used " + - "with the built-in OpenIddictToken entity.", exception.Message); - } - [Theory] - [InlineData(typeof(OpenIddictApplicationStore))] - [InlineData(typeof(OpenIddictAuthorizationStore))] - [InlineData(typeof(OpenIddictScopeStore))] - [InlineData(typeof(OpenIddictTokenStore))] - public void AddEntityFrameworkStores_RegistersEntityFrameworkStores(Type type) + [InlineData(typeof(OpenIddictApplicationStoreResolver))] + [InlineData(typeof(OpenIddictAuthorizationStoreResolver))] + [InlineData(typeof(OpenIddictScopeStoreResolver))] + [InlineData(typeof(OpenIddictTokenStoreResolver))] + public void AddEntityFrameworkStores_RegistersEntityFrameworkStoreFactories(Type type) { // Arrange var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; + var builder = services.AddOpenIddict().AddCore(); // Act builder.AddEntityFrameworkStores(); @@ -126,60 +31,5 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Assert Assert.Contains(services, service => service.ImplementationType == type); } - - [Theory] - [InlineData(typeof(OpenIddictApplicationStore, OpenIddictAuthorization, OpenIddictToken, DbContext, Guid>))] - [InlineData(typeof(OpenIddictAuthorizationStore, OpenIddictApplication, OpenIddictToken, DbContext, Guid>))] - [InlineData(typeof(OpenIddictScopeStore, DbContext, Guid>))] - [InlineData(typeof(OpenIddictTokenStore, OpenIddictApplication, OpenIddictAuthorization, DbContext, Guid>))] - public void AddEntityFrameworkStores_KeyTypeIsInferredFromEntities(Type type) - { - // Arrange - var services = new ServiceCollection(); - - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; - - // Act - builder.AddEntityFrameworkStores(); - - // Assert - Assert.Contains(services, service => service.ImplementationType == type); - } - - [Theory] - [InlineData(typeof(OpenIddictApplicationStore))] - [InlineData(typeof(OpenIddictAuthorizationStore))] - [InlineData(typeof(OpenIddictScopeStore))] - [InlineData(typeof(OpenIddictTokenStore))] - public void AddEntityFrameworkStores_DefaultEntitiesCanBeReplaced(Type type) - { - // Arrange - var services = new ServiceCollection(); - - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(CustomApplication), - AuthorizationType = typeof(CustomAuthorization), - ScopeType = typeof(CustomScope), - TokenType = typeof(CustomToken) - }; - - // Act - builder.AddEntityFrameworkStores(); - - // Assert - Assert.Contains(services, service => service.ImplementationType == type); - } - - public class CustomApplication : OpenIddictApplication { } - public class CustomAuthorization : OpenIddictAuthorization { } - public class CustomScope : OpenIddictScope { } - public class CustomToken : OpenIddictToken { } } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs index 42752609..76da44ba 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs @@ -7,117 +7,22 @@ using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using OpenIddict.Models; using Xunit; namespace OpenIddict.EntityFrameworkCore.Tests { public class OpenIddictExtensionsTests { - [Fact] - public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidApplicationEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(object), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkCoreStores(); - }); - - Assert.Equal("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictApplication entity.", exception.Message); - } - - [Fact] - public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidAuthorizationEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(object), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkCoreStores(); - }); - - Assert.Equal("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictAuthorization entity.", exception.Message); - } - - [Fact] - public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidScopeEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(object), - TokenType = typeof(OpenIddictToken) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkCoreStores(); - }); - - Assert.Equal("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictScope entity.", exception.Message); - } - - [Fact] - public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidTokenEntity() - { - // Arrange - var builder = new OpenIddictBuilder(new ServiceCollection()) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(object) - }; - - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddEntityFrameworkCoreStores(); - }); - - Assert.Equal("The Entity Framework Core stores can only be used " + - "with the built-in OpenIddictToken entity.", exception.Message); - } - [Theory] - [InlineData(typeof(OpenIddictApplicationStore))] - [InlineData(typeof(OpenIddictAuthorizationStore))] - [InlineData(typeof(OpenIddictScopeStore))] - [InlineData(typeof(OpenIddictTokenStore))] - public void AddEntityFrameworkCoreStores_RegistersEntityFrameworkStores(Type type) + [InlineData(typeof(OpenIddictApplicationStoreResolver))] + [InlineData(typeof(OpenIddictAuthorizationStoreResolver))] + [InlineData(typeof(OpenIddictScopeStoreResolver))] + [InlineData(typeof(OpenIddictTokenStoreResolver))] + public void AddEntityFrameworkCoreStores_RegistersEntityFrameworkCoreStoreFactories(Type type) { // Arrange var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; + var builder = services.AddOpenIddict().AddCore(); // Act builder.AddEntityFrameworkCoreStores(); @@ -125,60 +30,5 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Assert Assert.Contains(services, service => service.ImplementationType == type); } - - [Theory] - [InlineData(typeof(OpenIddictApplicationStore, OpenIddictAuthorization, OpenIddictToken, DbContext, Guid>))] - [InlineData(typeof(OpenIddictAuthorizationStore, OpenIddictApplication, OpenIddictToken, DbContext, Guid>))] - [InlineData(typeof(OpenIddictScopeStore, DbContext, Guid>))] - [InlineData(typeof(OpenIddictTokenStore, OpenIddictApplication, OpenIddictAuthorization, DbContext, Guid>))] - public void AddEntityFrameworkCoreStores_KeyTypeIsInferredFromEntities(Type type) - { - // Arrange - var services = new ServiceCollection(); - - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; - - // Act - builder.AddEntityFrameworkCoreStores(); - - // Assert - Assert.Contains(services, service => service.ImplementationType == type); - } - - [Theory] - [InlineData(typeof(OpenIddictApplicationStore))] - [InlineData(typeof(OpenIddictAuthorizationStore))] - [InlineData(typeof(OpenIddictScopeStore))] - [InlineData(typeof(OpenIddictTokenStore))] - public void AddEntityFrameworkCoreStores_DefaultEntitiesCanBeReplaced(Type type) - { - // Arrange - var services = new ServiceCollection(); - - var builder = new OpenIddictBuilder(services) - { - ApplicationType = typeof(CustomApplication), - AuthorizationType = typeof(CustomAuthorization), - ScopeType = typeof(CustomScope), - TokenType = typeof(CustomToken) - }; - - // Act - builder.AddEntityFrameworkCoreStores(); - - // Assert - Assert.Contains(services, service => service.ImplementationType == type); - } - - public class CustomApplication : OpenIddictApplication { } - public class CustomAuthorization : OpenIddictAuthorization { } - public class CustomScope : OpenIddictScope { } - public class CustomToken : OpenIddictToken { } } } diff --git a/test/OpenIddict.Mvc.Tests/OpenIddict.Mvc.Tests.csproj b/test/OpenIddict.Mvc.Tests/OpenIddict.Mvc.Tests.csproj index 616e6347..04be4b35 100644 --- a/test/OpenIddict.Mvc.Tests/OpenIddict.Mvc.Tests.csproj +++ b/test/OpenIddict.Mvc.Tests/OpenIddict.Mvc.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs index e6674752..5aa9c286 100644 --- a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs @@ -20,7 +20,7 @@ namespace OpenIddict.Mvc.Tests var services = new ServiceCollection(); services.AddOptions(); - var builder = new OpenIddictBuilder(services); + var builder = services.AddOpenIddict().AddServer(); // Act builder.AddMvcBinders(); diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs similarity index 99% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Authentication.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs index 34b412ec..60984c13 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Authentication.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs @@ -4,7 +4,6 @@ * the license and the contributors participating to this project. */ -using System.Collections.Immutable; using System.IO; using System.Security.Cryptography; using System.Threading; @@ -17,13 +16,14 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Bson; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Fact] public async Task ExtractAuthorizationRequest_UnsupportedRequestParameterIsRejected() diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Discovery.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Discovery.cs similarity index 98% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Discovery.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Discovery.cs index 1dc97567..417e9273 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Discovery.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Discovery.cs @@ -10,15 +10,13 @@ using AspNet.Security.OpenIdConnect.Primitives; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Facebook; using Microsoft.AspNetCore.Authentication.Google; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; -using OpenIddict.Core; +using OpenIddict.Abstractions; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Fact] public async Task HandleConfigurationRequest_PlainCodeChallengeMethodIsNotReturned() diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs similarity index 99% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Exchange.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs index d46bc023..080e6182 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Exchange.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs @@ -18,13 +18,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Authentication; using Microsoft.Extensions.DependencyInjection; using Moq; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Theory] [InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)] diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs similarity index 99% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Introspection.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs index bfa1a9ca..4c3fd00a 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Introspection.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs @@ -17,13 +17,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Authentication; using Microsoft.Extensions.DependencyInjection; using Moq; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Fact] public async Task ExtractIntrospectionRequest_GetRequestsAreRejected() diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Revocation.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Revocation.cs similarity index 99% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Revocation.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Revocation.cs index 22f77925..ce13cf9b 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Revocation.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Revocation.cs @@ -19,13 +19,14 @@ using Microsoft.AspNetCore.Http.Authentication; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Moq; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Theory] [InlineData(OpenIdConnectConstants.TokenTypeHints.AccessToken)] diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs similarity index 99% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Serialization.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs index d72b1b96..cdbecb94 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Serialization.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs @@ -18,13 +18,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Authentication; using Microsoft.Extensions.DependencyInjection; using Moq; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Fact] public async Task DeserializeAccessToken_ReturnsNullForMalformedReferenceToken() diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Session.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs similarity index 98% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Session.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs index cf0544a0..5550abd1 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Session.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs @@ -13,12 +13,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Moq; +using OpenIddict.Abstractions; using OpenIddict.Core; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Fact] public async Task ExtractLogoutRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Userinfo.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Userinfo.cs similarity index 92% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Userinfo.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Userinfo.cs index 883f383d..b591bd0e 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Userinfo.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Userinfo.cs @@ -9,9 +9,9 @@ using AspNet.Security.OpenIdConnect.Client; using AspNet.Security.OpenIdConnect.Primitives; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { [Fact] public async Task ExtractUserinfoRequest_RequestIsHandledByUserCode() diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs similarity index 95% rename from test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs index 7f59576e..20a5f583 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs @@ -26,17 +26,19 @@ using Microsoft.AspNetCore.Http.Authentication; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public partial class OpenIddictProviderTests + public partial class OpenIddictServerProviderTests { public const string AuthorizationEndpoint = "/connect/authorize"; public const string ConfigurationEndpoint = "/.well-known/openid-configuration"; @@ -1332,7 +1334,7 @@ namespace OpenIddict.Tests Assert.Equal("value", (string) response["custom_string_parameter"]); } - private static TestServer CreateAuthorizationServer(Action configuration = null) + private static TestServer CreateAuthorizationServer(Action configuration = null) { var builder = new WebHostBuilder(); @@ -1345,47 +1347,52 @@ namespace OpenIddict.Tests services.AddAuthentication(); services.AddOptions(); - // Replace the default OpenIddict managers. - services.AddSingleton(CreateApplicationManager()); - services.AddSingleton(CreateAuthorizationManager()); - services.AddSingleton(CreateScopeManager()); - services.AddSingleton(CreateTokenManager()); + services.AddOpenIddict() + .AddCore(options => + { + options.UseDefaultModels(); - services.AddOpenIddict(options => - { - // Disable the transport security requirement during testing. - options.DisableHttpsRequirement(); - - // Enable the tested endpoints. - options.EnableAuthorizationEndpoint(AuthorizationEndpoint) - .EnableIntrospectionEndpoint(IntrospectionEndpoint) - .EnableLogoutEndpoint(LogoutEndpoint) - .EnableRevocationEndpoint(RevocationEndpoint) - .EnableTokenEndpoint(TokenEndpoint) - .EnableUserinfoEndpoint(UserinfoEndpoint); - - // Enable the tested flows. - options.AllowAuthorizationCodeFlow() - .AllowClientCredentialsFlow() - .AllowImplicitFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow(); - - // Register the X.509 certificate used to sign the identity tokens. - options.AddSigningCertificate( - assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly, - resource: "OpenIddict.Server.Tests.Certificate.pfx", - password: "OpenIddict"); - - // Note: overriding the default data protection provider is not necessary for the tests to pass, - // but is useful to ensure unnecessary keys are not persisted in testing environments, which also - // helps make the unit tests run faster, as no registry or disk access is required in this case. - options.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); - - // Run the configuration delegate - // registered by the unit tests. - configuration?.Invoke(options); - }); + options.Services.AddSingleton(CreateApplicationManager()); + options.Services.AddSingleton(CreateAuthorizationManager()); + options.Services.AddSingleton(CreateScopeManager()); + options.Services.AddSingleton(CreateTokenManager()); + }) + + .AddServer(options => + { + // Disable the transport security requirement during testing. + options.DisableHttpsRequirement(); + + // Enable the tested endpoints. + options.EnableAuthorizationEndpoint(AuthorizationEndpoint) + .EnableIntrospectionEndpoint(IntrospectionEndpoint) + .EnableLogoutEndpoint(LogoutEndpoint) + .EnableRevocationEndpoint(RevocationEndpoint) + .EnableTokenEndpoint(TokenEndpoint) + .EnableUserinfoEndpoint(UserinfoEndpoint); + + // Enable the tested flows. + options.AllowAuthorizationCodeFlow() + .AllowClientCredentialsFlow() + .AllowImplicitFlow() + .AllowPasswordFlow() + .AllowRefreshTokenFlow(); + + // Register the X.509 certificate used to sign the identity tokens. + options.AddSigningCertificate( + assembly: typeof(OpenIddictServerProviderTests).GetTypeInfo().Assembly, + resource: "OpenIddict.Server.Tests.Certificate.pfx", + password: "OpenIddict"); + + // Note: overriding the default data protection provider is not necessary for the tests to pass, + // but is useful to ensure unnecessary keys are not persisted in testing environments, which also + // helps make the unit tests run faster, as no registry or disk access is required in this case. + options.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); + + // Run the configuration delegate + // registered by the unit tests. + configuration?.Invoke(options); + }); }); builder.Configure(app => @@ -1430,7 +1437,7 @@ namespace OpenIddict.Tests SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme }); - app.UseOpenIddict(); + app.UseOpenIddictServer(); app.Run(context => { @@ -1513,8 +1520,9 @@ namespace OpenIddict.Tests Action>> configuration = null) { var manager = new Mock>( - Mock.Of>(), - Mock.Of>>()); + Mock.Of(), + Mock.Of>>(), + Mock.Of>()); configuration?.Invoke(manager); @@ -1525,8 +1533,9 @@ namespace OpenIddict.Tests Action>> configuration = null) { var manager = new Mock>( - Mock.Of>(), - Mock.Of>>()); + Mock.Of(), + Mock.Of>>(), + Mock.Of>()); configuration?.Invoke(manager); @@ -1537,8 +1546,9 @@ namespace OpenIddict.Tests Action>> configuration = null) { var manager = new Mock>( - Mock.Of>(), - Mock.Of>>()); + Mock.Of(), + Mock.Of>>(), + Mock.Of>()); configuration?.Invoke(manager); @@ -1549,8 +1559,9 @@ namespace OpenIddict.Tests Action>> configuration = null) { var manager = new Mock>( - Mock.Of>(), - Mock.Of>>()); + Mock.Of(), + Mock.Of>>(), + Mock.Of>()); configuration?.Invoke(manager); diff --git a/test/OpenIddict.Server.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs similarity index 68% rename from test/OpenIddict.Server.Tests/OpenIddictExtensionsTests.cs rename to test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs index 6b022078..54e2db29 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs @@ -8,200 +8,20 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using AspNet.Security.OpenIdConnect.Primitives; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Moq; -using OpenIddict.Models; using Xunit; -namespace OpenIddict.Tests +namespace OpenIddict.Server.Tests { - public class OpenIddictExtensionsTests + public class OpenIddictServerBuilderTests { - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenServicesAreNotRegistered() - { - // Arrange - var services = new ServiceCollection(); - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("The OpenIddict services cannot be resolved from the dependency injection container. " + - "Make sure 'services.AddOpenIddict()' is correctly called from 'ConfigureServices()'.", exception.Message); - } - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenNoFlowIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOpenIddict(); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("At least one OAuth2/OpenID Connect flow must be enabled.", exception.Message); - } - - [Theory] - [InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)] - [InlineData(OpenIdConnectConstants.GrantTypes.Implicit)] - public void UseOpenIddict_ThrowsAnExceptionWhenAuthorizationEndpointIsDisabled(string flow) - { - // Arrange - var services = new ServiceCollection(); - - services.AddOpenIddict() - .Configure(options => options.GrantTypes.Add(flow)) - .Configure(options => options.AuthorizationEndpointPath = PathString.Empty); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("The authorization endpoint must be enabled to use " + - "the authorization code and implicit flows.", exception.Message); - } - - [Theory] - [InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)] - [InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)] - [InlineData(OpenIdConnectConstants.GrantTypes.Password)] - [InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)] - public void UseOpenIddict_ThrowsAnExceptionWhenTokenEndpointIsDisabled(string flow) - { - // Arrange - var services = new ServiceCollection(); - - services.AddOpenIddict() - .EnableAuthorizationEndpoint("/connect/authorize") - .Configure(options => options.GrantTypes.Add(flow)) - .Configure(options => options.TokenEndpointPath = PathString.Empty); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("The token endpoint must be enabled to use the authorization code, " + - "client credentials, password and refresh token flows.", exception.Message); - } - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenTokenRevocationIsDisabled() - { - // Arrange - var services = new ServiceCollection(); - - services.AddOpenIddict() - .EnableAuthorizationEndpoint("/connect/authorize") - .EnableRevocationEndpoint("/connect/revocation") - .AllowImplicitFlow() - .DisableTokenRevocation(); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("The revocation endpoint cannot be enabled when token revocation is disabled.", exception.Message); - } - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenUsingReferenceTokensWithTokenRevocationDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddDataProtection(); - - services.AddOpenIddict() - .EnableAuthorizationEndpoint("/connect/authorize") - .AllowImplicitFlow() - .DisableTokenRevocation() - .UseReferenceTokens(); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("Reference tokens cannot be used when disabling token revocation.", exception.Message); - } - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenUsingReferenceTokensIfAnAccessTokenHandlerIsSet() - { - // Arrange - var services = new ServiceCollection(); - services.AddDataProtection(); - - services.AddOpenIddict() - .EnableAuthorizationEndpoint("/connect/authorize") - .AllowImplicitFlow() - .UseReferenceTokens() - .UseJsonWebTokens(); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("Reference tokens cannot be used when configuring JWT as the access token format.", exception.Message); - } - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenRevocationDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddDataProtection(); - - services.AddOpenIddict() - .EnableAuthorizationEndpoint("/connect/authorize") - .AllowImplicitFlow() - .DisableTokenRevocation(); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - Assert.Equal("Sliding expiration must be disabled when turning off " + - "token revocation if rolling tokens are not used.", exception.Message); - } - - [Fact] - public void UseOpenIddict_ThrowsAnExceptionWhenNoSigningKeyIsRegisteredIfTheImplicitFlowIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - - services.AddOpenIddict() - .EnableAuthorizationEndpoint("/connect/authorize") - .AllowImplicitFlow(); - - var builder = new ApplicationBuilder(services.BuildServiceProvider()); - - // Act and assert - var exception = Assert.Throws(() => builder.UseOpenIddict()); - - 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); - } - [Fact] public void Configure_OptionsAreCorrectlyAmended() { @@ -215,37 +35,12 @@ namespace OpenIddict.Tests builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("OpenIddict", options.Value.Description.DisplayName); } - [Fact] - public void UseOpenIddict_OpenIdConnectServerMiddlewareIsRegistered() - { - // Arrange - var services = new ServiceCollection(); - - services.AddOpenIddict() - .AddSigningCertificate( - assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly, - resource: "OpenIddict.Server.Tests.Certificate.pfx", - password: "OpenIddict") - .AllowImplicitFlow() - .EnableAuthorizationEndpoint("/connect/authorize"); - - var builder = new Mock(); - builder.SetupGet(mock => mock.ApplicationServices) - .Returns(services.BuildServiceProvider()); - - // Act - builder.Object.UseOpenIddict(); - - // Assert - builder.Verify(mock => mock.Use(It.IsAny>()), Times.Once()); - } - [Fact] public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() { @@ -259,7 +54,7 @@ namespace OpenIddict.Tests builder.AddEphemeralSigningKey(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(1, options.Value.SigningCredentials.Count); @@ -286,7 +81,7 @@ namespace OpenIddict.Tests builder.AddEphemeralSigningKey(algorithm); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); var credentials = options.Value.SigningCredentials[0]; // Assert @@ -318,7 +113,7 @@ namespace OpenIddict.Tests builder.AddSigningKey(key); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Same(key, options.Value.SigningCredentials[0].Key); @@ -335,12 +130,12 @@ namespace OpenIddict.Tests // Act builder.AddSigningCertificate( - assembly: typeof(OpenIddictExtensionsTests).GetTypeInfo().Assembly, + assembly: typeof(OpenIddictServerBuilderTests).GetTypeInfo().Assembly, resource: "OpenIddict.Server.Tests.Certificate.pfx", password: "OpenIddict"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key); @@ -359,7 +154,7 @@ namespace OpenIddict.Tests builder.AllowAuthorizationCodeFlow(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes); @@ -378,7 +173,7 @@ namespace OpenIddict.Tests builder.AllowClientCredentialsFlow(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes); @@ -397,7 +192,7 @@ namespace OpenIddict.Tests builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes); @@ -416,7 +211,7 @@ namespace OpenIddict.Tests builder.AllowImplicitFlow(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes); @@ -435,7 +230,7 @@ namespace OpenIddict.Tests builder.AllowPasswordFlow(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes); @@ -454,7 +249,7 @@ namespace OpenIddict.Tests builder.AllowRefreshTokenFlow(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes); @@ -473,7 +268,7 @@ namespace OpenIddict.Tests builder.DisableConfigurationEndpoint(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath); @@ -492,7 +287,7 @@ namespace OpenIddict.Tests builder.DisableCryptographyEndpoint(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath); @@ -511,7 +306,7 @@ namespace OpenIddict.Tests builder.DisableSlidingExpiration(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.False(options.Value.UseSlidingExpiration); @@ -530,7 +325,7 @@ namespace OpenIddict.Tests builder.DisableTokenRevocation(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.True(options.Value.DisableTokenRevocation); @@ -549,7 +344,7 @@ namespace OpenIddict.Tests builder.EnableAuthorizationEndpoint("/endpoint-path"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath); @@ -568,7 +363,7 @@ namespace OpenIddict.Tests builder.EnableIntrospectionEndpoint("/endpoint-path"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath); @@ -587,7 +382,7 @@ namespace OpenIddict.Tests builder.EnableLogoutEndpoint("/endpoint-path"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath); @@ -606,7 +401,7 @@ namespace OpenIddict.Tests builder.EnableRequestCaching(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.True(options.Value.EnableRequestCaching); @@ -625,7 +420,7 @@ namespace OpenIddict.Tests builder.EnableRevocationEndpoint("/endpoint-path"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath); @@ -644,7 +439,7 @@ namespace OpenIddict.Tests builder.EnableScopeValidation(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.True(options.Value.EnableScopeValidation); @@ -663,7 +458,7 @@ namespace OpenIddict.Tests builder.EnableTokenEndpoint("/endpoint-path"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath); @@ -682,7 +477,7 @@ namespace OpenIddict.Tests builder.EnableUserinfoEndpoint("/endpoint-path"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath); @@ -701,7 +496,7 @@ namespace OpenIddict.Tests builder.RequireClientIdentification(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.True(options.Value.RequireClientIdentification); @@ -720,7 +515,7 @@ namespace OpenIddict.Tests builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime); @@ -739,7 +534,7 @@ namespace OpenIddict.Tests builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime); @@ -758,7 +553,7 @@ namespace OpenIddict.Tests builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime); @@ -777,7 +572,7 @@ namespace OpenIddict.Tests builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime); @@ -796,7 +591,7 @@ namespace OpenIddict.Tests builder.SetIssuer(new Uri("http://www.fabrikam.com/")); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Value.Issuer); @@ -815,7 +610,7 @@ namespace OpenIddict.Tests builder.RegisterClaims("custom_claim_1", "custom_claim_2"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains("custom_claim_1", options.Value.Claims); @@ -835,7 +630,7 @@ namespace OpenIddict.Tests builder.RegisterScopes("custom_scope_1", "custom_scope_2"); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.Contains("custom_scope_1", options.Value.Scopes); @@ -855,7 +650,7 @@ namespace OpenIddict.Tests builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider); @@ -874,7 +669,7 @@ namespace OpenIddict.Tests builder.UseJsonWebTokens(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.IsType(options.Value.AccessTokenHandler); @@ -893,19 +688,34 @@ namespace OpenIddict.Tests builder.UseReferenceTokens(); var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + var options = provider.GetRequiredService>(); // Assert Assert.True(options.Value.UseReferenceTokens); } - private static OpenIddictBuilder CreateBuilder(IServiceCollection services) - => new OpenIddictBuilder(services) - { - ApplicationType = typeof(OpenIddictApplication), - AuthorizationType = typeof(OpenIddictAuthorization), - ScopeType = typeof(OpenIddictScope), - TokenType = typeof(OpenIddictToken) - }; + private static OpenIddictServerBuilder CreateBuilder(IServiceCollection services) + => services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer(); + + private static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddAuthentication(); + services.AddDistributedMemoryCache(); + services.AddLogging(); + services.AddSingleton(); + + return services; + } + + private static OpenIddictServerOptions GetOptions(IServiceCollection services) + { + var provider = services.BuildServiceProvider(); + + var options = provider.GetRequiredService>(); + return options.Value; + } } } diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs new file mode 100644 index 00000000..d90ef2d3 --- /dev/null +++ b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs @@ -0,0 +1,226 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Reflection; +using AspNet.Security.OpenIdConnect.Primitives; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder.Internal; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace OpenIddict.Server.Tests +{ + public class OpenIddictServerExtensionsTests + { + [Fact] + public void UseOpenIddictServer_ThrowsAnExceptionWhenNoFlowIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("At least one OAuth2/OpenID Connect flow must be enabled.", exception.Message); + } + + [Theory] + [InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)] + [InlineData(OpenIdConnectConstants.GrantTypes.Implicit)] + public void UseOpenIddictServer_ThrowsAnExceptionWhenAuthorizationEndpointIsDisabled(string flow) + { + // Arrange + var services = new ServiceCollection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .Configure(options => options.GrantTypes.Add(flow)) + .Configure(options => options.AuthorizationEndpointPath = PathString.Empty); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("The authorization endpoint must be enabled to use " + + "the authorization code and implicit flows.", exception.Message); + } + + [Theory] + [InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)] + [InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)] + [InlineData(OpenIdConnectConstants.GrantTypes.Password)] + [InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)] + public void UseOpenIddictServer_ThrowsAnExceptionWhenTokenEndpointIsDisabled(string flow) + { + // Arrange + var services = new ServiceCollection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .EnableAuthorizationEndpoint("/connect/authorize") + .Configure(options => options.GrantTypes.Add(flow)) + .Configure(options => options.TokenEndpointPath = PathString.Empty); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("The token endpoint must be enabled to use the authorization code, " + + "client credentials, password and refresh token flows.", exception.Message); + } + + [Fact] + public void UseOpenIddictServer_ThrowsAnExceptionWhenTokenRevocationIsDisabled() + { + // Arrange + var services = new ServiceCollection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .EnableAuthorizationEndpoint("/connect/authorize") + .EnableRevocationEndpoint("/connect/revocation") + .AllowImplicitFlow() + .DisableTokenRevocation(); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("The revocation endpoint cannot be enabled when token revocation is disabled.", exception.Message); + } + + [Fact] + public void UseOpenIddictServer_ThrowsAnExceptionWhenUsingReferenceTokensWithTokenRevocationDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddDataProtection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .EnableAuthorizationEndpoint("/connect/authorize") + .AllowImplicitFlow() + .DisableTokenRevocation() + .UseReferenceTokens(); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("Reference tokens cannot be used when disabling token revocation.", exception.Message); + } + + [Fact] + public void UseOpenIddictServer_ThrowsAnExceptionWhenUsingReferenceTokensIfAnAccessTokenHandlerIsSet() + { + // Arrange + var services = new ServiceCollection(); + services.AddDataProtection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .EnableAuthorizationEndpoint("/connect/authorize") + .AllowImplicitFlow() + .UseReferenceTokens() + .UseJsonWebTokens(); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("Reference tokens cannot be used when configuring JWT as the access token format.", exception.Message); + } + + [Fact] + public void UseOpenIddictServer_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenRevocationDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddDataProtection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .EnableAuthorizationEndpoint("/connect/authorize") + .AllowImplicitFlow() + .DisableTokenRevocation(); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => builder.UseOpenIddictServer()); + + Assert.Equal("Sliding expiration must be disabled when turning off " + + "token revocation if rolling tokens are not used.", exception.Message); + } + + [Fact] + public void UseOpenIddictServer_ThrowsAnExceptionWhenNoSigningKeyIsRegisteredIfTheImplicitFlowIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .EnableAuthorizationEndpoint("/connect/authorize") + .AllowImplicitFlow(); + + var builder = new ApplicationBuilder(services.BuildServiceProvider()); + + // Act and assert + var exception = Assert.Throws(() => 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); + } + + [Fact] + public void UseOpenIddictServer_OpenIdConnectServerMiddlewareIsRegistered() + { + // Arrange + var services = new ServiceCollection(); + + services.AddOpenIddict() + .AddCore(options => options.UseDefaultModels()) + .AddServer() + .AddSigningCertificate( + assembly: typeof(OpenIddictServerProviderTests).GetTypeInfo().Assembly, + resource: "OpenIddict.Server.Tests.Certificate.pfx", + password: "OpenIddict") + .AllowImplicitFlow() + .EnableAuthorizationEndpoint("/connect/authorize"); + + var builder = new Mock(); + builder.SetupGet(mock => mock.ApplicationServices) + .Returns(services.BuildServiceProvider()); + + // Act + builder.Object.UseOpenIddictServer(); + + // Assert + builder.Verify(mock => mock.Use(It.IsAny>()), Times.Once()); + } + } +} diff --git a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs index 6bc06d40..eee39891 100644 --- a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs @@ -7,6 +7,7 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using OpenIddict.Core; using OpenIddict.Models; using Xunit; @@ -15,38 +16,39 @@ namespace OpenIddict.Tests { public class OpenIddictExtensionsTests { - [Theory] - [InlineData(typeof(OpenIddictApplicationManager))] - [InlineData(typeof(OpenIddictAuthorizationManager))] - [InlineData(typeof(OpenIddictScopeManager))] - [InlineData(typeof(OpenIddictTokenManager))] - public void AddOpenIddict_KeyTypeDefaultsToString(Type type) + public void UseDefaultModels_KeyTypeDefaultsToString() { // Arrange var services = new ServiceCollection(); + var builder = services.AddOpenIddict().AddCore(); // Act - services.AddOpenIddict(); + builder.UseDefaultModels(); // Assert - Assert.Contains(services, service => service.ImplementationType == type); + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; + + Assert.Equal(typeof(OpenIddictApplication), options.DefaultApplicationType); + Assert.Equal(typeof(OpenIddictAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(OpenIddictScope), options.DefaultScopeType); + Assert.Equal(typeof(OpenIddictToken), options.DefaultTokenType); } - [Theory] - [InlineData(typeof(OpenIddictApplicationManager>))] - [InlineData(typeof(OpenIddictAuthorizationManager>))] - [InlineData(typeof(OpenIddictScopeManager>))] - [InlineData(typeof(OpenIddictTokenManager>))] - public void AddOpenIddict_KeyTypeCanBeOverriden(Type type) + public void UseDefaultModels_KeyTypeCanBeOverriden() { // Arrange var services = new ServiceCollection(); + var builder = services.AddOpenIddict().AddCore(); // Act - services.AddOpenIddict(); + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - // Assert - Assert.Contains(services, service => service.ImplementationType == type); + Assert.Equal(typeof(OpenIddictApplication), options.DefaultApplicationType); + Assert.Equal(typeof(OpenIddictAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(OpenIddictScope), options.DefaultScopeType); + Assert.Equal(typeof(OpenIddictToken), options.DefaultTokenType); } } }