// ******************************************************************* /// <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); }
// ******************************************************************* /// <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); }