// *******************************************************************

        /// <summary>
        /// This method configures the specified <typeparamref name="TOptions"/>
        /// object as a singleton service.
        /// parameter.
        /// </summary>
        /// <typeparam name="TOptions">The type of associated options.</typeparam>
        /// <param name="serviceCollection">The service collection to use for the
        /// operation.</param>
        /// <param name="dataProtector">The data protector to use for the operation.</param>
        /// <param name="configuration">The configuration to use for the operation.</param>
        /// <param name="options">The bound and validated options instance.</param>
        /// <returns>The value of the <paramref name="serviceCollection"/> parameter,
        /// for chaining calls together.</returns>
        /// <exception cref="ArgumentException">This exception is thrown whenever
        /// one or more of the required parameters is missing or invalid.</exception>
        /// <exception cref="ValidationException">This exception is thrown whenever
        /// the <typeparamref name="TOptions"/> object fails to validate properly
        /// after the bind operation.</exception>
        /// <exception cref="OptionsException">This exception is thrown whenever the method
        /// encounters a configuration with no settings.</exception>
        /// <remarks>
        /// <para>
        /// In this method, the options are read from the configuration, bound to an
        /// instance of <typeparamref name="TOptions"/>, any decorated properties
        /// on that object are then unprotected, then the object is verified (if the
        /// <typeparamref name="TOptions"/> type derives from <see cref="OptionsBase"/>),
        /// and finally the object is registered with <paramref name="serviceCollection"/>
        /// as a singleton service, using <typeparamref name="TOptions"/> as the service
        /// type. The unadorned option instance is returned using the <paramref name="options"/>
        /// parameter - for scenarios where options need to be configured and then immediately
        /// used for other configuration purposes.
        /// </para>
        /// </remarks>
        public static IServiceCollection ConfigureOptions <TOptions>(
            this IServiceCollection serviceCollection,
            IDataProtector dataProtector,
            IConfiguration configuration,
            out TOptions options
            ) where TOptions : class, new()
        {
            // Validate the parameters before attempting to use them.
            Guard.Instance().ThrowIfNull(serviceCollection, nameof(serviceCollection))
            .ThrowIfNull(dataProtector, nameof(dataProtector))
            .ThrowIfNull(configuration, nameof(configuration));

            // Create the options.
            options = new TOptions();

            // Bind the options to the configuration.
            configuration.Bind(options);

            // Unprotect any decorated properties.
            dataProtector.UnprotectProperties(options);

            // Verify the result - if possible.
            (options as OptionsBase)?.ThrowIfInvalid();

            // Add the options to the DI container.
            serviceCollection.TryAddSingleton <IOptions <TOptions> >(
                new OptionsWrapper <TOptions>(options)
                );

            // Return the service collection.
            return(serviceCollection);
        }
        // *******************************************************************

        /// <summary>
        /// This method attempts to configure the specified <typeparamref name="TOptions"/>
        /// object as a singleton service.
        /// </summary>
        /// <typeparam name="TOptions">The type of associated options.</typeparam>
        /// <param name="serviceCollection">The service collection to use for the
        /// operation.</param>
        /// <param name="dataProtector">The data protector to use for the operation.</param>
        /// <param name="configuration">The configuration to use for the operation.</param>
        /// <param name="options">The options that were created by the operation.</param>
        /// <returns>True if the options were configured; false otherwise.</returns>
        /// <exception cref="ArgumentException">This exception is thrown whenever
        /// one or more of the required parameters is missing or invalid.</exception>
        /// <remarks>
        /// <para>
        /// In this method, the options are read from the configuration, bound to an
        /// instance of <typeparamref name="TOptions"/>, any decorated properties
        /// on that object are then unprotected, then the object is verified (if the
        /// <typeparamref name="TOptions"/> type derives from <see cref="OptionsBase"/>),
        /// and finally the object is registered with <paramref name="serviceCollection"/>
        /// as a singleton service, using <typeparamref name="TOptions"/> as the service
        /// type. The unadorned option instance is returned using the <paramref name="options"/>
        /// parameter - for scenarios where options need to be configured and then immediately
        /// used for other configuration purposes.
        /// </para>
        /// </remarks>
        public static bool TryConfigureOptions <TOptions>(
            this IServiceCollection serviceCollection,
            IDataProtector dataProtector,
            IConfiguration configuration,
            out TOptions options
            ) where TOptions : class, new()
        {
            // Validate the parameters before attempting to use them.
            Guard.Instance().ThrowIfNull(serviceCollection, nameof(serviceCollection))
            .ThrowIfNull(dataProtector, nameof(dataProtector))
            .ThrowIfNull(configuration, nameof(configuration));

            // Call the non-data protection overload.
            var result = serviceCollection.TryConfigureOptions <TOptions>(
                configuration,
                out options
                );

            // Did we succeed?
            if (true == result)
            {
                // Unprotect any decorated properties.
                dataProtector.UnprotectProperties(options);
            }

            // Return the results.
            return(result);
        }
        // *******************************************************************

        /// <summary>
        /// This method attempts to configure the specified <typeparamref name="TOptions"/>
        /// object as a singleton service.
        /// </summary>
        /// <typeparam name="TOptions">The type of associated options.</typeparam>
        /// <typeparam name="TImplementation">The type of associated options interface.</typeparam>
        /// <param name="serviceCollection">The service collection to use for the
        /// operation.</param>
        /// <param name="dataProtector">The data protector to use for the operation.</param>
        /// <param name="options">The options to use for the operation.</param>
        /// <returns>True if the options were configured; false otherwise.</returns>
        /// <exception cref="ArgumentException">This exception is thrown whenever
        /// one or more of the required parameters is missing or invalid.</exception>
        /// <remarks>
        /// <para>
        /// In this method, any decorated properties on the options object are unprotected,
        /// then the object is verified (if the <typeparamref name="TImplementation"/> type
        /// derives from <see cref="OptionsBase"/>), and finally the object is registered with
        /// <paramref name="serviceCollection"/> as a singleton service, using <typeparamref name="TOptions"/>
        /// as the service type.
        /// </para>
        /// </remarks>
        public static bool TryConfigureOptions <TOptions, TImplementation>(
            this IServiceCollection serviceCollection,
            IDataProtector dataProtector,
            TImplementation options
            ) where TOptions : class
            where TImplementation : class, TOptions, new()
        {
            // Validate the parameters before attempting to use them.
            Guard.Instance().ThrowIfNull(serviceCollection, nameof(serviceCollection))
            .ThrowIfNull(dataProtector, nameof(dataProtector))
            .ThrowIfNull(options, nameof(options));

            // Unprotect any decorated properties.
            dataProtector.UnprotectProperties(options);

            // Are the options verifiable?
            if (options is OptionsBase)
            {
                // Are the options not valid?
                if (false == (options as OptionsBase).IsValid())
                {
                    // Return the results.
                    return(false);
                }
            }

            // Add the options to the DI container.
            serviceCollection.TryAddSingleton <IOptions <TOptions> >(
                new OptionsWrapper <TImplementation>(options)
                );

            // Return the results.
            return(true);
        }
