Add CreateProtector convenience extension method

This commit is contained in:
Levi B 2015-02-24 17:48:29 -08:00
parent 6637cb264f
commit ab18f52e98
5 changed files with 109 additions and 0 deletions

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNet.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
@ -21,6 +23,37 @@ namespace Microsoft.AspNet.Security.DataProtection
?? new TimeLimitedDataProtector(protector.CreateProtector(TimeLimitedDataProtector.PurposeString));
}
/// <summary>
/// Creates an IDataProtector given an array of purposes.
/// </summary>
/// <param name="provider">The provider from which to generate the purpose chain.</param>
/// <param name="purposes">
/// This is a convenience method used for chaining several purposes together
/// in a single call to CreateProtector. See the documentation of
/// IDataProtectionProvider.CreateProtector for more information.
/// </param>
/// <returns>An IDataProtector tied to the provided purpose chain.</returns>
public static IDataProtector CreateProtector([NotNull] this IDataProtectionProvider provider, params string[] purposes)
{
if (purposes == null || purposes.Length == 0)
{
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesArray, nameof(purposes));
}
IDataProtectionProvider retVal = provider;
foreach (string purpose in purposes)
{
if (String.IsNullOrEmpty(purpose))
{
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesArray, nameof(purposes));
}
retVal = retVal.CreateProtector(purpose) ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
}
Debug.Assert(retVal is IDataProtector); // CreateProtector is supposed to return an instance of this interface
return (IDataProtector)retVal;
}
/// <summary>
/// Cryptographically protects a piece of plaintext data.
/// </summary>

View File

@ -186,6 +186,22 @@ namespace Microsoft.AspNet.Security.DataProtection
return string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
}
/// <summary>
/// The purposes array cannot be null or empty and cannot contain null or empty elements.
/// </summary>
internal static string DataProtectionExtensions_NullPurposesArray
{
get { return GetString("DataProtectionExtensions_NullPurposesArray"); }
}
/// <summary>
/// The purposes array cannot be null or empty and cannot contain null or empty elements.
/// </summary>
internal static string FormatDataProtectionExtensions_NullPurposesArray()
{
return GetString("DataProtectionExtensions_NullPurposesArray");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -150,4 +150,7 @@
<data name="TimeLimitedDataProtector_PayloadExpired" xml:space="preserve">
<value>The payload expired at {0}.</value>
</data>
<data name="DataProtectionExtensions_NullPurposesArray" xml:space="preserve">
<value>The purposes array cannot be null or empty and cannot contain null or empty elements.</value>
</data>
</root>

View File

@ -39,6 +39,43 @@ namespace Microsoft.AspNet.Security.DataProtection.Test
Assert.Same(innerProtector, timeLimitedProtector.InnerProtector);
}
[Theory]
[InlineData(new object[] { null })]
[InlineData(new object[] { new string[0] })]
[InlineData(new object[] { new string[] { null } })]
[InlineData(new object[] { new string[] { "the next value is bad", "" } })]
public void CreateProtector_Chained_FailureCases(string[] purposes)
{
// Arrange
var mockProtector = new Mock<IDataProtector>();
mockProtector.Setup(o => o.CreateProtector(It.IsAny<string>())).Returns(mockProtector.Object);
var provider = mockProtector.Object;
// Act & assert
var ex = Assert.Throws<ArgumentException>(() => provider.CreateProtector(purposes));
ex.AssertMessage("purposes", Resources.DataProtectionExtensions_NullPurposesArray);
}
[Fact]
public void CreateProtector_Chained_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);
// Act
var retVal = firstMock.Object.CreateProtector("first", "second", "third");
// Assert
Assert.Same(finalExpectedProtector, retVal);
}
[Fact]
public void Protect_InvalidUtf_Failure()
{

View File

@ -0,0 +1,20 @@
// 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 Xunit;
namespace Microsoft.AspNet.Security.DataProtection.Test
{
internal static class ExceptionHelpers
{
public static void AssertMessage(this ArgumentException exception, string parameterName, string message)
{
Assert.Equal(parameterName, exception.ParamName);
// We'll let ArgumentException handle the message formatting for us and treat it as our control value
var controlException = new ArgumentException(message, parameterName);
Assert.Equal(controlException.Message, exception.Message);
}
}
}