diff --git a/src/Microsoft.AspNet.DataProtection.Interfaces/DataProtectionExtensions.cs b/src/Microsoft.AspNet.DataProtection.Interfaces/DataProtectionExtensions.cs
index 291ab59633..393d9b5307 100644
--- a/src/Microsoft.AspNet.DataProtection.Interfaces/DataProtectionExtensions.cs
+++ b/src/Microsoft.AspNet.DataProtection.Interfaces/DataProtectionExtensions.cs
@@ -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
{
///
@@ -53,7 +58,7 @@ namespace Microsoft.AspNet.DataProtection
/// Creates an given a list of purposes.
///
/// The from which to generate the purpose chain.
- /// The primary purpose used to create the .
+ /// The primary purpose used to create the .
/// An optional list of secondary purposes which contribute to the purpose chain.
/// If this list is provided it cannot contain null elements.
/// An tied to the provided purpose chain.
@@ -75,7 +80,93 @@ namespace Microsoft.AspNet.DataProtection
}
return protector ?? CryptoUtil.Fail("CreateProtector returned null.");
}
-
+
+ ///
+ /// Returns a unique identifier for this application.
+ ///
+ /// The application-level .
+ /// A unique application identifier, or null if is null
+ /// or cannot provide a unique application identifier.
+ ///
+ /// 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.
+ ///
+ [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;
+ }
+
+ ///
+ /// Retrieves an from an .
+ ///
+ /// The service provider from which to retrieve the .
+ /// An . This method is guaranteed never to return null.
+ /// If no service exists in .
+ public static IDataProtectionProvider GetDataProtectionProvider([NotNull] this IServiceProvider services)
+ {
+ // We have our own implementation of GetRequiredService 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;
+ }
+
+ ///
+ /// Retrieves an from an given a list of purposes.
+ ///
+ /// An which contains the
+ /// from which to generate the purpose chain.
+ /// 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.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which calls
+ /// then . See those methods'
+ /// documentation for more information.
+ ///
+ public static IDataProtector GetDataProtector([NotNull] this IServiceProvider services, [NotNull] IEnumerable purposes)
+ {
+ return services.GetDataProtectionProvider().CreateProtector(purposes);
+ }
+
+ ///
+ /// Retrieves an from an given a list of purposes.
+ ///
+ /// An which contains the
+ /// from which to generate the purpose chain.
+ /// The primary purpose used to create the .
+ /// An optional list of secondary purposes which contribute to the purpose chain.
+ /// If this list is provided it cannot contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which calls
+ /// then . See those methods'
+ /// documentation for more information.
+ ///
+ public static IDataProtector GetDataProtector([NotNull] this IServiceProvider services, [NotNull] string purpose, params string[] subPurposes)
+ {
+ return services.GetDataProtectionProvider().CreateProtector(purpose, subPurposes);
+ }
+
///
/// Cryptographically protects a piece of plaintext data.
///
diff --git a/src/Microsoft.AspNet.DataProtection.Interfaces/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.DataProtection.Interfaces/Properties/Resources.Designer.cs
index 9c0eed3510..c0b13a79e6 100644
--- a/src/Microsoft.AspNet.DataProtection.Interfaces/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.DataProtection.Interfaces/Properties/Resources.Designer.cs
@@ -58,6 +58,22 @@ namespace Microsoft.AspNet.DataProtection.Interfaces
return GetString("CryptCommon_GenericError");
}
+ ///
+ /// No service for type '{0}' has been registered.
+ ///
+ internal static string DataProtectionExtensions_NoService
+ {
+ get { return GetString("DataProtectionExtensions_NoService"); }
+ }
+
+ ///
+ /// No service for type '{0}' has been registered.
+ ///
+ 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);
diff --git a/src/Microsoft.AspNet.DataProtection.Interfaces/Resources.resx b/src/Microsoft.AspNet.DataProtection.Interfaces/Resources.resx
index 84fa596602..daa9e2cbd9 100644
--- a/src/Microsoft.AspNet.DataProtection.Interfaces/Resources.resx
+++ b/src/Microsoft.AspNet.DataProtection.Interfaces/Resources.resx
@@ -126,4 +126,7 @@
An error occurred during a cryptographic operation.
+
+ No service for type '{0}' has been registered.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.DataProtection.Interfaces/project.json b/src/Microsoft.AspNet.DataProtection.Interfaces/project.json
index f8543204e6..a29ad1792a 100644
--- a/src/Microsoft.AspNet.DataProtection.Interfaces/project.json
+++ b/src/Microsoft.AspNet.DataProtection.Interfaces/project.json
@@ -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-*",
diff --git a/src/Microsoft.AspNet.DataProtection.SystemWeb/CompatibilityDataProtector.cs b/src/Microsoft.AspNet.DataProtection.SystemWeb/CompatibilityDataProtector.cs
index 5bf5b5b6d4..3f67e256ab 100644
--- a/src/Microsoft.AspNet.DataProtection.SystemWeb/CompatibilityDataProtector.cs
+++ b/src/Microsoft.AspNet.DataProtection.SystemWeb/CompatibilityDataProtector.cs
@@ -18,7 +18,11 @@ namespace Microsoft.AspNet.DataProtection.SystemWeb
{
private static readonly Lazy _lazyProtectionProvider = new Lazy(CreateProtectionProvider);
+ [ThreadStatic]
+ private static bool _suppressPrimaryPurpose;
+
private readonly Lazy _lazyProtector;
+ private readonly Lazy _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(() => _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(() => _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 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);
+ }
+
+ ///
+ /// Invokes a delegate where calls to
+ /// and will ignore the primary
+ /// purpose and instead use only the sub-purposes.
+ ///
+ public static byte[] RunWithSuppressedPrimaryPurpose(Func