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>
|
||||
/// Contains static factory methods for creating <see cref="IDataProtectionProvider"/> instances.
|
||||
/// </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>
|
||||
/// Creates an <see cref="IDataProtectionProvider"/> given an <see cref="IServiceProvider"/>.
|
||||
/// </summary>
|
||||
|
|
@ -85,7 +85,7 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
public static ServiceDescriptor IDataProtectionProvider_Default()
|
||||
{
|
||||
return ServiceDescriptor.Singleton<IDataProtectionProvider>(
|
||||
services => DataProtectionProvider.GetProviderFromServices(
|
||||
services => DataProtectionProviderFactory.GetProviderFromServices(
|
||||
options: services.GetRequiredService<IOptions<DataProtectionOptions>>().Options,
|
||||
services: services,
|
||||
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