diff --git a/src/Microsoft.AspNet.DataProtection.Extensions/DataProtectionProvider.cs b/src/Microsoft.AspNet.DataProtection.Extensions/DataProtectionProvider.cs
new file mode 100644
index 0000000000..badb814072
--- /dev/null
+++ b/src/Microsoft.AspNet.DataProtection.Extensions/DataProtectionProvider.cs
@@ -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
+{
+ ///
+ /// A simple implementation of an where keys are stored
+ /// at a particular location on the file system.
+ ///
+ public sealed class DataProtectionProvider : IDataProtectionProvider
+ {
+ private readonly IDataProtectionProvider _innerProvider;
+
+ ///
+ /// Creates an given a location at which to store keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ public DataProtectionProvider([NotNull] DirectoryInfo keyDirectory)
+ : this(keyDirectory, configure: null)
+ {
+ }
+
+ ///
+ /// Creates an given a location at which to store keys and an
+ /// optional configuration callback.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// An optional callback which provides further configuration of the data protection
+ /// system. See for more information.
+ public DataProtectionProvider([NotNull] DirectoryInfo keyDirectory, Action 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();
+ }
+
+ ///
+ /// Implements .
+ ///
+ public IDataProtector CreateProtector([NotNull] string purpose)
+ {
+ return _innerProvider.CreateProtector(purpose);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs b/src/Microsoft.AspNet.DataProtection/DataProtectionProviderFactory.cs
similarity index 64%
rename from src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs
rename to src/Microsoft.AspNet.DataProtection/DataProtectionProviderFactory.cs
index de61cdd9f7..d08d326539 100644
--- a/src/Microsoft.AspNet.DataProtection/DataProtectionProvider.cs
+++ b/src/Microsoft.AspNet.DataProtection/DataProtectionProviderFactory.cs
@@ -12,37 +12,8 @@ namespace Microsoft.AspNet.DataProtection
///
/// Contains static factory methods for creating instances.
///
- public static class DataProtectionProvider
+ internal static class DataProtectionProviderFactory
{
- ///
- /// Creates an ephemeral .
- ///
- /// An ephemeral .
- ///
- /// Payloads generated by any given instance of an
- /// 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.
- ///
- public static EphemeralDataProtectionProvider CreateNewEphemeralProvider()
- {
- return CreateNewEphemeralProvider(services: null);
- }
-
- ///
- /// Creates an ephemeral .
- ///
- /// Optional services (such as logging) for use by the provider.
- /// An ephemeral .
- ///
- /// Payloads generated by any given instance of an
- /// 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.
- ///
- public static EphemeralDataProtectionProvider CreateNewEphemeralProvider(IServiceProvider services)
- {
- return new EphemeralDataProtectionProvider(services);
- }
-
///
/// Creates an given an .
///
diff --git a/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs b/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs
index f7a8c3da0b..280d0d63d8 100644
--- a/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs
+++ b/src/Microsoft.AspNet.DataProtection/DataProtectionServiceDescriptors.cs
@@ -85,7 +85,7 @@ namespace Microsoft.Framework.DependencyInjection
public static ServiceDescriptor IDataProtectionProvider_Default()
{
return ServiceDescriptor.Singleton(
- services => DataProtectionProvider.GetProviderFromServices(
+ services => DataProtectionProviderFactory.GetProviderFromServices(
options: services.GetRequiredService>().Options,
services: services,
mustCreateImmediately: true /* this is the ultimate fallback */));
diff --git a/test/Microsoft.AspNet.DataProtection.Extensions.Test/DataProtectionProviderTests.cs b/test/Microsoft.AspNet.DataProtection.Extensions.Test/DataProtectionProviderTests.cs
new file mode 100644
index 0000000000..3420dd030a
--- /dev/null
+++ b/test/Microsoft.AspNet.DataProtection.Extensions.Test/DataProtectionProviderTests.cs
@@ -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);
+ });
+ }
+
+ ///
+ /// Runs a test and cleans up the temp directory afterward.
+ ///
+ private static void WithUniqueTempDirectory(Action 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.";
+ }
+ }
+}