Continued API improvements and refactoring
- Add helpful extension methods to Interfaces project - Auto heuristic detection now writes default protection settings to the ILogger - Cleanup dead methods / add useful methods in DataProtectionConfiguration - Update System.Web compatibility project to allow mapping MachineKey.Protect directly to IDataProtector.Protect
This commit is contained in:
parent
d3313f2b6e
commit
82d92064c5
|
|
@ -3,10 +3,15 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNet.DataProtection.Interfaces;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
#if DNX451 || DNXCORE50 // [[ISSUE1400]] Replace with DNX_ANY when it becomes available
|
||||
using Microsoft.Framework.Runtime;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -53,7 +58,7 @@ namespace Microsoft.AspNet.DataProtection
|
|||
/// Creates an <see cref="IDataProtector"/> given a list of purposes.
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="IDataProtectionProvider"/> from which to generate the purpose chain.</param>
|
||||
/// <param name="purpose">The primary purpose used to create the <see cref="IDataProtectionProvider"/>.</param>
|
||||
/// <param name="purpose">The primary purpose used to create the <see cref="IDataProtector"/>.</param>
|
||||
/// <param name="subPurposes">An optional list of secondary purposes which contribute to the purpose chain.
|
||||
/// If this list is provided it cannot contain null elements.</param>
|
||||
/// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
|
||||
|
|
@ -75,7 +80,93 @@ namespace Microsoft.AspNet.DataProtection
|
|||
}
|
||||
return protector ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a unique identifier for this application.
|
||||
/// </summary>
|
||||
/// <param name="services">The application-level <see cref="IServiceProvider"/>.</param>
|
||||
/// <returns>A unique application identifier, or null if <paramref name="services"/> is null
|
||||
/// or cannot provide a unique application identifier.</returns>
|
||||
/// <remarks>
|
||||
/// The returned identifier should be stable for repeated runs of this same application on
|
||||
/// this machine. Additionally, the identifier is only unique within the scope of a single
|
||||
/// machine, e.g., two different applications on two different machines may return the same
|
||||
/// value.
|
||||
/// </remarks>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static string GetApplicationUniqueIdentifier(this IServiceProvider services)
|
||||
{
|
||||
string discriminator = (services?.GetService(typeof(IApplicationDiscriminator)) as IApplicationDiscriminator)?.Discriminator;
|
||||
#if DNX451 || DNXCORE50 // [[ISSUE1400]] Replace with DNX_ANY when it becomes available
|
||||
if (discriminator == null)
|
||||
{
|
||||
discriminator = (services?.GetService(typeof(IApplicationEnvironment)) as IApplicationEnvironment)?.ApplicationBasePath;
|
||||
}
|
||||
#elif NET451 // do nothing
|
||||
#else
|
||||
#error A new target framework was added to project.json, but it's not accounted for in this #ifdef. Please change the #ifdef accordingly.
|
||||
#endif
|
||||
|
||||
// Remove whitespace and homogenize empty -> null
|
||||
discriminator = discriminator?.Trim();
|
||||
return (String.IsNullOrEmpty(discriminator)) ? null : discriminator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an <see cref="IDataProtectionProvider"/> from an <see cref="IServiceProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The service provider from which to retrieve the <see cref="IDataProtectionProvider"/>.</param>
|
||||
/// <returns>An <see cref="IDataProtectionProvider"/>. This method is guaranteed never to return null.</returns>
|
||||
/// <exception cref="InvalidOperationException">If no <see cref="IDataProtectionProvider"/> service exists in <paramref name="services"/>.</exception>
|
||||
public static IDataProtectionProvider GetDataProtectionProvider([NotNull] this IServiceProvider services)
|
||||
{
|
||||
// We have our own implementation of GetRequiredService<T> since we don't want to
|
||||
// take a dependency on DependencyInjection.Interfaces.
|
||||
IDataProtectionProvider provider = (IDataProtectionProvider)services.GetService(typeof(IDataProtectionProvider));
|
||||
if (provider == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatDataProtectionExtensions_NoService(typeof(IDataProtectionProvider).FullName));
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an <see cref="IDataProtector"/> from an <see cref="IServiceProvider"/> given a list of purposes.
|
||||
/// </summary>
|
||||
/// <param name="services">An <see cref="IServiceProvider"/> which contains the <see cref="IDataProtectionProvider"/>
|
||||
/// from which to generate the purpose chain.</param>
|
||||
/// <param name="purposes">The list of purposes which contribute to the purpose chain. This list must
|
||||
/// contain at least one element, and it may not contain null elements.</param>
|
||||
/// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
|
||||
/// <remarks>
|
||||
/// This is a convenience method which calls <see cref="GetDataProtectionProvider(IServiceProvider)"/>
|
||||
/// then <see cref="CreateProtector(IDataProtectionProvider, IEnumerable{string})"/>. See those methods'
|
||||
/// documentation for more information.
|
||||
/// </remarks>
|
||||
public static IDataProtector GetDataProtector([NotNull] this IServiceProvider services, [NotNull] IEnumerable<string> purposes)
|
||||
{
|
||||
return services.GetDataProtectionProvider().CreateProtector(purposes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an <see cref="IDataProtector"/> from an <see cref="IServiceProvider"/> given a list of purposes.
|
||||
/// </summary>
|
||||
/// <param name="services">An <see cref="IServiceProvider"/> which contains the <see cref="IDataProtectionProvider"/>
|
||||
/// from which to generate the purpose chain.</param>
|
||||
/// <param name="purpose">The primary purpose used to create the <see cref="IDataProtector"/>.</param>
|
||||
/// <param name="subPurposes">An optional list of secondary purposes which contribute to the purpose chain.
|
||||
/// If this list is provided it cannot contain null elements.</param>
|
||||
/// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
|
||||
/// <remarks>
|
||||
/// This is a convenience method which calls <see cref="GetDataProtectionProvider(IServiceProvider)"/>
|
||||
/// then <see cref="CreateProtector(IDataProtectionProvider, string, string[])"/>. See those methods'
|
||||
/// documentation for more information.
|
||||
/// </remarks>
|
||||
public static IDataProtector GetDataProtector([NotNull] this IServiceProvider services, [NotNull] string purpose, params string[] subPurposes)
|
||||
{
|
||||
return services.GetDataProtectionProvider().CreateProtector(purpose, subPurposes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically protects a piece of plaintext data.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,22 @@ namespace Microsoft.AspNet.DataProtection.Interfaces
|
|||
return GetString("CryptCommon_GenericError");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No service for type '{0}' has been registered.
|
||||
/// </summary>
|
||||
internal static string DataProtectionExtensions_NoService
|
||||
{
|
||||
get { return GetString("DataProtectionExtensions_NoService"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No service for type '{0}' has been registered.
|
||||
/// </summary>
|
||||
internal static string FormatDataProtectionExtensions_NoService(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("DataProtectionExtensions_NoService"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -126,4 +126,7 @@
|
|||
<data name="CryptCommon_GenericError" xml:space="preserve">
|
||||
<value>An error occurred during a cryptographic operation.</value>
|
||||
</data>
|
||||
<data name="DataProtectionExtensions_NoService" xml:space="preserve">
|
||||
<value>No service for type '{0}' has been registered.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -7,9 +7,15 @@
|
|||
},
|
||||
"frameworks": {
|
||||
"net451": { },
|
||||
"dnx451": { },
|
||||
"dnx451": {
|
||||
"dependencies": {
|
||||
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*"
|
||||
}
|
||||
},
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
|
||||
"System.ComponentModel": "4.0.0-beta-*",
|
||||
"System.Diagnostics.Debug": "4.0.10-beta-*",
|
||||
"System.Reflection": "4.0.10-beta-*",
|
||||
"System.Resources.ResourceManager": "4.0.0-beta-*",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
{
|
||||
private static readonly Lazy<IDataProtectionProvider> _lazyProtectionProvider = new Lazy<IDataProtectionProvider>(CreateProtectionProvider);
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool _suppressPrimaryPurpose;
|
||||
|
||||
private readonly Lazy<IDataProtector> _lazyProtector;
|
||||
private readonly Lazy<IDataProtector> _lazyProtectorSuppressedPrimaryPurpose;
|
||||
|
||||
public CompatibilityDataProtector(string applicationName, string primaryPurpose, string[] specificPurposes)
|
||||
: base("application-name", "primary-purpose", null) // we feed dummy values to the base ctor
|
||||
|
|
@ -28,11 +32,27 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
// up a good error message to the developer.
|
||||
|
||||
_lazyProtector = new Lazy<IDataProtector>(() => _lazyProtectionProvider.Value.CreateProtector(primaryPurpose, specificPurposes));
|
||||
|
||||
// System.Web always provides "User.MachineKey.Protect" as the primary purpose for calls
|
||||
// to MachineKey.Protect. Only in this case should we allow suppressing the primary
|
||||
// purpose, as then we can easily map calls to MachineKey.Protect(userData, purposes)
|
||||
// into calls to provider.GetProtector(purposes).Protect(userData).
|
||||
if (primaryPurpose == "User.MachineKey.Protect")
|
||||
{
|
||||
_lazyProtectorSuppressedPrimaryPurpose = new Lazy<IDataProtector>(() => _lazyProtectionProvider.Value.CreateProtector(specificPurposes));
|
||||
}
|
||||
else
|
||||
{
|
||||
_lazyProtectorSuppressedPrimaryPurpose = _lazyProtector;
|
||||
}
|
||||
}
|
||||
|
||||
// We take care of flowing purposes ourselves.
|
||||
protected override bool PrependHashedPurposeToPlaintext { get; } = false;
|
||||
|
||||
// Retrieves the appropriate protector (potentially with a suppressed primary purpose) for this operation.
|
||||
private IDataProtector Protector => ((_suppressPrimaryPurpose) ? _lazyProtectorSuppressedPrimaryPurpose : _lazyProtector).Value;
|
||||
|
||||
private static IDataProtectionProvider CreateProtectionProvider()
|
||||
{
|
||||
// Read from <appSettings> the startup type we need to use, then create it
|
||||
|
|
@ -60,7 +80,7 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
{
|
||||
try
|
||||
{
|
||||
return _lazyProtector.Value.Protect(userData);
|
||||
return Protector.Protect(userData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -76,7 +96,38 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
|
||||
protected override byte[] ProviderUnprotect(byte[] encryptedData)
|
||||
{
|
||||
return _lazyProtector.Value.Unprotect(encryptedData);
|
||||
return Protector.Unprotect(encryptedData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a delegate where calls to <see cref="ProviderProtect(byte[])"/>
|
||||
/// and <see cref="ProviderUnprotect(byte[])"/> will ignore the primary
|
||||
/// purpose and instead use only the sub-purposes.
|
||||
/// </summary>
|
||||
public static byte[] RunWithSuppressedPrimaryPurpose(Func<object, byte[], byte[]> callback, object state, byte[] input)
|
||||
{
|
||||
if (_suppressPrimaryPurpose)
|
||||
{
|
||||
return callback(state, input); // already suppressed - just forward call
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_suppressPrimaryPurpose = true;
|
||||
return callback(state, input);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressPrimaryPurpose = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// defeat exception filters
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
/// </remarks>
|
||||
public virtual IDataProtectionProvider CreateDataProtectionProvider(IServiceProvider services)
|
||||
{
|
||||
return services.GetRequiredService<IDataProtectionProvider>();
|
||||
return services.GetDataProtectionProvider();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,30 +56,12 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
/// </summary>
|
||||
internal IDataProtectionProvider InternalConfigureServicesAndCreateProtectionProvider()
|
||||
{
|
||||
// Configure the default implementation, passing in our custom discriminator
|
||||
var services = new ServiceCollection();
|
||||
services.AddDataProtection();
|
||||
services.Configure<DataProtectionOptions>(options =>
|
||||
{
|
||||
// Try reading the discriminator from <machineKey applicationName="..." /> defined
|
||||
// at the web app root. If the value was set explicitly (even if the value is empty),
|
||||
// honor it as the discriminator. Otherwise, fall back to the metabase config path.
|
||||
var machineKeySection = (MachineKeySection)WebConfigurationManager.GetWebApplicationSection("system.web/machineKey");
|
||||
if (machineKeySection.ElementInformation.Properties["applicationName"].ValueOrigin != PropertyValueOrigin.Default)
|
||||
{
|
||||
options.ApplicationDiscriminator = machineKeySection.ApplicationName;
|
||||
}
|
||||
else
|
||||
{
|
||||
options.ApplicationDiscriminator = HttpRuntime.AppDomainAppId;
|
||||
}
|
||||
services.AddInstance<IApplicationDiscriminator>(new SystemWebApplicationDiscriminator());
|
||||
|
||||
if (String.IsNullOrEmpty(options.ApplicationDiscriminator))
|
||||
{
|
||||
options.ApplicationDiscriminator = null; // homogenize to null
|
||||
}
|
||||
});
|
||||
|
||||
// Run configuration and get an instance of the provider.
|
||||
// Run user-specified configuration and get an instance of the provider
|
||||
ConfigureServices(services);
|
||||
var provider = CreateDataProtectionProvider(services.BuildServiceProvider());
|
||||
if (provider == null)
|
||||
|
|
@ -90,5 +72,30 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
|
|||
// And we're done!
|
||||
return provider;
|
||||
}
|
||||
|
||||
private sealed class SystemWebApplicationDiscriminator : IApplicationDiscriminator
|
||||
{
|
||||
private readonly Lazy<string> _lazyDiscriminator = new Lazy<string>(GetAppDiscriminatorCore);
|
||||
|
||||
public string Discriminator => _lazyDiscriminator.Value;
|
||||
|
||||
private static string GetAppDiscriminatorCore()
|
||||
{
|
||||
// Try reading the discriminator from <machineKey applicationName="..." /> defined
|
||||
// at the web app root. If the value was set explicitly (even if the value is empty),
|
||||
// honor it as the discriminator.
|
||||
var machineKeySection = (MachineKeySection)WebConfigurationManager.GetWebApplicationSection("system.web/machineKey");
|
||||
if (machineKeySection.ElementInformation.Properties["applicationName"].ValueOrigin != PropertyValueOrigin.Default)
|
||||
{
|
||||
return machineKeySection.ApplicationName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, fall back to the IIS metabase config path.
|
||||
// This is unique per machine.
|
||||
return HttpRuntime.AppDomainAppId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ namespace Microsoft.AspNet.DataProtection
|
|||
/// <typeparam name="TImplementation">The concrete type of the <see cref="IKeyEscrowSink"/> to register.</typeparam>
|
||||
/// <returns>The 'this' instance.</returns>
|
||||
/// <remarks>
|
||||
/// Registrations are additive.
|
||||
/// Registrations are additive. The factory is registered as <see cref="ServiceLifetime.Singleton"/>.
|
||||
/// </remarks>
|
||||
public DataProtectionConfiguration AddKeyEscrowSink<TImplementation>()
|
||||
where TImplementation : IKeyEscrowSink
|
||||
|
|
@ -94,7 +94,7 @@ namespace Microsoft.AspNet.DataProtection
|
|||
/// <param name="factory">A factory that creates the <see cref="IKeyEscrowSink"/> instance.</param>
|
||||
/// <returns>The 'this' instance.</returns>
|
||||
/// <remarks>
|
||||
/// Registrations are additive.
|
||||
/// Registrations are additive. The factory is registered as <see cref="ServiceLifetime.Singleton"/>.
|
||||
/// </remarks>
|
||||
public DataProtectionConfiguration AddKeyEscrowSink([NotNull] Func<IServiceProvider, IKeyEscrowSink> factory)
|
||||
{
|
||||
|
|
@ -130,19 +130,6 @@ namespace Microsoft.AspNet.DataProtection
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the data protection system to persist keys in storage as plaintext.
|
||||
/// </summary>
|
||||
/// <returns>The 'this' instance.</returns>
|
||||
/// <remarks>
|
||||
/// Caution: cryptographic key material will not be protected at rest.
|
||||
/// </remarks>
|
||||
public DataProtectionConfiguration DisableProtectionOfKeysAtRest()
|
||||
{
|
||||
RemoveAllServicesOfType(typeof(IXmlEncryptor));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the data protection system to persist keys to the specified directory.
|
||||
/// This path may be on the local machine or may point to a UNC share.
|
||||
|
|
@ -267,6 +254,23 @@ namespace Microsoft.AspNet.DataProtection
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the unique name of this application within the data protection system.
|
||||
/// </summary>
|
||||
/// <param name="applicationName">The application name.</param>
|
||||
/// <returns>The 'this' instance.</returns>
|
||||
/// <remarks>
|
||||
/// This API corresponds to setting the <see cref="DataProtectionOptions.ApplicationDiscriminator"/> property
|
||||
/// to the value of <paramref name="applicationName"/>.
|
||||
/// </remarks>
|
||||
public DataProtectionConfiguration SetApplicationName(string applicationName)
|
||||
{
|
||||
return ConfigureGlobalOptions(options =>
|
||||
{
|
||||
options.ApplicationDiscriminator = applicationName;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default lifetime of keys created by the data protection system.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
{
|
||||
return new ConfigureOptions<DataProtectionOptions>(options =>
|
||||
{
|
||||
options.ApplicationDiscriminator = services.GetService<IApplicationDiscriminator>()?.Discriminator;
|
||||
options.ApplicationDiscriminator = services.GetApplicationUniqueIdentifier();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
|
|||
using Microsoft.AspNet.DataProtection.Cng;
|
||||
using Microsoft.AspNet.DataProtection.KeyManagement;
|
||||
using Microsoft.AspNet.DataProtection.Repositories;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
{
|
||||
|
|
@ -31,6 +32,8 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
// we'll not use the fallback at all.
|
||||
yield return ServiceDescriptor.Singleton<IDefaultKeyServices>(services =>
|
||||
{
|
||||
ILogger log = services.GetLogger(typeof(DataProtectionServices));
|
||||
|
||||
ServiceDescriptor keyEncryptorDescriptor = null;
|
||||
ServiceDescriptor keyRepositoryDescriptor = null;
|
||||
|
||||
|
|
@ -38,6 +41,11 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
var azureWebSitesKeysFolder = FileSystemXmlRepository.GetKeyStorageDirectoryForAzureWebSites();
|
||||
if (azureWebSitesKeysFolder != null)
|
||||
{
|
||||
if (log.IsInformationLevelEnabled())
|
||||
{
|
||||
log.LogInformation("Azure Web Sites environment detected. Using '{0}' as key repository; keys will not be encrypted at rest.", azureWebSitesKeysFolder.FullName);
|
||||
}
|
||||
|
||||
// Cloud DPAPI isn't yet available, so we don't encrypt keys at rest.
|
||||
// This isn't all that different than what Azure Web Sites does today, and we can always add this later.
|
||||
keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_FileSystem(azureWebSitesKeysFolder);
|
||||
|
|
@ -55,6 +63,18 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
keyEncryptorDescriptor = DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToMachine: !DpapiSecretSerializerHelper.CanProtectToCurrentUserAccount());
|
||||
}
|
||||
keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_FileSystem(localAppDataKeysFolder);
|
||||
|
||||
if (log.IsInformationLevelEnabled())
|
||||
{
|
||||
if (keyEncryptorDescriptor != null)
|
||||
{
|
||||
log.LogInformation("User profile is available. Using '{0}' as key repository and Windows DPAPI to encrypt keys at rest.", localAppDataKeysFolder.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogInformation("User profile is available. Using '{0}' as key repository; keys will not be encrypted at rest.", localAppDataKeysFolder.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -68,12 +88,29 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
keyEncryptorDescriptor = DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToMachine: true);
|
||||
}
|
||||
keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_Registry(regKeyStorageKey);
|
||||
|
||||
if (log.IsInformationLevelEnabled())
|
||||
{
|
||||
if (keyEncryptorDescriptor != null)
|
||||
{
|
||||
log.LogInformation("User profile not available. Using '{0}' as key repository and Windows DPAPI to encrypt keys at rest.", regKeyStorageKey.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.LogInformation("User profile not available. Using '{0}' as key repository; keys will not be encrypted at rest.", regKeyStorageKey.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final fallback - use an ephemeral repository since we don't know where else to go.
|
||||
// This can only be used for development scenarios.
|
||||
keyRepositoryDescriptor = DataProtectionServiceDescriptors.IXmlRepository_InMemory();
|
||||
|
||||
if (log.IsWarningLevelEnabled())
|
||||
{
|
||||
log.LogWarning("Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,19 +8,36 @@ using Microsoft.Framework.Logging;
|
|||
namespace System
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpful extension methods on IServiceProvider.
|
||||
/// Helpful logging-related extension methods on <see cref="IServiceProvider"/>.
|
||||
/// </summary>
|
||||
internal static class LoggingServiceProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves an instance of ILogger given the type name of the caller.
|
||||
/// The caller's type name is used as the name of the ILogger created.
|
||||
/// This method returns null if the IServiceProvider is null or if it
|
||||
/// does not contain a registered ILoggerFactory.
|
||||
/// Retrieves an instance of <see cref="ILogger"/> given the type name <typeparamref name="T"/>.
|
||||
/// This is equivalent to <see cref="LoggerFactoryExtensions.CreateLogger{T}(ILoggerFactory)"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="ILogger"/> instance, or null if <paramref name="services"/> is null or the
|
||||
/// <see cref="IServiceProvider"/> cannot produce an <see cref="ILoggerFactory"/>.
|
||||
/// </returns>
|
||||
public static ILogger GetLogger<T>(this IServiceProvider services)
|
||||
{
|
||||
return services?.GetService<ILoggerFactory>()?.CreateLogger<T>();
|
||||
return GetLogger(services, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an instance of <see cref="ILogger"/> given the type name <paramref name="type"/>.
|
||||
/// This is equivalent to <see cref="LoggerFactoryExtensions.CreateLogger{T}(ILoggerFactory)"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="ILogger"/> instance, or null if <paramref name="services"/> is null or the
|
||||
/// <see cref="IServiceProvider"/> cannot produce an <see cref="ILoggerFactory"/>.
|
||||
/// </returns>
|
||||
public static ILogger GetLogger(this IServiceProvider services, Type type)
|
||||
{
|
||||
// Compiler won't allow us to use static types as the type parameter
|
||||
// for the call to CreateLogger<T>, so we'll duplicate its logic here.
|
||||
return services?.GetService<ILoggerFactory>()?.CreateLogger(type.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
"frameworks": {
|
||||
"net451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Runtime": { "version": "", "type": "build" },
|
||||
"System.Security": "",
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": ""
|
||||
|
|
@ -21,7 +20,6 @@
|
|||
},
|
||||
"dnx451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Runtime": { "version": "", "type": "build" },
|
||||
"System.Security": "",
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": ""
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Security.Cryptography;
|
|||
using System.Text;
|
||||
using Microsoft.AspNet.DataProtection.Interfaces;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Microsoft.Framework.Runtime;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -106,6 +107,170 @@ namespace Microsoft.AspNet.DataProtection
|
|||
Assert.Same(finalExpectedProtector, retVal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(" discriminator", "app-path ", "discriminator")] // normalized trim
|
||||
[InlineData("", "app-path", null)] // app discriminator not null -> overrides app base path
|
||||
[InlineData(null, "app-path ", "app-path")] // normalized trim
|
||||
[InlineData(null, " ", null)] // normalized whitespace -> null
|
||||
[InlineData(null, null, null)] // nothing provided at all
|
||||
public void GetApplicationUniqueIdentifier(string appDiscriminator, string appBasePath, string expected)
|
||||
{
|
||||
// Arrange
|
||||
var mockAppDiscriminator = new Mock<IApplicationDiscriminator>();
|
||||
mockAppDiscriminator.Setup(o => o.Discriminator).Returns(appDiscriminator);
|
||||
var mockAppEnvironment = new Mock<IApplicationEnvironment>();
|
||||
mockAppEnvironment.Setup(o => o.ApplicationBasePath).Returns(appBasePath);
|
||||
var mockServiceProvider = new Mock<IServiceProvider>();
|
||||
mockServiceProvider.Setup(o => o.GetService(typeof(IApplicationDiscriminator))).Returns(mockAppDiscriminator.Object);
|
||||
mockServiceProvider.Setup(o => o.GetService(typeof(IApplicationEnvironment))).Returns(mockAppEnvironment.Object);
|
||||
|
||||
// Act
|
||||
string actual = mockServiceProvider.Object.GetApplicationUniqueIdentifier();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApplicationUniqueIdentifier_NoServiceProvider_ReturnsNull()
|
||||
{
|
||||
Assert.Null(((IServiceProvider)null).GetApplicationUniqueIdentifier());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDataProtectionProvider_NoServiceFound_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var services = new Mock<IServiceProvider>().Object;
|
||||
|
||||
// Act & assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => services.GetDataProtectionProvider());
|
||||
Assert.Equal(Resources.FormatDataProtectionExtensions_NoService(typeof(IDataProtectionProvider).FullName), ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDataProtectionProvider_ServiceFound_ReturnsService()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new Mock<IDataProtectionProvider>().Object;
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
mockServices.Setup(o => o.GetService(typeof(IDataProtectionProvider))).Returns(expected);
|
||||
var services = mockServices.Object;
|
||||
|
||||
// Act
|
||||
var actual = services.GetDataProtectionProvider();
|
||||
|
||||
// Assert
|
||||
Assert.Same(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new object[] { new string[0] })]
|
||||
[InlineData(new object[] { new string[] { null } })]
|
||||
[InlineData(new object[] { new string[] { "the next value is bad", null } })]
|
||||
public void GetDataProtector_ChainedAsIEnumerable_FailureCases(string[] purposes)
|
||||
{
|
||||
// Arrange
|
||||
var mockProtector = new Mock<IDataProtector>();
|
||||
mockProtector.Setup(o => o.CreateProtector(It.IsAny<string>())).Returns(mockProtector.Object);
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
mockServices.Setup(o => o.GetService(typeof(IDataProtectionProvider))).Returns(mockProtector.Object);
|
||||
var services = mockServices.Object;
|
||||
|
||||
// Act & assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
testCode: () => services.GetDataProtector((IEnumerable<string>)purposes),
|
||||
paramName: "purposes",
|
||||
exceptionMessage: Resources.DataProtectionExtensions_NullPurposesCollection);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new object[] { new string[] { null } })]
|
||||
[InlineData(new object[] { new string[] { "the next value is bad", null } })]
|
||||
public void GetDataProtector_ChainedAsParams_FailureCases(string[] subPurposes)
|
||||
{
|
||||
// Arrange
|
||||
var mockProtector = new Mock<IDataProtector>();
|
||||
mockProtector.Setup(o => o.CreateProtector(It.IsAny<string>())).Returns(mockProtector.Object);
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
mockServices.Setup(o => o.GetService(typeof(IDataProtectionProvider))).Returns(mockProtector.Object);
|
||||
var services = mockServices.Object;
|
||||
|
||||
// Act & assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
testCode: () => services.GetDataProtector("primary-purpose", subPurposes),
|
||||
paramName: "purposes",
|
||||
exceptionMessage: Resources.DataProtectionExtensions_NullPurposesCollection);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDataProtector_ChainedAsIEnumerable_SuccessCase()
|
||||
{
|
||||
// Arrange
|
||||
var finalExpectedProtector = new Mock<IDataProtector>().Object;
|
||||
|
||||
var thirdMock = new Mock<IDataProtector>();
|
||||
thirdMock.Setup(o => o.CreateProtector("third")).Returns(finalExpectedProtector);
|
||||
var secondMock = new Mock<IDataProtector>();
|
||||
secondMock.Setup(o => o.CreateProtector("second")).Returns(thirdMock.Object);
|
||||
var firstMock = new Mock<IDataProtector>();
|
||||
firstMock.Setup(o => o.CreateProtector("first")).Returns(secondMock.Object);
|
||||
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
mockServices.Setup(o => o.GetService(typeof(IDataProtectionProvider))).Returns(firstMock.Object);
|
||||
var services = mockServices.Object;
|
||||
|
||||
// Act
|
||||
var retVal = services.GetDataProtector((IEnumerable<string>)new string[] { "first", "second", "third" });
|
||||
|
||||
// Assert
|
||||
Assert.Same(finalExpectedProtector, retVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDataProtector_ChainedAsParams_NonEmptyParams_SuccessCase()
|
||||
{
|
||||
// Arrange
|
||||
var finalExpectedProtector = new Mock<IDataProtector>().Object;
|
||||
|
||||
var thirdMock = new Mock<IDataProtector>();
|
||||
thirdMock.Setup(o => o.CreateProtector("third")).Returns(finalExpectedProtector);
|
||||
var secondMock = new Mock<IDataProtector>();
|
||||
secondMock.Setup(o => o.CreateProtector("second")).Returns(thirdMock.Object);
|
||||
var firstMock = new Mock<IDataProtector>();
|
||||
firstMock.Setup(o => o.CreateProtector("first")).Returns(secondMock.Object);
|
||||
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
mockServices.Setup(o => o.GetService(typeof(IDataProtectionProvider))).Returns(firstMock.Object);
|
||||
var services = mockServices.Object;
|
||||
|
||||
// Act
|
||||
var retVal = services.GetDataProtector("first", "second", "third");
|
||||
|
||||
// Assert
|
||||
Assert.Same(finalExpectedProtector, retVal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new object[] { null })]
|
||||
[InlineData(new object[] { new string[0] })]
|
||||
public void GetDataProtector_ChainedAsParams_EmptyParams_SuccessCases(string[] subPurposes)
|
||||
{
|
||||
// Arrange
|
||||
var finalExpectedProtector = new Mock<IDataProtector>().Object;
|
||||
var firstMock = new Mock<IDataProtector>();
|
||||
firstMock.Setup(o => o.CreateProtector("first")).Returns(finalExpectedProtector);
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
mockServices.Setup(o => o.GetService(typeof(IDataProtectionProvider))).Returns(firstMock.Object);
|
||||
var services = mockServices.Object;
|
||||
|
||||
// Act
|
||||
var retVal = services.GetDataProtector("first", subPurposes);
|
||||
|
||||
// Assert
|
||||
Assert.Same(finalExpectedProtector, retVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Protect_InvalidUtf8_Failure()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue