Add simple file-based provider instantiation APIs
This commit is contained in:
parent
84490846b6
commit
22927ec289
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.DataProtection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A simple implementation of an <see cref="IDataProtectionProvider"/> where keys are stored
|
||||||
|
/// at a particular location on the file system.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DataProtectionProvider : IDataProtectionProvider
|
||||||
|
{
|
||||||
|
private readonly IDataProtectionProvider _innerProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="DataProtectionProvider"/> given a location at which to store keys.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyDirectory">The <see cref="DirectoryInfo"/> in which keys should be stored. This may
|
||||||
|
/// represent a directory on a local disk or a UNC share.</param>
|
||||||
|
public DataProtectionProvider([NotNull] DirectoryInfo keyDirectory)
|
||||||
|
: this(keyDirectory, configure: null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="DataProtectionProvider"/> given a location at which to store keys and an
|
||||||
|
/// optional configuration callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyDirectory">The <see cref="DirectoryInfo"/> in which keys should be stored. This may
|
||||||
|
/// represent a directory on a local disk or a UNC share.</param>
|
||||||
|
/// <param name="configure">An optional callback which provides further configuration of the data protection
|
||||||
|
/// system. See <see cref="DataProtectionConfiguration"/> for more information.</param>
|
||||||
|
public DataProtectionProvider([NotNull] DirectoryInfo keyDirectory, Action<DataProtectionConfiguration> configure)
|
||||||
|
{
|
||||||
|
// build the service collection
|
||||||
|
ServiceCollection serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddDataProtection();
|
||||||
|
serviceCollection.ConfigureDataProtection(configurationObject =>
|
||||||
|
{
|
||||||
|
configurationObject.PersistKeysToFileSystem(keyDirectory);
|
||||||
|
configure?.Invoke(configurationObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
// extract the provider instance from the service collection
|
||||||
|
_innerProvider = serviceCollection.BuildServiceProvider().GetRequiredService<IDataProtectionProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements <see cref="IDataProtectionProvider.CreateProtector(string)"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IDataProtector CreateProtector([NotNull] string purpose)
|
||||||
|
{
|
||||||
|
return _innerProvider.CreateProtector(purpose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,37 +12,8 @@ namespace Microsoft.AspNet.DataProtection
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains static factory methods for creating <see cref="IDataProtectionProvider"/> instances.
|
/// Contains static factory methods for creating <see cref="IDataProtectionProvider"/> instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DataProtectionProvider
|
internal static class DataProtectionProviderFactory
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Creates an ephemeral <see cref="IDataProtectionProvider"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An ephemeral <see cref="IDataProtectionProvider"/>.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Payloads generated by any given instance of an <see cref="EphemeralDataProtectionProvider"/>
|
|
||||||
/// can only be unprotected by that same provider instance. Once an instance of an ephemeral
|
|
||||||
/// provider is lost, all payloads generated by that provider are permanently undecipherable.
|
|
||||||
/// </remarks>
|
|
||||||
public static EphemeralDataProtectionProvider CreateNewEphemeralProvider()
|
|
||||||
{
|
|
||||||
return CreateNewEphemeralProvider(services: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an ephemeral <see cref="IDataProtectionProvider"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="services">Optional services (such as logging) for use by the provider.</param>
|
|
||||||
/// <returns>An ephemeral <see cref="IDataProtectionProvider"/>.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Payloads generated by any given instance of an <see cref="EphemeralDataProtectionProvider"/>
|
|
||||||
/// can only be unprotected by that same provider instance. Once an instance of an ephemeral
|
|
||||||
/// provider is lost, all payloads generated by that provider are permanently undecipherable.
|
|
||||||
/// </remarks>
|
|
||||||
public static EphemeralDataProtectionProvider CreateNewEphemeralProvider(IServiceProvider services)
|
|
||||||
{
|
|
||||||
return new EphemeralDataProtectionProvider(services);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an <see cref="IDataProtectionProvider"/> given an <see cref="IServiceProvider"/>.
|
/// Creates an <see cref="IDataProtectionProvider"/> given an <see cref="IServiceProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.Framework.DependencyInjection
|
||||||
public static ServiceDescriptor IDataProtectionProvider_Default()
|
public static ServiceDescriptor IDataProtectionProvider_Default()
|
||||||
{
|
{
|
||||||
return ServiceDescriptor.Singleton<IDataProtectionProvider>(
|
return ServiceDescriptor.Singleton<IDataProtectionProvider>(
|
||||||
services => DataProtectionProvider.GetProviderFromServices(
|
services => DataProtectionProviderFactory.GetProviderFromServices(
|
||||||
options: services.GetRequiredService<IOptions<DataProtectionOptions>>().Options,
|
options: services.GetRequiredService<IOptions<DataProtectionOptions>>().Options,
|
||||||
services: services,
|
services: services,
|
||||||
mustCreateImmediately: true /* this is the ultimate fallback */));
|
mustCreateImmediately: true /* this is the ultimate fallback */));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.Testing.xunit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.DataProtection
|
||||||
|
{
|
||||||
|
public class DataProtectionProviderTests
|
||||||
|
{
|
||||||
|
[ConditionalFact]
|
||||||
|
[ConditionalRunTestOnlyIfLocalAppDataAvailable]
|
||||||
|
public void System_UsesProvidedDirectory()
|
||||||
|
{
|
||||||
|
WithUniqueTempDirectory(directory =>
|
||||||
|
{
|
||||||
|
// Step 1: directory should be completely empty
|
||||||
|
directory.Create();
|
||||||
|
Assert.Empty(directory.GetFiles());
|
||||||
|
|
||||||
|
// Step 2: instantiate the system and round-trip a payload
|
||||||
|
var protector = new DataProtectionProvider(directory).CreateProtector("purpose");
|
||||||
|
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
|
||||||
|
|
||||||
|
// Step 3: validate that there's now a single key in the directory and that it's not protected
|
||||||
|
var allFiles = directory.GetFiles();
|
||||||
|
Assert.Equal(1, allFiles.Length);
|
||||||
|
Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
string fileText = File.ReadAllText(allFiles[0].FullName);
|
||||||
|
Assert.Contains("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain("Windows DPAPI", fileText, StringComparison.Ordinal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[ConditionalRunTestOnlyIfLocalAppDataAvailable]
|
||||||
|
public void System_UsesProvidedDirectory_WithConfigurationCallback()
|
||||||
|
{
|
||||||
|
WithUniqueTempDirectory(directory =>
|
||||||
|
{
|
||||||
|
// Step 1: directory should be completely empty
|
||||||
|
directory.Create();
|
||||||
|
Assert.Empty(directory.GetFiles());
|
||||||
|
|
||||||
|
// Step 2: instantiate the system and round-trip a payload
|
||||||
|
var protector = new DataProtectionProvider(directory, configure =>
|
||||||
|
{
|
||||||
|
configure.ProtectKeysWithDpapi();
|
||||||
|
}).CreateProtector("purpose");
|
||||||
|
Assert.Equal("payload", protector.Unprotect(protector.Protect("payload")));
|
||||||
|
|
||||||
|
// Step 3: validate that there's now a single key in the directory and that it's protected with DPAPI
|
||||||
|
var allFiles = directory.GetFiles();
|
||||||
|
Assert.Equal(1, allFiles.Length);
|
||||||
|
Assert.StartsWith("key-", allFiles[0].Name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
string fileText = File.ReadAllText(allFiles[0].FullName);
|
||||||
|
Assert.DoesNotContain("Warning: the key below is in an unencrypted form.", fileText, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("Windows DPAPI", fileText, StringComparison.Ordinal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a test and cleans up the temp directory afterward.
|
||||||
|
/// </summary>
|
||||||
|
private static void WithUniqueTempDirectory(Action<DirectoryInfo> testCode)
|
||||||
|
{
|
||||||
|
string uniqueTempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
var dirInfo = new DirectoryInfo(uniqueTempPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
testCode(dirInfo);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// clean up when test is done
|
||||||
|
if (dirInfo.Exists)
|
||||||
|
{
|
||||||
|
dirInfo.Delete(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConditionalRunTestOnlyIfLocalAppDataAvailable : Attribute, ITestCondition
|
||||||
|
{
|
||||||
|
public bool IsMet => (Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) != null);
|
||||||
|
|
||||||
|
public string SkipReason { get; } = "%LOCALAPPDATA% couldn't be located.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue