Example #1
0
        /// <summary>
        /// Populates the default OpenIddict server options and ensures
        /// that the configuration is in a consistent and valid state.
        /// </summary>
        /// <param name="name">The name of the options instance to configure, if applicable.</param>
        /// <param name="options">The options instance to initialize.</param>
        public void PostConfigure(string name, OpenIddictServerOptions options)
        {
            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            // Explicitly disable all the features that are implicitly excluded when the degraded mode is active.
            if (options.EnableDegradedMode)
            {
                options.DisableAuthorizationStorage   = options.DisableTokenStorage = options.DisableRollingRefreshTokens = true;
                options.IgnoreEndpointPermissions     = options.IgnoreGrantTypePermissions = true;
                options.IgnoreResponseTypePermissions = options.IgnoreScopePermissions = true;
                options.UseReferenceAccessTokens      = options.UseReferenceRefreshTokens = false;
            }

            // Explicitly disable rolling refresh tokens when token storage is disabled.
            if (options.DisableTokenStorage)
            {
                options.DisableRollingRefreshTokens = true;
            }

            if (options.JsonWebTokenHandler is null)
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0075));
            }

            // Ensure at least one flow has been enabled.
            if (options.GrantTypes.Count == 0)
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0076));
            }

            var addresses = options.AuthorizationEndpointUris.Distinct()
                            .Concat(options.ConfigurationEndpointUris.Distinct())
                            .Concat(options.CryptographyEndpointUris.Distinct())
                            .Concat(options.DeviceEndpointUris.Distinct())
                            .Concat(options.IntrospectionEndpointUris.Distinct())
                            .Concat(options.LogoutEndpointUris.Distinct())
                            .Concat(options.RevocationEndpointUris.Distinct())
                            .Concat(options.TokenEndpointUris.Distinct())
                            .Concat(options.UserinfoEndpointUris.Distinct())
                            .Concat(options.VerificationEndpointUris.Distinct())
                            .ToList();

            // Ensure endpoint addresses are unique across endpoints.
            if (addresses.Count != addresses.Distinct().Count())
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0285));
            }

            // Ensure the authorization endpoint has been enabled when
            // the authorization code or implicit grants are supported.
            if (options.AuthorizationEndpointUris.Count == 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) ||
                                                                 options.GrantTypes.Contains(GrantTypes.Implicit)))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0077));
            }

            // Ensure the device endpoint has been enabled when the device grant is supported.
            if (options.DeviceEndpointUris.Count == 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0078));
            }

            // Ensure the token endpoint has been enabled when the authorization code,
            // client credentials, device, password or refresh token grants are supported.
            if (options.TokenEndpointUris.Count == 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) ||
                                                         options.GrantTypes.Contains(GrantTypes.ClientCredentials) ||
                                                         options.GrantTypes.Contains(GrantTypes.DeviceCode) ||
                                                         options.GrantTypes.Contains(GrantTypes.Password) ||
                                                         options.GrantTypes.Contains(GrantTypes.RefreshToken)))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0079));
            }

            // Ensure the verification endpoint has been enabled when the device grant is supported.
            if (options.VerificationEndpointUris.Count == 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0080));
            }

            // Ensure the device grant is allowed when the device endpoint is enabled.
            if (options.DeviceEndpointUris.Count > 0 && !options.GrantTypes.Contains(GrantTypes.DeviceCode))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0084));
            }

            // Ensure the grant types/response types configuration is consistent.
            foreach (var type in options.ResponseTypes)
            {
                var types = new HashSet <string>(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries), StringComparer.Ordinal);
                if (types.Contains(ResponseTypes.Code) && !options.GrantTypes.Contains(GrantTypes.AuthorizationCode))
                {
                    throw new InvalidOperationException(SR.FormatID0281(ResponseTypes.Code));
                }

                if (types.Contains(ResponseTypes.IdToken) && !options.GrantTypes.Contains(GrantTypes.Implicit))
                {
                    throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.IdToken));
                }

                if (types.Contains(ResponseTypes.Token) && !options.GrantTypes.Contains(GrantTypes.Implicit))
                {
                    throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.Token));
                }
            }

            // Ensure reference tokens support was not enabled when token storage is disabled.
            if (options.DisableTokenStorage && (options.UseReferenceAccessTokens || options.UseReferenceRefreshTokens))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0083));
            }

            if (options.EncryptionCredentials.Count == 0)
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0085));
            }

            if (!options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0086));
            }

            // If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid.
            if (options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey &&
                                                  (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now)))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0087));
            }

            // If all the registered signing credentials are backed by a X.509 certificate, at least one of them must be valid.
            if (options.SigningCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey &&
                                               (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now)))
            {
                throw new InvalidOperationException(SR.GetResourceString(SR.ID0088));
            }

            if (options.EnableDegradedMode)
            {
                // If the degraded mode was enabled, ensure custom validation handlers
                // have been registered for the endpoints that require manual validation.

                if (options.AuthorizationEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateAuthorizationRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0089));
                }

                if (options.DeviceEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateDeviceRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0090));
                }

                if (options.IntrospectionEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateIntrospectionRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0091));
                }

                if (options.LogoutEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateLogoutRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0092));
                }

                if (options.RevocationEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateRevocationRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0093));
                }

                if (options.TokenEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateTokenRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0094));
                }

                if (options.VerificationEndpointUris.Count != 0 && !options.Handlers.Any(
                        descriptor => descriptor.ContextType == typeof(ValidateVerificationRequestContext) &&
                        descriptor.Type == OpenIddictServerHandlerType.Custom &&
                        descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                {
                    throw new InvalidOperationException(SR.GetResourceString(SR.ID0095));
                }

                // If the degraded mode was enabled, ensure custom authentication/sign-in handlers
                // have been registered to deal with device/user codes validation and generation.

                if (options.GrantTypes.Contains(GrantTypes.DeviceCode))
                {
                    if (!options.Handlers.Any(
                            descriptor => descriptor.ContextType == typeof(ProcessAuthenticationContext) &&
                            descriptor.Type == OpenIddictServerHandlerType.Custom &&
                            descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                    {
                        throw new InvalidOperationException(SR.GetResourceString(SR.ID0096));
                    }

                    if (!options.Handlers.Any(
                            descriptor => descriptor.ContextType == typeof(ProcessSignInContext) &&
                            descriptor.Type == OpenIddictServerHandlerType.Custom &&
                            descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
                    {
                        throw new InvalidOperationException(SR.GetResourceString(SR.ID0097));
                    }
                }
            }

            // Sort the handlers collection using the order associated with each handler.
            options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));

            // Sort the encryption and signing credentials.
            options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key));
            options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key));

            // Generate a key identifier for the encryption/signing keys that don't already have one.
            foreach (var key in options.EncryptionCredentials
                     .Select(credentials => credentials.Key)
                     .Concat(options.SigningCredentials.Select(credentials => credentials.Key))
                     .Where(key => string.IsNullOrEmpty(key.KeyId)))
            {
                key.KeyId = GetKeyIdentifier(key);
            }

            // Attach the signing credentials to the token validation parameters.
            options.TokenValidationParameters.IssuerSigningKeys =
                from credentials in options.SigningCredentials
                select credentials.Key;

            // Attach the encryption credentials to the token validation parameters.
            options.TokenValidationParameters.TokenDecryptionKeys =
                from credentials in options.EncryptionCredentials
                select credentials.Key;