diff --git a/OpenIddict.sln b/OpenIddict.sln index fa904b3d..a3571712 100644 --- a/OpenIddict.sln +++ b/OpenIddict.sln @@ -46,6 +46,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFramework" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFramework.Tests", "test\OpenIddict.EntityFramework.Tests\OpenIddict.EntityFramework.Tests.csproj", "{96325E37-9897-43AC-8408-7B17F58E8788}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Stores", "src\OpenIddict.Stores\OpenIddict.Stores.csproj", "{275D888A-B4C8-4E93-AC4B-B1AA25D98159}" +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}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -104,6 +110,18 @@ Global {96325E37-9897-43AC-8408-7B17F58E8788}.Debug|Any CPU.Build.0 = Debug|Any CPU {96325E37-9897-43AC-8408-7B17F58E8788}.Release|Any CPU.ActiveCfg = Release|Any CPU {96325E37-9897-43AC-8408-7B17F58E8788}.Release|Any CPU.Build.0 = Release|Any CPU + {275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Debug|Any CPU.Build.0 = Debug|Any CPU + {275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Release|Any CPU.ActiveCfg = Release|Any CPU + {275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Release|Any CPU.Build.0 = Release|Any CPU + {21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Release|Any CPU.Build.0 = Release|Any CPU + {07B02B98-8A68-432D-A932-48E6D52B221A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -122,6 +140,9 @@ Global {0102A6CC-41A6-4B34-B49E-65AFE95882BB} = {D544447C-D701-46BB-9A5B-C76C612A596B} {BF42CC6C-0B56-4F66-9866-18B8393F3C06} = {D544447C-D701-46BB-9A5B-C76C612A596B} {96325E37-9897-43AC-8408-7B17F58E8788} = {5FC71D6A-A994-4F62-977F-88A7D25379D7} + {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616} diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index 316ea0d6..31b35cfd 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -12,17 +12,12 @@ aspnetcore;authentication;jwt;openidconnect;openiddict;security - - - - - diff --git a/src/OpenIddict.Core/OpenIddictBuilder.cs b/src/OpenIddict.Core/OpenIddictBuilder.cs index cd156dc4..08802957 100644 --- a/src/OpenIddict.Core/OpenIddictBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictBuilder.cs @@ -8,7 +8,6 @@ using System; using System.ComponentModel; using JetBrains.Annotations; using OpenIddict.Core; -using OpenIddict.Models; #if NETSTANDARD1_3 using System.Reflection; @@ -39,25 +38,25 @@ namespace Microsoft.Extensions.DependencyInjection /// Gets or sets the type corresponding to the Application entity. /// [EditorBrowsable(EditorBrowsableState.Never)] - public Type ApplicationType { get; set; } = typeof(OpenIddictApplication); + public Type ApplicationType { get; set; } /// /// Gets or sets the type corresponding to the Authorization entity. /// [EditorBrowsable(EditorBrowsableState.Never)] - public Type AuthorizationType { get; set; } = typeof(OpenIddictAuthorization); + public Type AuthorizationType { get; set; } /// /// Gets or sets the type corresponding to the Scope entity. /// [EditorBrowsable(EditorBrowsableState.Never)] - public Type ScopeType { get; set; } = typeof(OpenIddictScope); + public Type ScopeType { get; set; } /// /// Gets or sets the type corresponding to the Token entity. /// [EditorBrowsable(EditorBrowsableState.Never)] - public Type TokenType { get; set; } = typeof(OpenIddictToken); + public Type TokenType { get; set; } /// /// Gets the services collection. diff --git a/src/OpenIddict.Core/OpenIddictExtensions.cs b/src/OpenIddict.Core/OpenIddictExtensions.cs index 84fdf251..25424759 100644 --- a/src/OpenIddict.Core/OpenIddictExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictExtensions.cs @@ -8,42 +8,11 @@ using System; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.Core; -using OpenIddict.Models; 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. - /// - /// The services collection. - /// The . - public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) - { - 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>(); - } - /// /// Registers the default OpenIddict services in the DI container, using the specified entities. /// @@ -86,42 +55,6 @@ namespace Microsoft.Extensions.DependencyInjection return 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); - } - - /// - /// 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 configuration delegate used to register new services. - /// The . - public static IServiceCollection AddOpenIddict( - [NotNull] this IServiceCollection services, - [NotNull] Action configuration) - where TKey : IEquatable - { - return services.AddOpenIddict, - OpenIddictAuthorization, - OpenIddictScope, - OpenIddictToken>(configuration); - } - /// /// Registers the default OpenIddict services in the DI container, using the specified entities. /// diff --git a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj index 65d32066..be54d1e2 100644 --- a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj +++ b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index e5d1717b..4b2b2e06 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -14,8 +14,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; -using OpenIddict.Core; using OpenIddict.Models; +using OpenIddict.Stores; namespace OpenIddict.EntityFramework { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index beb2303c..75d0f3bc 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -16,6 +16,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; +using OpenIddict.Stores; namespace OpenIddict.EntityFramework { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs index 20aaab33..52a32538 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs @@ -56,7 +56,7 @@ namespace OpenIddict.EntityFramework /// The type of the Scope entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. - public class OpenIddictScopeStore : Core.OpenIddictScopeStore + public class OpenIddictScopeStore : Stores.OpenIddictScopeStore where TScope : OpenIddictScope, new() where TContext : DbContext where TKey : IEquatable diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 64f9fc5e..34b6629a 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -16,6 +16,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; +using OpenIddict.Stores; namespace OpenIddict.EntityFramework { diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj index 0f5cd493..d0878369 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index 62adf89c..53f46811 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs @@ -16,8 +16,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; -using OpenIddict.Core; using OpenIddict.Models; +using OpenIddict.Stores; namespace OpenIddict.EntityFrameworkCore { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index bc00816f..060d4352 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -18,6 +18,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; +using OpenIddict.Stores; namespace OpenIddict.EntityFrameworkCore { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs index 53bff672..5e254dad 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs @@ -53,7 +53,7 @@ namespace OpenIddict.EntityFrameworkCore /// The type of the Scope entity. /// The type of the Entity Framework database context. /// The type of the entity primary keys. - public class OpenIddictScopeStore : Core.OpenIddictScopeStore + public class OpenIddictScopeStore : Stores.OpenIddictScopeStore where TScope : OpenIddictScope, new() where TContext : DbContext where TKey : IEquatable diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 7a957880..31e1a500 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -18,6 +18,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; using OpenIddict.Core; using OpenIddict.Models; +using OpenIddict.Stores; namespace OpenIddict.EntityFrameworkCore { diff --git a/src/OpenIddict.Mvc/OpenIddict.Mvc.csproj b/src/OpenIddict.Mvc/OpenIddict.Mvc.csproj index 7a350f53..d943bbbf 100644 --- a/src/OpenIddict.Mvc/OpenIddict.Mvc.csproj +++ b/src/OpenIddict.Mvc/OpenIddict.Mvc.csproj @@ -13,11 +13,10 @@ - + - diff --git a/src/OpenIddict/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Authentication.cs similarity index 99% rename from src/OpenIddict/OpenIddictProvider.Authentication.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Authentication.cs index c037b725..60949384 100644 --- a/src/OpenIddict/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictProvider.Authentication.cs @@ -5,7 +5,6 @@ */ using System; -using System.Collections.Immutable; using System.IO; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; diff --git a/src/OpenIddict/OpenIddictProvider.Discovery.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Discovery.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Discovery.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Discovery.cs diff --git a/src/OpenIddict/OpenIddictProvider.Exchange.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Exchange.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Exchange.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Exchange.cs diff --git a/src/OpenIddict/OpenIddictProvider.Helpers.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Helpers.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Helpers.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Helpers.cs diff --git a/src/OpenIddict/OpenIddictProvider.Introspection.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Introspection.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Introspection.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Introspection.cs diff --git a/src/OpenIddict/OpenIddictProvider.Revocation.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Revocation.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Revocation.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Revocation.cs diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Serialization.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Serialization.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Serialization.cs diff --git a/src/OpenIddict/OpenIddictProvider.Session.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Session.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Session.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Session.cs diff --git a/src/OpenIddict/OpenIddictProvider.Userinfo.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.Userinfo.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.Userinfo.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.Userinfo.cs diff --git a/src/OpenIddict/OpenIddictProvider.cs b/src/OpenIddict.Server/Internal/OpenIddictProvider.cs similarity index 100% rename from src/OpenIddict/OpenIddictProvider.cs rename to src/OpenIddict.Server/Internal/OpenIddictProvider.cs diff --git a/src/OpenIddict.Server/OpenIddict.Server.csproj b/src/OpenIddict.Server/OpenIddict.Server.csproj new file mode 100644 index 00000000..34988f67 --- /dev/null +++ b/src/OpenIddict.Server/OpenIddict.Server.csproj @@ -0,0 +1,25 @@ + + + + + + net451;netstandard1.4 + + + + OpenID Connect server components for OpenIddict. + Kévin Chalet + aspnetcore;authentication;jwt;openidconnect;openiddict;security + + + + + + + + + + + + + diff --git a/src/OpenIddict.Server/OpenIddictExtensions.cs b/src/OpenIddict.Server/OpenIddictExtensions.cs new file mode 100644 index 00000000..9b604b48 --- /dev/null +++ b/src/OpenIddict.Server/OpenIddictExtensions.cs @@ -0,0 +1,1069 @@ +/* + * 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/OpenIddictOptions.cs b/src/OpenIddict.Server/OpenIddictOptions.cs similarity index 100% rename from src/OpenIddict/OpenIddictOptions.cs rename to src/OpenIddict.Server/OpenIddictOptions.cs diff --git a/src/OpenIddict.Stores/OpenIddict.Stores.csproj b/src/OpenIddict.Stores/OpenIddict.Stores.csproj new file mode 100644 index 00000000..aed6ec69 --- /dev/null +++ b/src/OpenIddict.Stores/OpenIddict.Stores.csproj @@ -0,0 +1,24 @@ + + + + + + net451;netstandard1.3 + + + + Default base stores for OpenIddict. + Kévin Chalet + aspnetcore;authentication;jwt;openidconnect;openiddict;security + + + + + + + + + + + + diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs rename to src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs index b4981416..d014f89f 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs @@ -15,9 +15,10 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OpenIddict.Core; using OpenIddict.Models; -namespace OpenIddict.Core +namespace OpenIddict.Stores { /// /// Provides methods allowing to manage the applications stored in a database. diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs rename to src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs index 6eae3a68..30d4d477 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs @@ -15,9 +15,10 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OpenIddict.Core; using OpenIddict.Models; -namespace OpenIddict.Core +namespace OpenIddict.Stores { /// /// Provides methods allowing to manage the authorizations stored in a database. diff --git a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs rename to src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs index 046dda95..f0ccb621 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs @@ -15,9 +15,10 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OpenIddict.Core; using OpenIddict.Models; -namespace OpenIddict.Core +namespace OpenIddict.Stores { /// /// Provides methods allowing to manage the scopes stored in a database. diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs similarity index 99% rename from src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs rename to src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs index 510cb210..83831467 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs @@ -15,9 +15,10 @@ using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OpenIddict.Core; using OpenIddict.Models; -namespace OpenIddict.Core +namespace OpenIddict.Stores { /// /// Provides methods allowing to manage the tokens stored in a database. diff --git a/src/OpenIddict/OpenIddict.csproj b/src/OpenIddict/OpenIddict.csproj index 49e0c916..297fe6ae 100644 --- a/src/OpenIddict/OpenIddict.csproj +++ b/src/OpenIddict/OpenIddict.csproj @@ -13,14 +13,12 @@ - + + - - - diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index f2607e21..64db7ac3 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -5,1065 +5,78 @@ */ 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; +using OpenIddict.Models; namespace Microsoft.AspNetCore.Builder { public static class OpenIddictExtensions { /// - /// Registers OpenIddict in the ASP.NET Core pipeline. + /// Registers the default OpenIddict services in the DI container, + /// using the default entities and the default entity key type. /// - /// 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 services collection. /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, - [NotNull] Stream stream, [NotNull] string password) + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) { - 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)); + return services.AddOpenIddict(); } /// - /// Registers a extracted from a - /// stream and used to sign the JWT tokens issued by OpenIddict. + /// Registers the default OpenIddict services in the DI container, + /// using the default entities and the specified entity key type. /// - /// 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 type of the entity primary keys. + /// The services collection. /// The . - public static OpenIddictBuilder AddSigningCertificate( - [NotNull] this OpenIddictBuilder builder, [NotNull] Stream stream, - [NotNull] string password, X509KeyStorageFlags flags) + public static OpenIddictBuilder AddOpenIddict([NotNull] this IServiceCollection services) + where TKey : IEquatable { - 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)); + return services.AddOpenIddict, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken>(); } /// - /// Registers a retrieved from the X.509 - /// machine store and used to sign the JWT tokens issued by OpenIddict. + /// Registers the default OpenIddict services in the DI container, + /// using the default entities and the default entity key type. /// - /// 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) + /// The services collection. + /// The configuration delegate used to register new services. + /// The . + public static IServiceCollection AddOpenIddict( + [NotNull] this IServiceCollection services, + [NotNull] Action configuration) { - 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)); + return services.AddOpenIddict(configuration); } /// - /// Registers a retrieved from the given - /// X.509 store and used to sign the JWT tokens issued by OpenIddict. + /// Registers the default OpenIddict services in the DI container, + /// using the default entities and the specified entity key type. /// - /// 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) + /// 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) + where TKey : IEquatable { - 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); + return services.AddOpenIddict, + OpenIddictAuthorization, + OpenIddictScope, + OpenIddictToken>(configuration); } } } \ No newline at end of file diff --git a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs index edb2ccfa..b3467ae8 100644 --- a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs @@ -6,45 +6,26 @@ using System; using Microsoft.Extensions.DependencyInjection; -using OpenIddict.Models; using Xunit; namespace OpenIddict.Core.Tests { public class OpenIddictExtensionsTests { - [Theory] - [InlineData(typeof(OpenIddictApplicationManager))] - [InlineData(typeof(OpenIddictAuthorizationManager))] - [InlineData(typeof(OpenIddictScopeManager))] - [InlineData(typeof(OpenIddictTokenManager))] - public void AddOpenIddict_KeyTypeDefaultsToString(Type type) - { - // Arrange - var services = new ServiceCollection(); - - // Act - services.AddOpenIddict(); - - // Assert - Assert.Contains(services, service => service.ImplementationType == type); - } - - [Theory] - [InlineData(typeof(OpenIddictApplicationManager>))] - [InlineData(typeof(OpenIddictAuthorizationManager>))] - [InlineData(typeof(OpenIddictScopeManager>))] - [InlineData(typeof(OpenIddictTokenManager>))] - public void AddOpenIddict_KeyTypeCanBeOverriden(Type type) + [Fact] + public void AddOpenIddict_CustomEntitiesAreCorrectlySet() { // Arrange var services = new ServiceCollection(); // Act - services.AddOpenIddict(); + var builder = services.AddOpenIddict(); // Assert - Assert.Contains(services, service => service.ImplementationType == type); + 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] @@ -52,7 +33,7 @@ namespace OpenIddict.Core.Tests [InlineData(typeof(OpenIddictAuthorizationManager))] [InlineData(typeof(OpenIddictScopeManager))] [InlineData(typeof(OpenIddictTokenManager))] - public void AddOpenIddict_DefaultEntitiesCanBeReplaced(Type type) + public void AddOpenIddict_ManagersForCustomEntitiesAreCorrectlyRegistered(Type type) { // Arrange var services = new ServiceCollection(); diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs index 45d9007c..874bd0b3 100644 --- a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs @@ -21,7 +21,10 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { - ApplicationType = typeof(object) + ApplicationType = typeof(object), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(OpenIddictScope), + TokenType = typeof(OpenIddictToken) }; // Act and assert @@ -40,7 +43,10 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { - AuthorizationType = typeof(object) + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(object), + ScopeType = typeof(OpenIddictScope), + TokenType = typeof(OpenIddictToken) }; // Act and assert @@ -59,7 +65,10 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { - ScopeType = typeof(object) + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(object), + TokenType = typeof(OpenIddictToken) }; // Act and assert @@ -78,6 +87,9 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(OpenIddictScope), TokenType = typeof(object) }; @@ -100,7 +112,13 @@ namespace OpenIddict.EntityFrameworkCore.Tests { // Arrange var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + var builder = new OpenIddictBuilder(services) + { + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(OpenIddictScope), + TokenType = typeof(OpenIddictToken) + }; // Act builder.AddEntityFrameworkStores(); diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs index 00b9f939..42752609 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs @@ -20,7 +20,10 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { - ApplicationType = typeof(object) + ApplicationType = typeof(object), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(OpenIddictScope), + TokenType = typeof(OpenIddictToken) }; // Act and assert @@ -39,7 +42,10 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { - AuthorizationType = typeof(object) + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(object), + ScopeType = typeof(OpenIddictScope), + TokenType = typeof(OpenIddictToken) }; // Act and assert @@ -58,7 +64,10 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { - ScopeType = typeof(object) + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(object), + TokenType = typeof(OpenIddictToken) }; // Act and assert @@ -77,6 +86,9 @@ namespace OpenIddict.EntityFrameworkCore.Tests // Arrange var builder = new OpenIddictBuilder(new ServiceCollection()) { + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(OpenIddictScope), TokenType = typeof(object) }; @@ -99,7 +111,13 @@ namespace OpenIddict.EntityFrameworkCore.Tests { // Arrange var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + var builder = new OpenIddictBuilder(services) + { + ApplicationType = typeof(OpenIddictApplication), + AuthorizationType = typeof(OpenIddictAuthorization), + ScopeType = typeof(OpenIddictScope), + TokenType = typeof(OpenIddictToken) + }; // Act builder.AddEntityFrameworkCoreStores(); diff --git a/test/OpenIddict.Tests/Certificate.pfx b/test/OpenIddict.Server.Tests/Certificate.pfx similarity index 100% rename from test/OpenIddict.Tests/Certificate.pfx rename to test/OpenIddict.Server.Tests/Certificate.pfx diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Authentication.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Authentication.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Discovery.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Discovery.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Exchange.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Exchange.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Introspection.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Introspection.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Revocation.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Revocation.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Serialization.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Serialization.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Session.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Session.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Userinfo.cs similarity index 100% rename from test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.Userinfo.cs diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.cs similarity index 99% rename from test/OpenIddict.Tests/OpenIddictProviderTests.cs rename to test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.cs index 7a659199..7f59576e 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictProviderTests.cs @@ -1374,7 +1374,7 @@ namespace OpenIddict.Tests // Register the X.509 certificate used to sign the identity tokens. options.AddSigningCertificate( assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly, - resource: "OpenIddict.Tests.Certificate.pfx", + resource: "OpenIddict.Server.Tests.Certificate.pfx", password: "OpenIddict"); // Note: overriding the default data protection provider is not necessary for the tests to pass, diff --git a/test/OpenIddict.Server.Tests/OpenIddict.Server.Tests.csproj b/test/OpenIddict.Server.Tests/OpenIddict.Server.Tests.csproj new file mode 100644 index 00000000..1f7e7af1 --- /dev/null +++ b/test/OpenIddict.Server.Tests/OpenIddict.Server.Tests.csproj @@ -0,0 +1,41 @@ + + + + + + netcoreapp1.0;net452 + netcoreapp1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefineConstants);SUPPORTS_ECDSA + + + diff --git a/test/OpenIddict.Server.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictExtensionsTests.cs new file mode 100644 index 00000000..6b022078 --- /dev/null +++ b/test/OpenIddict.Server.Tests/OpenIddictExtensionsTests.cs @@ -0,0 +1,911 @@ +/* + * 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.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.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Moq; +using OpenIddict.Models; +using Xunit; + +namespace OpenIddict.Tests +{ + public class OpenIddictExtensionsTests + { + + [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() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict"); + + var provider = services.BuildServiceProvider(); + 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() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AddEphemeralSigningKey(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(1, options.Value.SigningCredentials.Count); + } + + [Theory] + [InlineData(SecurityAlgorithms.RsaSha256Signature)] + [InlineData(SecurityAlgorithms.RsaSha384Signature)] + [InlineData(SecurityAlgorithms.RsaSha512Signature)] +#if SUPPORTS_ECDSA + [InlineData(SecurityAlgorithms.EcdsaSha256Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha384Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha512Signature)] +#endif + public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AddEphemeralSigningKey(algorithm); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + var credentials = options.Value.SigningCredentials[0]; + + // Assert + Assert.Equal(algorithm, credentials.Algorithm); + } + + [Theory] + [InlineData(SecurityAlgorithms.HmacSha256Signature)] + [InlineData(SecurityAlgorithms.RsaSha256Signature)] +#if SUPPORTS_ECDSA + [InlineData(SecurityAlgorithms.EcdsaSha256Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha384Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha512Signature)] +#endif + public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + var factory = Mock.Of(mock => + mock.IsSupportedAlgorithm(algorithm, It.IsAny())); + + var key = Mock.Of(mock => mock.CryptoProviderFactory == factory); + + // Act + builder.AddSigningKey(key); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Same(key, options.Value.SigningCredentials[0].Key); + } + + [Fact] + public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AddSigningCertificate( + assembly: typeof(OpenIddictExtensionsTests).GetTypeInfo().Assembly, + resource: "OpenIddict.Server.Tests.Certificate.pfx", + password: "OpenIddict"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key); + } + + [Fact] + public void AllowAuthorizationCodeFlow_CodeFlowIsAddedToGrantTypes() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AllowAuthorizationCodeFlow(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes); + } + + [Fact] + public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAddedToGrantTypes() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AllowClientCredentialsFlow(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes); + } + + [Fact] + public void AllowCustomFlow_CustomFlowIsAddedToGrantTypes() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes); + } + + [Fact] + public void AllowImplicitFlow_ImplicitFlowIsAddedToGrantTypes() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AllowImplicitFlow(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes); + } + + [Fact] + public void AllowPasswordFlow_PasswordFlowIsAddedToGrantTypes() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AllowPasswordFlow(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes); + } + + [Fact] + public void AllowRefreshTokenFlow_RefreshTokenFlowIsAddedToGrantTypes() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.AllowRefreshTokenFlow(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes); + } + + [Fact] + public void DisableConfigurationEndpoint_ConfigurationEndpointIsDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.DisableConfigurationEndpoint(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath); + } + + [Fact] + public void DisableCryptographyEndpoint_CryptographyEndpointIsDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.DisableCryptographyEndpoint(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath); + } + + [Fact] + public void DisableSlidingExpiration_SlidingExpirationIsDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.DisableSlidingExpiration(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.False(options.Value.UseSlidingExpiration); + } + + [Fact] + public void DisableTokenRevocation_TokenRevocationIsDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.DisableTokenRevocation(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.True(options.Value.DisableTokenRevocation); + } + + [Fact] + public void EnableAuthorizationEndpoint_AuthorizationEndpointIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableAuthorizationEndpoint("/endpoint-path"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath); + } + + [Fact] + public void EnableIntrospectionEndpoint_IntrospectionEndpointIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableIntrospectionEndpoint("/endpoint-path"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath); + } + + [Fact] + public void EnableLogoutEndpoint_LogoutEndpointIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableLogoutEndpoint("/endpoint-path"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath); + } + + [Fact] + public void EnableRequestCaching_RequestCachingIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableRequestCaching(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.True(options.Value.EnableRequestCaching); + } + + [Fact] + public void EnableRevocationEndpoint_RevocationEndpointIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableRevocationEndpoint("/endpoint-path"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath); + } + + [Fact] + public void EnableScopeValidation_ScopeValidationIsDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableScopeValidation(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.True(options.Value.EnableScopeValidation); + } + + [Fact] + public void EnableTokenEndpoint_TokenEndpointIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableTokenEndpoint("/endpoint-path"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath); + } + + [Fact] + public void EnableUserinfoEndpoint_UserinfoEndpointIsEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.EnableUserinfoEndpoint("/endpoint-path"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath); + } + + [Fact] + public void RequireClientIdentification_ClientIdentificationIsEnforced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.RequireClientIdentification(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.True(options.Value.RequireClientIdentification); + } + + [Fact] + public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime); + } + + [Fact] + public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime); + } + + [Fact] + public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime); + } + + [Fact] + public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime); + } + + [Fact] + public void SetIssuer_AddressIsReplaced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.SetIssuer(new Uri("http://www.fabrikam.com/")); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Value.Issuer); + } + + [Fact] + public void RegisterClaims_ClaimsAreAdded() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.RegisterClaims("custom_claim_1", "custom_claim_2"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains("custom_claim_1", options.Value.Claims); + Assert.Contains("custom_claim_2", options.Value.Claims); + } + + [Fact] + public void RegisterScopes_ScopesAreAdded() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.RegisterScopes("custom_scope_1", "custom_scope_2"); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.Contains("custom_scope_1", options.Value.Scopes); + Assert.Contains("custom_scope_2", options.Value.Scopes); + } + + [Fact] + public void UseDataProtectionProvider_DefaultProviderIsReplaced() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider); + } + + [Fact] + public void UseJsonWebTokens_AccessTokenHandlerIsCorrectlySet() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.UseJsonWebTokens(); + + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + + // Assert + Assert.IsType(options.Value.AccessTokenHandler); + } + + [Fact] + public void UseReferenceTokens_ReferenceTokensAreEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = CreateBuilder(services); + + // Act + builder.UseReferenceTokens(); + + var provider = services.BuildServiceProvider(); + 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) + }; + } +} diff --git a/test/OpenIddict.Tests/OpenIddict.Tests.csproj b/test/OpenIddict.Tests/OpenIddict.Tests.csproj index df06533e..896f2250 100644 --- a/test/OpenIddict.Tests/OpenIddict.Tests.csproj +++ b/test/OpenIddict.Tests/OpenIddict.Tests.csproj @@ -7,23 +7,12 @@ netcoreapp1.0 - - - - - - - - - - - - + @@ -34,8 +23,4 @@ - - $(DefineConstants);SUPPORTS_ECDSA - - diff --git a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs index 8259ffdd..6bc06d40 100644 --- a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs @@ -5,896 +5,48 @@ */ 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.Http; -using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using Moq; +using OpenIddict.Core; +using OpenIddict.Models; using Xunit; namespace OpenIddict.Tests { public class OpenIddictExtensionsTests { - [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() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.Configure(configuration => configuration.Description.DisplayName = "OpenIddict"); - - var provider = services.BuildServiceProvider(); - 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.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() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddEphemeralSigningKey(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(1, options.Value.SigningCredentials.Count); - } - [Theory] - [InlineData(SecurityAlgorithms.RsaSha256Signature)] - [InlineData(SecurityAlgorithms.RsaSha384Signature)] - [InlineData(SecurityAlgorithms.RsaSha512Signature)] -#if SUPPORTS_ECDSA - [InlineData(SecurityAlgorithms.EcdsaSha256Signature)] - [InlineData(SecurityAlgorithms.EcdsaSha384Signature)] - [InlineData(SecurityAlgorithms.EcdsaSha512Signature)] -#endif - public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) + [InlineData(typeof(OpenIddictApplicationManager))] + [InlineData(typeof(OpenIddictAuthorizationManager))] + [InlineData(typeof(OpenIddictScopeManager))] + [InlineData(typeof(OpenIddictTokenManager))] + public void AddOpenIddict_KeyTypeDefaultsToString(Type type) { // Arrange var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); // Act - builder.AddEphemeralSigningKey(algorithm); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - var credentials = options.Value.SigningCredentials[0]; + services.AddOpenIddict(); // Assert - Assert.Equal(algorithm, credentials.Algorithm); + Assert.Contains(services, service => service.ImplementationType == type); } [Theory] - [InlineData(SecurityAlgorithms.HmacSha256Signature)] - [InlineData(SecurityAlgorithms.RsaSha256Signature)] -#if SUPPORTS_ECDSA - [InlineData(SecurityAlgorithms.EcdsaSha256Signature)] - [InlineData(SecurityAlgorithms.EcdsaSha384Signature)] - [InlineData(SecurityAlgorithms.EcdsaSha512Signature)] -#endif - public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) + [InlineData(typeof(OpenIddictApplicationManager>))] + [InlineData(typeof(OpenIddictAuthorizationManager>))] + [InlineData(typeof(OpenIddictScopeManager>))] + [InlineData(typeof(OpenIddictTokenManager>))] + public void AddOpenIddict_KeyTypeCanBeOverriden(Type type) { // Arrange var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - var factory = Mock.Of(mock => - mock.IsSupportedAlgorithm(algorithm, It.IsAny())); - - var key = Mock.Of(mock => mock.CryptoProviderFactory == factory); - - // Act - builder.AddSigningKey(key); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Same(key, options.Value.SigningCredentials[0].Key); - } - - [Fact] - public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddSigningCertificate( - assembly: typeof(OpenIddictExtensionsTests).GetTypeInfo().Assembly, - resource: "OpenIddict.Tests.Certificate.pfx", - password: "OpenIddict"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.IsType(typeof(X509SecurityKey), options.Value.SigningCredentials[0].Key); - } - - [Fact] - public void AllowAuthorizationCodeFlow_CodeFlowIsAddedToGrantTypes() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); // Act - builder.AllowAuthorizationCodeFlow(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode, options.Value.GrantTypes); - } - - [Fact] - public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAddedToGrantTypes() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AllowClientCredentialsFlow(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains(OpenIdConnectConstants.GrantTypes.ClientCredentials, options.Value.GrantTypes); - } - - [Fact] - public void AllowCustomFlow_CustomFlowIsAddedToGrantTypes() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.Value.GrantTypes); - } - - [Fact] - public void AllowImplicitFlow_ImplicitFlowIsAddedToGrantTypes() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AllowImplicitFlow(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains(OpenIdConnectConstants.GrantTypes.Implicit, options.Value.GrantTypes); - } - - [Fact] - public void AllowPasswordFlow_PasswordFlowIsAddedToGrantTypes() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AllowPasswordFlow(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains(OpenIdConnectConstants.GrantTypes.Password, options.Value.GrantTypes); - } - - [Fact] - public void AllowRefreshTokenFlow_RefreshTokenFlowIsAddedToGrantTypes() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.AllowRefreshTokenFlow(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken, options.Value.GrantTypes); - } - - [Fact] - public void DisableConfigurationEndpoint_ConfigurationEndpointIsDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.DisableConfigurationEndpoint(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(PathString.Empty, options.Value.ConfigurationEndpointPath); - } - - [Fact] - public void DisableCryptographyEndpoint_CryptographyEndpointIsDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.DisableCryptographyEndpoint(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath); - } - - [Fact] - public void DisableSlidingExpiration_SlidingExpirationIsDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.DisableSlidingExpiration(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.False(options.Value.UseSlidingExpiration); - } - - [Fact] - public void DisableTokenRevocation_TokenRevocationIsDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.DisableTokenRevocation(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.True(options.Value.DisableTokenRevocation); - } - - [Fact] - public void EnableAuthorizationEndpoint_AuthorizationEndpointIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableAuthorizationEndpoint("/endpoint-path"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal("/endpoint-path", options.Value.AuthorizationEndpointPath); - } - - [Fact] - public void EnableIntrospectionEndpoint_IntrospectionEndpointIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableIntrospectionEndpoint("/endpoint-path"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal("/endpoint-path", options.Value.IntrospectionEndpointPath); - } - - [Fact] - public void EnableLogoutEndpoint_LogoutEndpointIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableLogoutEndpoint("/endpoint-path"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal("/endpoint-path", options.Value.LogoutEndpointPath); - } - - [Fact] - public void EnableRequestCaching_RequestCachingIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableRequestCaching(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.True(options.Value.EnableRequestCaching); - } - - [Fact] - public void EnableRevocationEndpoint_RevocationEndpointIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableRevocationEndpoint("/endpoint-path"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal("/endpoint-path", options.Value.RevocationEndpointPath); - } - - [Fact] - public void EnableScopeValidation_ScopeValidationIsDisabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableScopeValidation(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.True(options.Value.EnableScopeValidation); - } - - [Fact] - public void EnableTokenEndpoint_TokenEndpointIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableTokenEndpoint("/endpoint-path"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal("/endpoint-path", options.Value.TokenEndpointPath); - } - - [Fact] - public void EnableUserinfoEndpoint_UserinfoEndpointIsEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.EnableUserinfoEndpoint("/endpoint-path"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal("/endpoint-path", options.Value.UserinfoEndpointPath); - } - - [Fact] - public void RequireClientIdentification_ClientIdentificationIsEnforced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.RequireClientIdentification(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.True(options.Value.RequireClientIdentification); - } - - [Fact] - public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AccessTokenLifetime); - } - - [Fact] - public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.Value.AuthorizationCodeLifetime); - } - - [Fact] - public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.Value.IdentityTokenLifetime); - } - - [Fact] - public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.Value.RefreshTokenLifetime); - } - - [Fact] - public void SetIssuer_AddressIsReplaced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.SetIssuer(new Uri("http://www.fabrikam.com/")); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Value.Issuer); - } - - [Fact] - public void RegisterClaims_ClaimsAreAdded() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.RegisterClaims("custom_claim_1", "custom_claim_2"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains("custom_claim_1", options.Value.Claims); - Assert.Contains("custom_claim_2", options.Value.Claims); - } - - [Fact] - public void RegisterScopes_ScopesAreAdded() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.RegisterScopes("custom_scope_1", "custom_scope_2"); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.Contains("custom_scope_1", options.Value.Scopes); - Assert.Contains("custom_scope_2", options.Value.Scopes); - } - - [Fact] - public void UseDataProtectionProvider_DefaultProviderIsReplaced() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.IsType(typeof(EphemeralDataProtectionProvider), options.Value.DataProtectionProvider); - } - - [Fact] - public void UseJsonWebTokens_AccessTokenHandlerIsCorrectlySet() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.UseJsonWebTokens(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - - // Assert - Assert.IsType(options.Value.AccessTokenHandler); - } - - [Fact] - public void UseReferenceTokens_ReferenceTokensAreEnabled() - { - // Arrange - var services = new ServiceCollection(); - services.AddOptions(); - - var builder = new OpenIddictBuilder(services); - - // Act - builder.UseReferenceTokens(); - - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); + services.AddOpenIddict(); // Assert - Assert.True(options.Value.UseReferenceTokens); + Assert.Contains(services, service => service.ImplementationType == type); } } }