Port more security tests from Katana.
This commit is contained in:
parent
d5ce9fe736
commit
05804c78db
10
Security.sln
10
Security.sln
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.21813.0
|
||||
VisualStudioVersion = 14.0.21916.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||
EndProject
|
||||
|
|
@ -15,7 +15,13 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSample", "samples\Coo
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7BF11F3A-60B6-4796-B504-579C67FFBA34}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security", "test\Microsoft.AspNet.Security.Test\Microsoft.AspNet.Security.kproj", "{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}"
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.Tests", "test\Microsoft.AspNet.Security.Test\Microsoft.AspNet.Security.Tests.kproj", "{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C40A5A3B-ABA3-4819-9C44-D821E6DA1BA1}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
global.json = global.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
|
|
@ -120,17 +121,28 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
signin.Properties,
|
||||
cookieOptions);
|
||||
|
||||
DateTimeOffset issuedUtc = Options.SystemClock.UtcNow;
|
||||
DateTimeOffset expiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
DateTimeOffset issuedUtc;
|
||||
if (signin.Properties.IssuedUtc.HasValue)
|
||||
{
|
||||
issuedUtc = signin.Properties.IssuedUtc.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
issuedUtc = Options.SystemClock.UtcNow;
|
||||
signin.Properties.IssuedUtc = issuedUtc;
|
||||
}
|
||||
|
||||
context.Properties.IssuedUtc = issuedUtc;
|
||||
context.Properties.ExpiresUtc = expiresUtc;
|
||||
if (!signin.Properties.ExpiresUtc.HasValue)
|
||||
{
|
||||
signin.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
}
|
||||
|
||||
Options.Notifications.ResponseSignIn(context);
|
||||
|
||||
if (context.Properties.IsPersistent)
|
||||
{
|
||||
cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||
DateTimeOffset expiresUtc = context.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
|
||||
context.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||
}
|
||||
|
||||
var model = new AuthenticationTicket(context.Identity, context.Properties);
|
||||
|
|
@ -229,18 +241,27 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
return;
|
||||
}
|
||||
|
||||
string currentUri =
|
||||
Request.PathBase +
|
||||
Request.Path +
|
||||
Request.QueryString;
|
||||
|
||||
string loginUri =
|
||||
Request.Scheme +
|
||||
"://" +
|
||||
Request.Host +
|
||||
Request.PathBase +
|
||||
Options.LoginPath +
|
||||
new QueryString(Options.ReturnUrlParameter, currentUri);
|
||||
string loginUri = string.Empty;
|
||||
if (ChallengeContext != null)
|
||||
{
|
||||
loginUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(loginUri))
|
||||
{
|
||||
string currentUri =
|
||||
Request.PathBase +
|
||||
Request.Path +
|
||||
Request.QueryString;
|
||||
|
||||
loginUri =
|
||||
Request.Scheme +
|
||||
"://" +
|
||||
Request.Host +
|
||||
Request.PathBase +
|
||||
Options.LoginPath +
|
||||
new QueryString(Options.ReturnUrlParameter, currentUri);
|
||||
}
|
||||
|
||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
|
||||
Options.Notifications.ApplyRedirect(redirectContext);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
// 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.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class CertificateSubjectKeyIdentifierValidatorTests
|
||||
{
|
||||
private static readonly X509Certificate2 SelfSigned = new X509Certificate2("selfSigned.cer");
|
||||
private static readonly X509Certificate2 Chained = new X509Certificate2("katanatest.redmond.corp.microsoft.com.cer");
|
||||
|
||||
// The Katana test cert has a valid full chain
|
||||
// katanatest.redmond.corp.microsoft.com -> MSIT Machine Auth CA2 -> Microsoft Internet Authority -> Baltimore CyberTrustRoot
|
||||
|
||||
private const string KatanaTestKeyIdentifier = "d964b2941aaf3e62761041b1f3db098edfa3270a";
|
||||
private const string MicrosoftInternetAuthorityKeyIdentifier = "2a4d97955d347e9db6e633be9c27c1707e67dbc1";
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldNotThrowWithValidValues()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty });
|
||||
|
||||
instance.ShouldNotBe(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrownWhenTheValidHashEnumerableIsNull()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new CertificateSubjectKeyIdentifierValidator(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateChainErrors()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty });
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNameMismatch()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty });
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNameMismatch);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNotAvailable()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty });
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNotAvailable);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedASelfSignedCertificate()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty });
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(SelfSigned);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, SelfSigned, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedSubjectKeyIdentifier()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(new[] { string.Empty });
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsSubjectKeyIdentifierWhiteListed()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(
|
||||
new[]
|
||||
{
|
||||
KatanaTestKeyIdentifier
|
||||
});
|
||||
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementSubjectKeyIdentifierWhiteListed()
|
||||
{
|
||||
var instance = new CertificateSubjectKeyIdentifierValidator(
|
||||
new[]
|
||||
{
|
||||
MicrosoftInternetAuthorityKeyIdentifier
|
||||
});
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
// 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.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class CertificateSubjectPublicKeyInfoValidatorTests
|
||||
{
|
||||
private static readonly X509Certificate2 SelfSigned = new X509Certificate2("selfSigned.cer");
|
||||
private static readonly X509Certificate2 Chained = new X509Certificate2("katanatest.redmond.corp.microsoft.com.cer");
|
||||
|
||||
// The Katana test cert has a valid full chain
|
||||
// katanatest.redmond.corp.microsoft.com -> MSIT Machine Auth CA2 -> Microsoft Internet Authority -> Baltimore CyberTrustRoot
|
||||
|
||||
// The following fingerprints were generated using the go program in appendix A of the Public Key Pinning Extension for HTTP
|
||||
// draft-ietf-websec-key-pinning-05
|
||||
|
||||
private const string KatanaTestSha1Hash = "xvNsCWwxvL3qsCYChZLiwNm1D6o=";
|
||||
private const string KatanaTestSha256Hash = "AhR1Y/xhxK2uD7YJ0xKUPq8tYrWm4+F7DgO2wUOqB+4=";
|
||||
|
||||
private const string MicrosoftInternetAuthoritySha1Hash = "Z3HnseSVDEPu5hZoj05/bBSnT/s=";
|
||||
private const string MicrosoftInternetAuthoritySha256Hash = "UQTPeq/Tlg/vLt2ijtl7qlMFBFkbGG9aAWJbQMOMWFg=";
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldNotThrowWithValidValues()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
|
||||
instance.ShouldNotBe(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrownWhenTheValidHashEnumerableIsNull()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new CertificateSubjectPublicKeyInfoValidator(null, SubjectPublicKeyInfoAlgorithm.Sha1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrowWhenTheHashEnumerableContainsNoHashes()
|
||||
{
|
||||
Should.Throw<ArgumentOutOfRangeException>(() =>
|
||||
new CertificateSubjectPublicKeyInfoValidator(new string[0], SubjectPublicKeyInfoAlgorithm.Sha1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrowIfAnInvalidAlgorithmIsPassed()
|
||||
{
|
||||
Should.Throw<ArgumentOutOfRangeException>(() =>
|
||||
new CertificateSubjectPublicKeyInfoValidator(new string[0], (SubjectPublicKeyInfoAlgorithm)2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateChainErrors()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNameMismatch()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNameMismatch);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNotAvailable()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNotAvailable);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedASelfSignedCertificate()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(SelfSigned);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, SelfSigned, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedSha1Spki()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsSha1SpkiWhiteListed()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { KatanaTestSha1Hash }, SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementSha1SpkiWhiteListed()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { MicrosoftInternetAuthoritySha1Hash }, SubjectPublicKeyInfoAlgorithm.Sha1);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedSha256Spki()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new string[1], SubjectPublicKeyInfoAlgorithm.Sha256);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsSha256SpkiWhiteListed()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { KatanaTestSha256Hash }, SubjectPublicKeyInfoAlgorithm.Sha256);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementSha256SpkiWhiteListed()
|
||||
{
|
||||
var instance = new CertificateSubjectPublicKeyInfoValidator(new[] { MicrosoftInternetAuthoritySha256Hash }, SubjectPublicKeyInfoAlgorithm.Sha256);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
// 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.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class CertificateThumbprintValidatorTests
|
||||
{
|
||||
private static readonly X509Certificate2 SelfSigned = new X509Certificate2("selfSigned.cer");
|
||||
private static readonly X509Certificate2 Chained = new X509Certificate2("katanatest.redmond.corp.microsoft.com.cer");
|
||||
|
||||
// The Katana test cert has a valid full chain
|
||||
// katanatest.redmond.corp.microsoft.com -> MSIT Machine Auth CA2 -> Microsoft Internet Authority -> Baltimore CyberTrustRoot
|
||||
|
||||
private const string KatanaTestThumbprint = "a9894c464b260cac3f5b91cece33b3c55e82e61c";
|
||||
private const string MicrosoftInternetAuthorityThumbprint = "992ad44d7dce298de17e6f2f56a7b9caa41db93f";
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldNotThrowWithValidValues()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new string[1]);
|
||||
|
||||
instance.ShouldNotBe(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrownWhenTheValidHashEnumerableIsNull()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new CertificateThumbprintValidator(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrowWhenTheHashEnumerableContainsNoHashes()
|
||||
{
|
||||
Should.Throw<ArgumentOutOfRangeException>(() =>
|
||||
new CertificateThumbprintValidator(new string[0]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateChainErrors()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new string[1]);
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateChainErrors);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNameMismatch()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new string[1]);
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNameMismatch);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenSslPolicyErrorsIsRemoteCertificateNotAvailable()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new string[1]);
|
||||
bool result = instance.Validate(null, null, null, SslPolicyErrors.RemoteCertificateNotAvailable);
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedASelfSignedCertificate()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new string[1]);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(SelfSigned);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, SelfSigned, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnFalseWhenPassedATrustedCertificateWhichDoesNotHaveAWhitelistedThumbprint()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new string[1]);
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasItsThumbprintWhiteListed()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new[] { KatanaTestThumbprint });
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidatorShouldReturnTrueWhenPassedATrustedCertificateWhichHasAChainElementThumbprintWhiteListed()
|
||||
{
|
||||
var instance = new CertificateThumbprintValidator(new[] { MicrosoftInternetAuthorityThumbprint });
|
||||
var certificateChain = new X509Chain();
|
||||
certificateChain.Build(Chained);
|
||||
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
|
||||
bool result = instance.Validate(null, Chained, certificateChain, SslPolicyErrors.None);
|
||||
|
||||
result.ShouldBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,479 @@
|
|||
// 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 System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Cookies
|
||||
{
|
||||
public class CookieMiddlewareTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task NormalRequestPassesThrough()
|
||||
{
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
});
|
||||
HttpResponseMessage response = await server.CreateClient().GetAsync("http://example.com/normal");
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProtectedRequestShouldRedirectToLogin()
|
||||
{
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
LoginPath = new PathString("/login")
|
||||
});
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/protected");
|
||||
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
|
||||
Uri location = transaction.Response.Headers.Location;
|
||||
location.LocalPath.ShouldBe("/login");
|
||||
location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProtectedCustomRequestShouldRedirectToCustomLogin()
|
||||
{
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
LoginPath = new PathString("/login")
|
||||
});
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
|
||||
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
|
||||
Uri location = transaction.Response.Headers.Location;
|
||||
location.ToString().ShouldBe("/CustomRedirect");
|
||||
}
|
||||
|
||||
private Task SignInAsAlice(HttpContext context)
|
||||
{
|
||||
context.Response.SignIn(
|
||||
new AuthenticationProperties(),
|
||||
new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")));
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SignInCausesDefaultCookieToBeCreated()
|
||||
{
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
LoginPath = new PathString("/login"),
|
||||
CookieName = "TestCookie",
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
string setCookie = transaction.SetCookie;
|
||||
setCookie.ShouldStartWith("TestCookie=");
|
||||
setCookie.ShouldContain("; path=/");
|
||||
setCookie.ShouldContain("; HttpOnly");
|
||||
setCookie.ShouldNotContain("; expires=");
|
||||
setCookie.ShouldNotContain("; domain=");
|
||||
setCookie.ShouldNotContain("; secure");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CookieSecureOption.Always, "http://example.com/testpath", true)]
|
||||
[InlineData(CookieSecureOption.Always, "https://example.com/testpath", true)]
|
||||
[InlineData(CookieSecureOption.Never, "http://example.com/testpath", false)]
|
||||
[InlineData(CookieSecureOption.Never, "https://example.com/testpath", false)]
|
||||
[InlineData(CookieSecureOption.SameAsRequest, "http://example.com/testpath", false)]
|
||||
[InlineData(CookieSecureOption.SameAsRequest, "https://example.com/testpath", true)]
|
||||
public async Task SecureSignInCausesSecureOnlyCookieByDefault(
|
||||
CookieSecureOption cookieSecureOption,
|
||||
string requestUri,
|
||||
bool shouldBeSecureOnly)
|
||||
{
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
LoginPath = new PathString("/login"),
|
||||
CookieName = "TestCookie",
|
||||
CookieSecure = cookieSecureOption
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction = await SendAsync(server, requestUri);
|
||||
string setCookie = transaction.SetCookie;
|
||||
|
||||
if (shouldBeSecureOnly)
|
||||
{
|
||||
setCookie.ShouldContain("; secure");
|
||||
}
|
||||
else
|
||||
{
|
||||
setCookie.ShouldNotContain("; secure");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieOptionsAlterSetCookieHeader()
|
||||
{
|
||||
TestServer server1 = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
CookieName = "TestCookie",
|
||||
CookiePath = "/foo",
|
||||
CookieDomain = "another.com",
|
||||
CookieSecure = CookieSecureOption.Always,
|
||||
CookieHttpOnly = true,
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction1 = await SendAsync(server1, "http://example.com/testpath");
|
||||
|
||||
TestServer server2 = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
CookieName = "SecondCookie",
|
||||
CookieSecure = CookieSecureOption.Never,
|
||||
CookieHttpOnly = false,
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction2 = await SendAsync(server2, "http://example.com/testpath");
|
||||
|
||||
string setCookie1 = transaction1.SetCookie;
|
||||
string setCookie2 = transaction2.SetCookie;
|
||||
|
||||
setCookie1.ShouldContain("TestCookie=");
|
||||
setCookie1.ShouldContain(" path=/foo");
|
||||
setCookie1.ShouldContain(" domain=another.com");
|
||||
setCookie1.ShouldContain(" secure");
|
||||
setCookie1.ShouldContain(" HttpOnly");
|
||||
|
||||
setCookie2.ShouldContain("SecondCookie=");
|
||||
setCookie2.ShouldNotContain(" domain=");
|
||||
setCookie2.ShouldNotContain(" secure");
|
||||
setCookie2.ShouldNotContain(" HttpOnly");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieContainsIdentity()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
SystemClock = clock
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieStopsWorkingAfterExpiration()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
SystemClock = clock,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(10),
|
||||
SlidingExpiration = false,
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(7));
|
||||
|
||||
Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(7));
|
||||
|
||||
Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
transaction2.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction3.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction4.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieExpirationCanBeOverridenInSignin()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
SystemClock = clock,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(10),
|
||||
SlidingExpiration = false,
|
||||
},
|
||||
context =>
|
||||
{
|
||||
context.Response.SignIn(
|
||||
new AuthenticationProperties() { ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)) },
|
||||
new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")));
|
||||
return Task.FromResult<object>(null);
|
||||
});
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(3));
|
||||
|
||||
Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(3));
|
||||
|
||||
Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
transaction2.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction3.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction4.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieExpirationCanBeOverridenInEvent()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
SystemClock = clock,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(10),
|
||||
SlidingExpiration = false,
|
||||
Notifications = new CookieAuthenticationNotifications()
|
||||
{
|
||||
OnResponseSignIn = context =>
|
||||
{
|
||||
context.Properties.ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5));
|
||||
}
|
||||
}
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(3));
|
||||
|
||||
Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(3));
|
||||
|
||||
Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
transaction2.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction3.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction4.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieIsRenewedWithSlidingExpiration()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
SystemClock = clock,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(10),
|
||||
SlidingExpiration = true,
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||
|
||||
Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(4));
|
||||
|
||||
Transaction transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(4));
|
||||
|
||||
// transaction4 should arrive with a new SetCookie value
|
||||
Transaction transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||
|
||||
clock.Add(TimeSpan.FromMinutes(4));
|
||||
|
||||
Transaction transaction5 = await SendAsync(server, "http://example.com/me/Cookies", transaction4.CookieNameValue);
|
||||
|
||||
transaction2.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction3.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction3, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction4.SetCookie.ShouldNotBe(null);
|
||||
FindClaimValue(transaction4, ClaimTypes.Name).ShouldBe("Alice");
|
||||
transaction5.SetCookie.ShouldBe(null);
|
||||
FindClaimValue(transaction5, ClaimTypes.Name).ShouldBe("Alice");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AjaxRedirectsAsExtraHeaderOnTwoHundred()
|
||||
{
|
||||
TestServer server = CreateServer(new CookieAuthenticationOptions
|
||||
{
|
||||
LoginPath = new PathString("/login")
|
||||
});
|
||||
|
||||
Transaction transaction = await SendAsync(server, "http://example.com/protected", ajaxRequest: true);
|
||||
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
var responded = transaction.Response.Headers.GetValues("X-Responded-JSON");
|
||||
|
||||
responded.Count().ShouldBe(1);
|
||||
responded.Single().ShouldContain("\"location\"");
|
||||
}
|
||||
|
||||
private static string FindClaimValue(Transaction transaction, string claimType)
|
||||
{
|
||||
XElement claim = transaction.ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
|
||||
if (claim == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return claim.Attribute("value").Value;
|
||||
}
|
||||
|
||||
private static async Task<XElement> GetAuthData(TestServer server, string url, string cookie)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Add("Cookie", cookie);
|
||||
|
||||
HttpResponseMessage response2 = await server.CreateClient().SendAsync(request);
|
||||
string text = await response2.Content.ReadAsStringAsync();
|
||||
XElement me = XElement.Parse(text);
|
||||
return me;
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(CookieAuthenticationOptions options, Func<HttpContext, Task> testpath = null)
|
||||
{
|
||||
return TestServer.Create(app =>
|
||||
{
|
||||
app.UseCookieAuthentication(options);
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
PathString remainder;
|
||||
if (req.Path == new PathString("/normal"))
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
}
|
||||
else if (req.Path == new PathString("/protected"))
|
||||
{
|
||||
res.StatusCode = 401;
|
||||
}
|
||||
else if (req.Path == new PathString("/protected/CustomRedirect"))
|
||||
{
|
||||
context.Response.Challenge(new AuthenticationProperties() { RedirectUri = "/CustomRedirect" });
|
||||
}
|
||||
else if (req.Path == new PathString("/me"))
|
||||
{
|
||||
Describe(res, new AuthenticationResult(context.User.Identity, new AuthenticationProperties(), new AuthenticationDescription()));
|
||||
}
|
||||
else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder))
|
||||
{
|
||||
var result = await context.AuthenticateAsync(remainder.Value.Substring(1));
|
||||
Describe(res, result);
|
||||
}
|
||||
else if (req.Path == new PathString("/testpath") && testpath != null)
|
||||
{
|
||||
await testpath(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static void Describe(HttpResponse res, AuthenticationResult result)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
var xml = new XElement("xml");
|
||||
if (result != null && result.Identity != null)
|
||||
{
|
||||
xml.Add(result.Identity.Claims.Select(claim => new XElement("claim", new XAttribute("type", claim.Type), new XAttribute("value", claim.Value))));
|
||||
}
|
||||
if (result != null && result.Properties != null)
|
||||
{
|
||||
xml.Add(result.Properties.Dictionary.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value))));
|
||||
}
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var writer = new XmlTextWriter(memory, Encoding.UTF8))
|
||||
{
|
||||
xml.WriteTo(writer);
|
||||
}
|
||||
res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null, bool ajaxRequest = false)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
if (!string.IsNullOrEmpty(cookieHeader))
|
||||
{
|
||||
request.Headers.Add("Cookie", cookieHeader);
|
||||
}
|
||||
if (ajaxRequest)
|
||||
{
|
||||
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
|
||||
}
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Request = request,
|
||||
Response = await server.CreateClient().SendAsync(request),
|
||||
};
|
||||
if (transaction.Response.Headers.Contains("Set-Cookie"))
|
||||
{
|
||||
transaction.SetCookie = transaction.Response.Headers.GetValues("Set-Cookie").SingleOrDefault();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(transaction.SetCookie))
|
||||
{
|
||||
transaction.CookieNameValue = transaction.SetCookie.Split(new[] { ';' }, 2).First();
|
||||
}
|
||||
transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
|
||||
|
||||
if (transaction.Response.Content != null &&
|
||||
transaction.Response.Content.Headers.ContentType != null &&
|
||||
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
|
||||
{
|
||||
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private class Transaction
|
||||
{
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
|
||||
public string SetCookie { get; set; }
|
||||
public string CookieNameValue { get; set; }
|
||||
|
||||
public string ResponseText { get; set; }
|
||||
public XElement ResponseElement { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// 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 Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.DataHandler.Encoder
|
||||
{
|
||||
public class Base64UrlTextEncoderTests
|
||||
{
|
||||
[Fact]
|
||||
public void DataOfVariousLengthRoundTripCorrectly()
|
||||
{
|
||||
var encoder = new Base64UrlTextEncoder();
|
||||
for (int length = 0; length != 256; ++length)
|
||||
{
|
||||
var data = new byte[length];
|
||||
for (int index = 0; index != length; ++index)
|
||||
{
|
||||
data[index] = (byte)(5 + length + (index * 23));
|
||||
}
|
||||
string text = encoder.Encode(data);
|
||||
byte[] result = encoder.Decode(text);
|
||||
|
||||
for (int index = 0; index != length; ++index)
|
||||
{
|
||||
result[index].ShouldBe(data[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,20 @@
|
|||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="katanatest.redmond.corp.microsoft.com.cer" />
|
||||
<Content Include="project.json" />
|
||||
<Content Include="selfSigned.cer" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CertificateSubjectKeyIdentifierValidatorTests.cs" />
|
||||
<Compile Include="CertificateSubjectPublicKeyInfoValidatorTests.cs" />
|
||||
<Compile Include="CertificateThumbprintValidatorTests.cs" />
|
||||
<Compile Include="Cookies\CookieMiddlewareTests.cs" />
|
||||
<Compile Include="DataHandler\Encoder\Base64UrlTextEncoderTests.cs" />
|
||||
<Compile Include="DefaultAuthorizationServiceTests.cs" />
|
||||
<Compile Include="FakePolicy.cs" />
|
||||
<Compile Include="SecurityHelperTests.cs" />
|
||||
<Compile Include="TestClock.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class SecurityHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddingToAnonymousIdentityDoesNotKeepAnonymousIdentity()
|
||||
{
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
context.User.ShouldNotBe(null);
|
||||
context.User.Identity.IsAuthenticated.ShouldBe(false);
|
||||
|
||||
SecurityHelper.AddUserIdentity(context, new GenericIdentity("Test1", "Alpha"));
|
||||
|
||||
context.User.ShouldNotBe(null);
|
||||
context.User.Identity.AuthenticationType.ShouldBe("Alpha");
|
||||
context.User.Identity.Name.ShouldBe("Test1");
|
||||
|
||||
context.User.ShouldBeTypeOf<ClaimsPrincipal>();
|
||||
context.User.Identity.ShouldBeTypeOf<ClaimsIdentity>();
|
||||
|
||||
((ClaimsPrincipal)context.User).Identities.Count().ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddingExistingIdentityChangesDefaultButPreservesPrior()
|
||||
{
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
context.User = new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), null);
|
||||
|
||||
context.User.Identity.AuthenticationType.ShouldBe("Alpha");
|
||||
context.User.Identity.Name.ShouldBe("Test1");
|
||||
|
||||
SecurityHelper.AddUserIdentity(context, new GenericIdentity("Test2", "Beta"));
|
||||
|
||||
context.User.Identity.AuthenticationType.ShouldBe("Beta");
|
||||
context.User.Identity.Name.ShouldBe("Test2");
|
||||
|
||||
SecurityHelper.AddUserIdentity(context, new GenericIdentity("Test3", "Gamma"));
|
||||
|
||||
context.User.Identity.AuthenticationType.ShouldBe("Gamma");
|
||||
context.User.Identity.Name.ShouldBe("Test3");
|
||||
|
||||
var principal = context.User;
|
||||
principal.Identities.Count().ShouldBe(3);
|
||||
principal.Identities.Skip(0).First().Name.ShouldBe("Test3");
|
||||
principal.Identities.Skip(1).First().Name.ShouldBe("Test2");
|
||||
principal.Identities.Skip(2).First().Name.ShouldBe("Test1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoChallengesMeansLookupsAreDeterminedOnlyByActiveOrPassiveMode()
|
||||
{
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
|
||||
bool activeNoChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Active);
|
||||
bool passiveNoChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Passive);
|
||||
|
||||
context.Response.StatusCode = 401;
|
||||
|
||||
bool activeEmptyChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Active);
|
||||
bool passiveEmptyChallenge = SecurityHelper.LookupChallenge(new string[0], "Alpha", AuthenticationMode.Passive);
|
||||
|
||||
Assert.True(activeNoChallenge);
|
||||
Assert.False(passiveNoChallenge);
|
||||
Assert.True(activeEmptyChallenge);
|
||||
Assert.False(passiveEmptyChallenge);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithChallengesMeansLookupsAreDeterminedOnlyByMatchingAuthenticationType()
|
||||
{
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
|
||||
IEnumerable<string> challengeTypes = new[] { "Beta", "Gamma" };
|
||||
|
||||
bool activeNoMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Active);
|
||||
bool passiveNoMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Passive);
|
||||
|
||||
challengeTypes = new[] { "Beta", "Alpha" };
|
||||
|
||||
bool activeWithMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Active);
|
||||
bool passiveWithMatch = SecurityHelper.LookupChallenge(challengeTypes, "Alpha", AuthenticationMode.Passive);
|
||||
|
||||
Assert.False(activeNoMatch);
|
||||
Assert.False(passiveNoMatch);
|
||||
Assert.True(activeWithMatch);
|
||||
Assert.True(passiveWithMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// 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 Microsoft.AspNet.Security.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class TestClock : ISystemClock
|
||||
{
|
||||
public TestClock()
|
||||
{
|
||||
UtcNow = new DateTimeOffset(2013, 6, 11, 12, 34, 56, 789, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
public DateTimeOffset UtcNow { get; set; }
|
||||
|
||||
public void Add(TimeSpan timeSpan)
|
||||
{
|
||||
UtcNow = UtcNow + timeSpan;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -4,6 +4,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Security" : "",
|
||||
"Microsoft.AspNet.Security.Cookies" : "",
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"System.Net.Http": "4.0.0.0",
|
||||
"Moq": "4.2.1312.1622",
|
||||
"Xunit.KRunner": "1.0.0-*"
|
||||
},
|
||||
|
|
@ -13,7 +16,10 @@
|
|||
"frameworks": {
|
||||
"net45": {
|
||||
"dependencies": {
|
||||
"System.Runtime": ""
|
||||
"Shouldly": "1.1.1.1",
|
||||
"System.Runtime": "",
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue