/// <summary>
        /// Populates the application using the specified descriptor.
        /// </summary>
        /// <param name="application">The application.</param>
        /// <param name="descriptor">The descriptor.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
        /// <returns>
        /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
        /// </returns>
        protected virtual async Task PopulateAsync([NotNull] TApplication application,
                                                   [NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
        {
            if (application == null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            if (descriptor == null)
            {
                throw new ArgumentNullException(nameof(descriptor));
            }

            await Store.SetClientIdAsync(application, descriptor.ClientId, cancellationToken);

            await Store.SetClientSecretAsync(application, descriptor.ClientSecret, cancellationToken);

            await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken);

            await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken);

            await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange(
                                                           descriptor.PostLogoutRedirectUris.Select(address => address.OriginalString)), cancellationToken);

            await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange(
                                                 descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken);
        }
        /// <summary>
        /// Creates a new application based on the specified descriptor.
        /// Note: the default implementation automatically hashes the client
        /// secret before storing it in the database, for security reasons.
        /// </summary>
        /// <param name="descriptor">The application descriptor.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
        /// <returns>
        /// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
        /// whose result returns the unique identifier associated with the application.
        /// </returns>
        public virtual async Task <TApplication> CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
        {
            if (descriptor == null)
            {
                throw new ArgumentNullException(nameof(descriptor));
            }

            var application = await Store.InstantiateAsync(cancellationToken);

            if (application == null)
            {
                throw new InvalidOperationException("An error occurred while trying to create a new application");
            }

            await PopulateAsync(application, descriptor, cancellationToken);

            var secret = await Store.GetClientSecretAsync(application, cancellationToken);

            if (!string.IsNullOrEmpty(secret))
            {
                await Store.SetClientSecretAsync(application, /* secret: */ null, cancellationToken);

                return(await CreateAsync(application, secret, cancellationToken));
            }

            return(await CreateAsync(application, cancellationToken));
        }
        /// <summary>
        /// Validates the application to ensure it's in a consistent state.
        /// </summary>
        /// <param name="application">The application.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
        /// <returns>
        /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
        /// </returns>
        protected virtual async Task ValidateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
        {
            var descriptor = new OpenIddictApplicationDescriptor
            {
                ClientId     = await Store.GetClientIdAsync(application, cancellationToken),
                ClientSecret = await Store.GetClientSecretAsync(application, cancellationToken),
                DisplayName  = await Store.GetDisplayNameAsync(application, cancellationToken),
                Type         = await Store.GetClientTypeAsync(application, cancellationToken)
            };

            foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
            {
                // Ensure the address is not null or empty.
                if (string.IsNullOrEmpty(address))
                {
                    throw new ArgumentException("Callback URLs cannot be null or empty.");
                }

                // Ensure the address is a valid absolute URL.
                if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
                {
                    throw new ArgumentException("Callback URLs must be valid absolute URLs.");
                }

                descriptor.PostLogoutRedirectUris.Add(uri);
            }

            foreach (var address in await Store.GetRedirectUrisAsync(application, cancellationToken))
            {
                // Ensure the address is not null or empty.
                if (string.IsNullOrEmpty(address))
                {
                    throw new ArgumentException("Callback URLs cannot be null or empty.");
                }

                // Ensure the address is a valid absolute URL.
                if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
                {
                    throw new ArgumentException("Callback URLs must be valid absolute URLs.");
                }

                descriptor.RedirectUris.Add(uri);
            }

            await ValidateAsync(descriptor, cancellationToken);
        }
        /// <summary>
        /// Creates a new application.
        /// Note: the default implementation automatically hashes the client
        /// secret before storing it in the database, for security reasons.
        /// </summary>
        /// <param name="descriptor">The application descriptor.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
        /// <returns>
        /// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
        /// whose result returns the unique identifier associated with the application.
        /// </returns>
        public virtual async Task <TApplication> CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
        {
            if (descriptor == null)
            {
                throw new ArgumentNullException(nameof(descriptor));
            }

            // If no client type was specified, assume it's a
            // public application if no secret was provided.
            if (string.IsNullOrEmpty(descriptor.Type))
            {
                descriptor.Type = string.IsNullOrEmpty(descriptor.ClientSecret) ?
                                  OpenIddictConstants.ClientTypes.Public :
                                  OpenIddictConstants.ClientTypes.Confidential;
            }

            // If the client is not a public application, throw an
            // exception as the client secret is required in this case.
            if (string.IsNullOrEmpty(descriptor.ClientSecret) &&
                !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("A client secret must be provided when creating " +
                                                    "a confidential or hybrid application.");
            }

            // Obfuscate the provided client secret.
            if (!string.IsNullOrEmpty(descriptor.ClientSecret))
            {
                descriptor.ClientSecret = await ObfuscateClientSecretAsync(descriptor.ClientSecret, cancellationToken);
            }

            await ValidateAsync(descriptor, cancellationToken);

            try
            {
                return(await Store.CreateAsync(descriptor, cancellationToken));
            }

            catch (Exception exception)
            {
                Logger.LogError(exception, "An exception occurred while trying to create a new application.");

                throw;
            }
        }
        /// <summary>
        /// Validates the application descriptor to ensure it's in a consistent state.
        /// </summary>
        /// <param name="descriptor">The application descriptor.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
        /// <returns>
        /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
        /// </returns>
        protected virtual Task ValidateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
        {
            if (descriptor == null)
            {
                throw new ArgumentNullException(nameof(descriptor));
            }

            if (string.IsNullOrEmpty(descriptor.ClientId))
            {
                throw new ArgumentException("The client identifier cannot be null or empty.", nameof(descriptor));
            }

            if (string.IsNullOrEmpty(descriptor.Type))
            {
                throw new ArgumentException("The client type cannot be null or empty.", nameof(descriptor));
            }

            // Ensure the application type is supported by the manager.
            if (!string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Hybrid, StringComparison.OrdinalIgnoreCase) &&
                !string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException("Only 'confidential', 'hybrid' or 'public' applications are " +
                                            "supported by the default application manager.", nameof(descriptor));
            }

            // Ensure a client secret was specified if the client is a confidential application.
            if (string.IsNullOrEmpty(descriptor.ClientSecret) &&
                string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException("The client secret cannot be null or empty for a confidential application.", nameof(descriptor));
            }

            // Ensure no client secret was specified if the client is a public application.
            else if (!string.IsNullOrEmpty(descriptor.ClientSecret) &&
                     string.Equals(descriptor.Type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(descriptor));
            }

            // When callback URLs are specified, ensure they are valid and spec-compliant.
            // See https://tools.ietf.org/html/rfc6749#section-3.1 for more information.
            foreach (var uri in descriptor.PostLogoutRedirectUris.Concat(descriptor.RedirectUris))
            {
                // Ensure the address is not null.
                if (uri == null)
                {
                    throw new ArgumentException("Callback URLs cannot be null.");
                }

                // Ensure the address is a valid and absolute URL.
                if (!uri.IsAbsoluteUri || !uri.IsWellFormedOriginalString())
                {
                    throw new ArgumentException("Callback URLs must be valid absolute URLs.");
                }

                // Ensure the address doesn't contain a fragment.
                if (!string.IsNullOrEmpty(uri.Fragment))
                {
                    throw new ArgumentException("Callback URLs cannot contain a fragment.");
                }
            }

            return(Task.CompletedTask);
        }
        /// <summary>
        /// Updates an existing application.
        /// </summary>
        /// <param name="application">The application to update.</param>
        /// <param name="operation">The delegate used to update the application based on the given descriptor.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
        /// <returns>
        /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
        /// </returns>
        public virtual async Task UpdateAsync([NotNull] TApplication application,
                                              [NotNull] Func <OpenIddictApplicationDescriptor, Task> operation, CancellationToken cancellationToken)
        {
            if (operation == null)
            {
                throw new ArgumentNullException(nameof(operation));
            }

            // Store the original client secret for later comparison.
            var secret = await Store.GetClientSecretAsync(application, cancellationToken);

            var descriptor = new OpenIddictApplicationDescriptor
            {
                ClientId     = await Store.GetClientIdAsync(application, cancellationToken),
                ClientSecret = secret,
                DisplayName  = await Store.GetDisplayNameAsync(application, cancellationToken),
                Type         = await Store.GetClientTypeAsync(application, cancellationToken)
            };

            foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
            {
                // Ensure the address is not null or empty.
                if (string.IsNullOrEmpty(address))
                {
                    throw new ArgumentException("Callback URLs cannot be null or empty.");
                }

                // Ensure the address is a valid absolute URL.
                if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
                {
                    throw new ArgumentException("Callback URLs must be valid absolute URLs.");
                }

                descriptor.PostLogoutRedirectUris.Add(uri);
            }

            foreach (var address in await Store.GetRedirectUrisAsync(application, cancellationToken))
            {
                // Ensure the address is not null or empty.
                if (string.IsNullOrEmpty(address))
                {
                    throw new ArgumentException("Callback URLs cannot be null or empty.");
                }

                // Ensure the address is a valid absolute URL.
                if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
                {
                    throw new ArgumentException("Callback URLs must be valid absolute URLs.");
                }

                descriptor.RedirectUris.Add(uri);
            }

            await operation(descriptor);
            await PopulateAsync(application, descriptor, cancellationToken);

            // If the client secret was updated, re-obfuscate it before persisting the changes.
            var comparand = await Store.GetClientSecretAsync(application, cancellationToken);

            if (!string.Equals(secret, comparand, StringComparison.Ordinal))
            {
                await UpdateAsync(application, comparand, cancellationToken);

                return;
            }

            await UpdateAsync(application, cancellationToken);
        }
 /// <summary>
 /// Creates a new application.
 /// </summary>
 /// <param name="descriptor">The application descriptor.</param>
 /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
 /// <returns>
 /// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the application.
 /// </returns>
 public abstract Task <TApplication> CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken);