/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/openiddict/openiddict-core for more information concerning * the license and the contributors participating to this project. */ using System; using System.Linq; using System.Text; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; namespace OpenIddict.Server.Internal { /// /// Contains the methods required to ensure that the OpenIddict server configuration is valid. /// Note: this API supports the OpenIddict infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future minor releases. /// public class OpenIddictServerInitializer : IPostConfigureOptions { private readonly IDistributedCache _cache; private readonly IDataProtectionProvider _dataProtectionProvider; /// /// Creates a new instance of the class. /// Note: this API supports the OpenIddict infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future minor releases. /// public OpenIddictServerInitializer( [NotNull] IDistributedCache cache, [NotNull] IDataProtectionProvider dataProtectionProvider) { _cache = cache; _dataProtectionProvider = dataProtectionProvider; } /// /// Populates the default OpenID Connect server options and ensure /// that the configuration is in a consistent and valid state. /// /// The authentication scheme associated with the handler instance. /// The options instance to initialize. public void PostConfigure([NotNull] string name, [NotNull] OpenIddictServerOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (string.IsNullOrEmpty(name)) { throw new ArgumentException("The options instance name cannot be null or empty.", nameof(name)); } if (options.RandomNumberGenerator == null) { throw new InvalidOperationException("A random number generator must be registered."); } if (options.ProviderType == null || options.ProviderType != typeof(OpenIddictServerProvider)) { throw new InvalidOperationException(new StringBuilder() .AppendLine("OpenIddict can only be used with its built-in server provider.") .AppendLine("This error may indicate that 'OpenIddictServerOptions.ProviderType' was manually set.") .Append("To execute custom request handling logic, consider registering an event handler using ") .Append("the generic 'services.AddOpenIddict().AddServer().AddEventHandler()' method.") .ToString()); } // When no distributed cache has been registered in the options, // try to resolve it from the dependency injection container. if (options.Cache == null) { options.Cache = _cache; } // 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 = _dataProtectionProvider; } if (options.AccessTokenFormat == null) { var protector = options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerHandler), nameof(options.AccessTokenFormat), nameof(options.UseReferenceTokens), name); options.AccessTokenFormat = new TicketDataFormat(protector); } if (options.AuthorizationCodeFormat == null) { var protector = options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerHandler), nameof(options.AuthorizationCodeFormat), nameof(options.UseReferenceTokens), name); options.AuthorizationCodeFormat = new TicketDataFormat(protector); } if (options.RefreshTokenFormat == null) { var protector = options.DataProtectionProvider.CreateProtector( nameof(OpenIdConnectServerHandler), nameof(options.RefreshTokenFormat), nameof(options.UseReferenceTokens), name); 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(OpenIddictConstants.GrantTypes.AuthorizationCode) || options.GrantTypes.Contains(OpenIddictConstants.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(OpenIddictConstants.GrantTypes.AuthorizationCode) || options.GrantTypes.Contains(OpenIddictConstants.GrantTypes.ClientCredentials) || options.GrantTypes.Contains(OpenIddictConstants.GrantTypes.Password) || options.GrantTypes.Contains(OpenIddictConstants.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.EnableRequestCaching && options.RequestCachingPolicy == null) { throw new InvalidOperationException("A caching policy must be specified when enabling request caching."); } if (options.RevocationEndpointPath.HasValue && options.DisableTokenStorage) { throw new InvalidOperationException("The revocation endpoint cannot be enabled when token storage is disabled."); } if (options.UseReferenceTokens && options.DisableTokenStorage) { throw new InvalidOperationException("Reference tokens cannot be used when disabling token storage."); } if (options.UseReferenceTokens && options.AccessTokenHandler != null) { throw new InvalidOperationException("Reference tokens cannot be used when configuring JWT as the access token format."); } if (options.UseSlidingExpiration && options.DisableTokenStorage && !options.UseRollingTokens) { throw new InvalidOperationException( "Sliding expiration must be disabled when turning off token storage if rolling tokens are not used."); } if (options.AccessTokenHandler != null && options.SigningCredentials.Count == 0) { throw new InvalidOperationException(new StringBuilder() .AppendLine("At least one signing key must be registered when using JWT as the access token format.") .Append("Consider registering a certificate using 'services.AddOpenIddict().AddServer().AddSigningCertificate()' ") .Append("or 'services.AddOpenIddict().AddServer().AddDevelopmentSigningCertificate()' or call ") .Append("'services.AddOpenIddict().AddServer().AddEphemeralSigningKey()' to use an ephemeral key.") .ToString()); } // Ensure at least one asymmetric signing certificate/key was registered if the implicit flow was enabled. if (!options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey) && options.GrantTypes.Contains(OpenIddictConstants.GrantTypes.Implicit)) { throw new InvalidOperationException(new StringBuilder() .AppendLine("At least one asymmetric signing key must be registered when enabling the implicit flow.") .Append("Consider registering a certificate using 'services.AddOpenIddict().AddServer().AddSigningCertificate()' ") .Append("or 'services.AddOpenIddict().AddServer().AddDevelopmentSigningCertificate()' or call ") .Append("'services.AddOpenIddict().AddServer().AddEphemeralSigningKey()' to use an ephemeral key.") .ToString()); } // Automatically add the offline_access scope if the refresh token grant has been enabled. if (options.GrantTypes.Contains(OpenIddictConstants.GrantTypes.RefreshToken)) { options.Scopes.Add(OpenIddictConstants.Scopes.OfflineAccess); } } } }