summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs')
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs365
1 files changed, 220 insertions, 145 deletions
diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs
index 6129ee7..871eb78 100644
--- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs
@@ -6,7 +6,11 @@
namespace DotNetOpenAuth.Test.OpenId {
using System;
- using System.Diagnostics.Contracts;
+ using System.Net;
+ using System.Net.Http;
+ using System.Threading;
+ using System.Threading.Tasks;
+
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OpenId;
@@ -15,6 +19,7 @@ namespace DotNetOpenAuth.Test.OpenId {
using DotNetOpenAuth.OpenId.RelyingParty;
using DotNetOpenAuth.Test.Mocks;
using NUnit.Framework;
+ using Validation;
[TestFixture]
public class AuthenticationTests : OpenIdTestBase {
@@ -24,76 +29,101 @@ namespace DotNetOpenAuth.Test.OpenId {
}
[Test]
- public void SharedAssociationPositive() {
- this.ParameterizedAuthenticationTest(true, true, false);
+ public async Task SharedAssociationPositive() {
+ await this.ParameterizedAuthenticationTestAsync(true, true, false);
}
/// <summary>
/// Verifies that a shared association protects against tampering.
/// </summary>
[Test]
- public void SharedAssociationTampered() {
- this.ParameterizedAuthenticationTest(true, true, true);
+ public async Task SharedAssociationTampered() {
+ await this.ParameterizedAuthenticationTestAsync(true, true, true);
}
[Test]
- public void SharedAssociationNegative() {
- this.ParameterizedAuthenticationTest(true, false, false);
+ public async Task SharedAssociationNegative() {
+ await this.ParameterizedAuthenticationTestAsync(true, false, false);
}
[Test]
- public void PrivateAssociationPositive() {
- this.ParameterizedAuthenticationTest(false, true, false);
+ public async Task PrivateAssociationPositive() {
+ await this.ParameterizedAuthenticationTestAsync(false, true, false);
}
/// <summary>
/// Verifies that a private association protects against tampering.
/// </summary>
[Test]
- public void PrivateAssociationTampered() {
- this.ParameterizedAuthenticationTest(false, true, true);
+ public async Task PrivateAssociationTampered() {
+ await this.ParameterizedAuthenticationTestAsync(false, true, true);
}
[Test]
- public void NoAssociationNegative() {
- this.ParameterizedAuthenticationTest(false, false, false);
+ public async Task NoAssociationNegative() {
+ await this.ParameterizedAuthenticationTestAsync(false, false, false);
}
[Test]
- public void UnsolicitedAssertion() {
- this.MockResponder.RegisterMockRPDiscovery();
- OpenIdCoordinator coordinator = new OpenIdCoordinator(
- rp => {
- rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler;
- IAuthenticationResponse response = rp.GetResponse();
+ public async Task UnsolicitedAssertion() {
+ var opStore = new MemoryCryptoKeyAndNonceStore();
+ Handle(RPUri).By(
+ async req => {
+ var rp = new OpenIdRelyingParty(new MemoryCryptoKeyAndNonceStore(), this.HostFactories);
+ IAuthenticationResponse response = await rp.GetResponseAsync(req);
+ Assert.That(response, Is.Not.Null);
Assert.AreEqual(AuthenticationStatus.Authenticated, response.Status);
- },
- op => {
- op.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler;
- Identifier id = GetMockIdentifier(ProtocolVersion.V20);
- op.SendUnsolicitedAssertion(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]);
- AutoProvider(op); // handle check_auth
+ return new HttpResponseMessage();
+ });
+ Handle(OPUri).By(
+ async (req, ct) => {
+ var op = new OpenIdProvider(opStore, this.HostFactories);
+ return await this.AutoProviderActionAsync(op, req, ct);
});
- coordinator.Run();
+ this.RegisterMockRPDiscovery(ssl: false);
+
+ {
+ var op = new OpenIdProvider(opStore, this.HostFactories);
+ Identifier id = GetMockIdentifier(ProtocolVersion.V20);
+ var assertion = await op.PrepareUnsolicitedAssertionAsync(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]);
+
+ using (var httpClient = this.HostFactories.CreateHttpClient()) {
+ using (var response = await httpClient.GetAsync(assertion.Headers.Location)) {
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
}
[Test]
- public void UnsolicitedAssertionRejected() {
- this.MockResponder.RegisterMockRPDiscovery();
- OpenIdCoordinator coordinator = new OpenIdCoordinator(
- rp => {
- rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler;
+ public async Task UnsolicitedAssertionRejected() {
+ var opStore = new MemoryCryptoKeyAndNonceStore();
+ Handle(RPUri).By(
+ async req => {
+ var rp = new OpenIdRelyingParty(new MemoryCryptoKeyAndNonceStore(), this.HostFactories);
rp.SecuritySettings.RejectUnsolicitedAssertions = true;
- IAuthenticationResponse response = rp.GetResponse();
+ IAuthenticationResponse response = await rp.GetResponseAsync(req);
+ Assert.That(response, Is.Not.Null);
Assert.AreEqual(AuthenticationStatus.Failed, response.Status);
- },
- op => {
- op.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler;
- Identifier id = GetMockIdentifier(ProtocolVersion.V20);
- op.SendUnsolicitedAssertion(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]);
- AutoProvider(op); // handle check_auth
+ return new HttpResponseMessage();
+ });
+ Handle(OPUri).By(
+ async req => {
+ var op = new OpenIdProvider(opStore, this.HostFactories);
+ return await this.AutoProviderActionAsync(op, req, CancellationToken.None);
});
- coordinator.Run();
+ this.RegisterMockRPDiscovery(ssl: false);
+
+ {
+ var op = new OpenIdProvider(opStore, this.HostFactories);
+ Identifier id = GetMockIdentifier(ProtocolVersion.V20);
+ var assertion = await op.PrepareUnsolicitedAssertionAsync(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]);
+ using (var httpClient = this.HostFactories.CreateHttpClient()) {
+ using (var response = await httpClient.GetAsync(assertion.Headers.Location)) {
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
}
/// <summary>
@@ -101,25 +131,37 @@ namespace DotNetOpenAuth.Test.OpenId {
/// when the appropriate security setting is set.
/// </summary>
[Test]
- public void UnsolicitedDelegatingIdentifierRejection() {
- this.MockResponder.RegisterMockRPDiscovery();
- OpenIdCoordinator coordinator = new OpenIdCoordinator(
- rp => {
- rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler;
+ public async Task UnsolicitedDelegatingIdentifierRejection() {
+ var opStore = new MemoryCryptoKeyAndNonceStore();
+ Handle(RPUri).By(
+ async req => {
+ var rp = this.CreateRelyingParty();
rp.SecuritySettings.RejectDelegatingIdentifiers = true;
- IAuthenticationResponse response = rp.GetResponse();
+ IAuthenticationResponse response = await rp.GetResponseAsync(req);
+ Assert.That(response, Is.Not.Null);
Assert.AreEqual(AuthenticationStatus.Failed, response.Status);
- },
- op => {
- op.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler;
- Identifier id = GetMockIdentifier(ProtocolVersion.V20, false, true);
- op.SendUnsolicitedAssertion(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]);
- AutoProvider(op); // handle check_auth
+ return new HttpResponseMessage();
});
- coordinator.Run();
+ Handle(OPUri).By(
+ async req => {
+ var op = new OpenIdProvider(opStore, this.HostFactories);
+ return await this.AutoProviderActionAsync(op, req, CancellationToken.None);
+ });
+ this.RegisterMockRPDiscovery(ssl: false);
+
+ {
+ var op = new OpenIdProvider(opStore, this.HostFactories);
+ Identifier id = GetMockIdentifier(ProtocolVersion.V20, false, true);
+ var assertion = await op.PrepareUnsolicitedAssertionAsync(OPUri, RPRealmUri, id, OPLocalIdentifiers[0]);
+ using (var httpClient = this.HostFactories.CreateHttpClient()) {
+ using (var response = await httpClient.GetAsync(assertion.Headers.Location)) {
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
}
- private void ParameterizedAuthenticationTest(bool sharedAssociation, bool positive, bool tamper) {
+ private async Task ParameterizedAuthenticationTestAsync(bool sharedAssociation, bool positive, bool tamper) {
foreach (Protocol protocol in Protocol.AllPracticalVersions) {
foreach (bool statelessRP in new[] { false, true }) {
if (sharedAssociation && statelessRP) {
@@ -129,121 +171,154 @@ namespace DotNetOpenAuth.Test.OpenId {
foreach (bool immediate in new[] { false, true }) {
TestLogger.InfoFormat("Beginning authentication test scenario. OpenID: {0}, Shared: {1}, positive: {2}, tamper: {3}, stateless: {4}, immediate: {5}", protocol.Version, sharedAssociation, positive, tamper, statelessRP, immediate);
- this.ParameterizedAuthenticationTest(protocol, statelessRP, sharedAssociation, positive, immediate, tamper);
+ await this.ParameterizedAuthenticationTestAsync(protocol, statelessRP, sharedAssociation, positive, immediate, tamper);
}
}
}
}
- private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) {
- Requires.True(!statelessRP || !sharedAssociation, null, "The RP cannot be stateless while sharing an association with the OP.");
- Requires.True(positive || !tamper, null, "Cannot tamper with a negative response.");
+ private async Task ParameterizedAuthenticationTestAsync(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) {
+ Requires.That(!statelessRP || !sharedAssociation, null, "The RP cannot be stateless while sharing an association with the OP.");
+ Requires.That(positive || !tamper, null, "Cannot tamper with a negative response.");
var securitySettings = new ProviderSecuritySettings();
var cryptoKeyStore = new MemoryCryptoKeyStore();
var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore);
Association association = sharedAssociation ? HmacShaAssociationProvider.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings) : null;
- var coordinator = new OpenIdCoordinator(
- rp => {
- var request = new CheckIdRequest(protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup);
-
+ int opStep = 0;
+ HandleProvider(
+ async (op, req) => {
if (association != null) {
- StoreAssociation(rp, OPUri, association);
- request.AssociationHandle = association.Handle;
+ var key = cryptoKeyStore.GetCurrentKey(
+ ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1));
+ op.CryptoKeyStore.StoreKey(
+ ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value);
}
- request.ClaimedIdentifier = "http://claimedid";
- request.LocalIdentifier = "http://localid";
- request.ReturnTo = RPUri;
- request.Realm = RPUri;
- rp.Channel.Respond(request);
- if (positive) {
- if (tamper) {
- try {
- rp.Channel.ReadFromRequest<PositiveAssertionResponse>();
- Assert.Fail("Expected exception {0} not thrown.", typeof(InvalidSignatureException).Name);
- } catch (InvalidSignatureException) {
- TestLogger.InfoFormat("Caught expected {0} exception after tampering with signed data.", typeof(InvalidSignatureException).Name);
+ switch (++opStep) {
+ case 1:
+ var request = await op.Channel.ReadFromRequestAsync<CheckIdRequest>(req, CancellationToken.None);
+ Assert.IsNotNull(request);
+ IProtocolMessage response;
+ if (positive) {
+ response = new PositiveAssertionResponse(request);
+ } else {
+ response = await NegativeAssertionResponse.CreateAsync(request, CancellationToken.None, op.Channel);
}
- } else {
- var response = rp.Channel.ReadFromRequest<PositiveAssertionResponse>();
- Assert.IsNotNull(response);
- Assert.AreEqual(request.ClaimedIdentifier, response.ClaimedIdentifier);
- Assert.AreEqual(request.LocalIdentifier, response.LocalIdentifier);
- Assert.AreEqual(request.ReturnTo, response.ReturnTo);
-
- // Attempt to replay the message and verify that it fails.
- // Because in various scenarios and protocol versions different components
- // notice the replay, we can get one of two exceptions thrown.
- // When the OP notices the replay we get a generic InvalidSignatureException.
- // When the RP notices the replay we get a specific ReplayMessageException.
- try {
- CoordinatingChannel channel = (CoordinatingChannel)rp.Channel;
- channel.Replay(response);
- Assert.Fail("Expected ProtocolException was not thrown.");
- } catch (ProtocolException ex) {
- Assert.IsTrue(ex is ReplayedMessageException || ex is InvalidSignatureException, "A {0} exception was thrown instead of the expected {1} or {2}.", ex.GetType(), typeof(ReplayedMessageException).Name, typeof(InvalidSignatureException).Name);
+
+ return await op.Channel.PrepareResponseAsync(response);
+ case 2:
+ if (positive && (statelessRP || !sharedAssociation)) {
+ var checkauthRequest =
+ await op.Channel.ReadFromRequestAsync<CheckAuthenticationRequest>(req, CancellationToken.None);
+ var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest);
+ checkauthResponse.IsValid = checkauthRequest.IsValid;
+ return await op.Channel.PrepareResponseAsync(checkauthResponse);
}
- }
- } else {
- var response = rp.Channel.ReadFromRequest<NegativeAssertionResponse>();
- Assert.IsNotNull(response);
- if (immediate) {
- // Only 1.1 was required to include user_setup_url
- if (protocol.Version.Major < 2) {
- Assert.IsNotNull(response.UserSetupUrl);
+
+ throw Assumes.NotReachable();
+ case 3:
+ if (positive && (statelessRP || !sharedAssociation)) {
+ if (!tamper) {
+ // Respond to the replay attack.
+ var checkauthRequest =
+ await op.Channel.ReadFromRequestAsync<CheckAuthenticationRequest>(req, CancellationToken.None);
+ var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest);
+ checkauthResponse.IsValid = checkauthRequest.IsValid;
+ return await op.Channel.PrepareResponseAsync(checkauthResponse);
+ }
}
- } else {
- Assert.IsNull(response.UserSetupUrl);
- }
+
+ throw Assumes.NotReachable();
+ default:
+ throw Assumes.NotReachable();
}
- },
- op => {
- if (association != null) {
- var key = cryptoKeyStore.GetCurrentKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1));
- op.CryptoKeyStore.StoreKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value);
+ });
+
+ {
+ var rp = this.CreateRelyingParty(statelessRP);
+ if (tamper) {
+ rp.Channel.IncomingMessageFilter = message => {
+ var assertion = message as PositiveAssertionResponse;
+ if (assertion != null) {
+ // Alter the Local Identifier between the Provider and the Relying Party.
+ // If the signature binding element does its job, this should cause the RP
+ // to throw.
+ assertion.LocalIdentifier = "http://victim";
+ }
+ };
+ }
+
+ var request = new CheckIdRequest(
+ protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup);
+
+ if (association != null) {
+ StoreAssociation(rp, OPUri, association);
+ request.AssociationHandle = association.Handle;
+ }
+
+ request.ClaimedIdentifier = "http://claimedid";
+ request.LocalIdentifier = "http://localid";
+ request.ReturnTo = RPUri;
+ request.Realm = RPUri;
+ var redirectRequest = await rp.Channel.PrepareResponseAsync(request);
+ Uri redirectResponse;
+ this.HostFactories.AllowAutoRedirects = false;
+ using (var httpClient = rp.Channel.HostFactories.CreateHttpClient()) {
+ using (var response = await httpClient.GetAsync(redirectRequest.Headers.Location)) {
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
+ redirectResponse = response.Headers.Location;
}
+ }
- var request = op.Channel.ReadFromRequest<CheckIdRequest>();
- Assert.IsNotNull(request);
- IProtocolMessage response;
- if (positive) {
- response = new PositiveAssertionResponse(request);
+ var assertionMessage = new HttpRequestMessage(HttpMethod.Get, redirectResponse);
+ if (positive) {
+ if (tamper) {
+ try {
+ await rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(assertionMessage, CancellationToken.None);
+ Assert.Fail("Expected exception {0} not thrown.", typeof(InvalidSignatureException).Name);
+ } catch (InvalidSignatureException) {
+ TestLogger.InfoFormat(
+ "Caught expected {0} exception after tampering with signed data.", typeof(InvalidSignatureException).Name);
+ }
} else {
- response = new NegativeAssertionResponse(request, op.Channel);
- }
- op.Channel.Respond(response);
-
- if (positive && (statelessRP || !sharedAssociation)) {
- var checkauthRequest = op.Channel.ReadFromRequest<CheckAuthenticationRequest>();
- var checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest);
- checkauthResponse.IsValid = checkauthRequest.IsValid;
- op.Channel.Respond(checkauthResponse);
-
- if (!tamper) {
- // Respond to the replay attack.
- checkauthRequest = op.Channel.ReadFromRequest<CheckAuthenticationRequest>();
- checkauthResponse = new CheckAuthenticationResponse(checkauthRequest.Version, checkauthRequest);
- checkauthResponse.IsValid = checkauthRequest.IsValid;
- op.Channel.Respond(checkauthResponse);
+ var response =
+ await rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(assertionMessage, CancellationToken.None);
+ Assert.IsNotNull(response);
+ Assert.AreEqual(request.ClaimedIdentifier, response.ClaimedIdentifier);
+ Assert.AreEqual(request.LocalIdentifier, response.LocalIdentifier);
+ Assert.AreEqual(request.ReturnTo, response.ReturnTo);
+
+ // Attempt to replay the message and verify that it fails.
+ // Because in various scenarios and protocol versions different components
+ // notice the replay, we can get one of two exceptions thrown.
+ // When the OP notices the replay we get a generic InvalidSignatureException.
+ // When the RP notices the replay we get a specific ReplayMessageException.
+ try {
+ await rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>(assertionMessage, CancellationToken.None);
+ Assert.Fail("Expected ProtocolException was not thrown.");
+ } catch (ProtocolException ex) {
+ Assert.IsTrue(
+ ex is ReplayedMessageException || ex is InvalidSignatureException,
+ "A {0} exception was thrown instead of the expected {1} or {2}.",
+ ex.GetType(),
+ typeof(ReplayedMessageException).Name,
+ typeof(InvalidSignatureException).Name);
}
}
- });
- if (tamper) {
- coordinator.IncomingMessageFilter = message => {
- var assertion = message as PositiveAssertionResponse;
- if (assertion != null) {
- // Alter the Local Identifier between the Provider and the Relying Party.
- // If the signature binding element does its job, this should cause the RP
- // to throw.
- assertion.LocalIdentifier = "http://victim";
+ } else {
+ var response =
+ await rp.Channel.ReadFromRequestAsync<NegativeAssertionResponse>(assertionMessage, CancellationToken.None);
+ Assert.IsNotNull(response);
+ if (immediate) {
+ // Only 1.1 was required to include user_setup_url
+ if (protocol.Version.Major < 2) {
+ Assert.IsNotNull(response.UserSetupUrl);
+ }
+ } else {
+ Assert.IsNull(response.UserSetupUrl);
}
- };
- }
- if (statelessRP) {
- coordinator.RelyingParty = new OpenIdRelyingParty(null);
+ }
}
-
- coordinator.Run();
}
}
}