Exemple #4
0
        // *******************************************************************

        /// <summary>
        /// This method decrypts the value of any properties on the specified
        /// <paramref name="options"/> object that: (1) are decorated with a
        /// <see cref="ProtectedPropertyAttribute"/> attribute, (2) are of
        /// type: string, and (3) have a value in them.
        /// </summary>
        /// <param name="dataProtector">The data protector object to use for
        /// the operation.</param>
        /// <param name="options">The options object to use for the operation.</param>
        /// <returns>The value of the <paramref name="dataProtector"/> parameter,
        /// for chaining calls together.</returns>
        /// <exception cref="ArgumentException">This exception is thrown whenever
        /// one or more of the required parameters is missing or invalid.</exception>
        /// <exception cref="InvalidOperationException">This exception is thrown whenever
        /// the underlying cryptography operation fails, for any reason.</exception>
        public static IDataProtector UnprotectProperties(
            this IDataProtector dataProtector,
            object options
            )
        {
            // Validate the parameters before attempting to use them.
            Guard.Instance().ThrowIfNull(dataProtector, nameof(dataProtector))
            .ThrowIfNull(options, nameof(options));

            // Get the actual type of object.
            var optionType = options.GetType();

            // Get a list of all object type properties.
            var props = optionType.GetProperties()
                        .Where(x => x.PropertyType.IsClass)
                        .Where(x => x.PropertyType != typeof(string))
                        .ToList();

            // Loop and unprotect each property, recursively.
            props.ForEach(prop =>
            {
                object obj = null;
                try
                {
                    // Get the object reference.
                    obj = prop.GetGetMethod().Invoke(
                        options,
                        Array.Empty <object>()
                        );

                    // Check for missing references first ...
                    if (null != obj)
                    {
                        // Unprotect any properties for the object.
                        dataProtector.UnprotectProperties(
                            obj
                            );
                    }
                }
                catch (Exception ex)
                {
                    // Wrap the exception.
                    throw new InvalidOperationException(
                        message: string.Format(
                            Resources.ConfigurationExtensions_UnprotectProperties,
                            prop.Name,
                            obj.GetType().Name
                            ),
                        innerException: ex
                        ).SetOriginator(nameof(DataProtectorExtensions))
                    .SetDateTime();
                }
            });

            // Get a list of all the read/write properties of type: string.
            props = optionType.GetProperties()
                    .Where(x => x.CanRead && x.CanWrite && x.PropertyType == typeof(string))
                    .ToList();

            // Loop and unprotect each property.
            props.ForEach(prop =>
            {
                try
                {
                    // Look for a custom attribute on the property.
                    var attr = prop.GetCustomAttributes(
                        true
                        ).OfType <ProtectedPropertyAttribute>()
                               .FirstOrDefault();

                    // Did we find one?
                    if (null != attr)
                    {
                        // If we get here then we should try to unprotect the value
                        //   of the decorated property.
                        var encryptedPropertyValue = prop.GetGetMethod().Invoke(
                            options,
                            Array.Empty <object>()
                            ) as string;

                        // Check for empty strings first ...
                        if (!string.IsNullOrEmpty(encryptedPropertyValue))
                        {
                            try
                            {
                                // Unprotect the value.
                                var unprotectedPropertyValue = dataProtector.Unprotect(
                                    encryptedPropertyValue
                                    );

                                // Write the unprotected string to the original property.
                                prop.GetSetMethod().Invoke(
                                    options,
                                    new[] { unprotectedPropertyValue }
                                    );
                            }
                            catch (CryptographicException ex)
                            {
                                // Is the encryption manditory?
                                if (false == attr.Optional)
                                {
                                    // Was it not a decryption failure?
                                    if ("An error occurred during a cryptographic operation." != ex.Message)
                                    {
                                        throw;
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    // Wrap the exception.
                    throw new InvalidOperationException(
                        message: string.Format(
                            Resources.ConfigurationExtensions_UnprotectProperties,
                            prop.Name,
                            options.GetType().Name
                            ),
                        innerException: ex
                        ).SetOriginator(nameof(DataProtectorExtensions))
                    .SetDateTime();
                }
            });

            // Return the configuration.
            return(dataProtector);
        }