using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using DotNetOpenId.RelyingParty;
using DotNetOpenId.Test.Mocks;
using NUnit.Framework;
using OpenIdProvider = DotNetOpenId.Provider.OpenIdProvider;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
public class OpenIdRelyingPartyTest {
UriIdentifier simpleOpenId = new UriIdentifier("http://nonexistant.openid.com");
readonly Realm realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
readonly Uri returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
Uri simpleNonOpenIdRequest = new Uri("http://localhost/hi");
[SetUp]
public void Setup() {
if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost"))
UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
[TearDown]
public void TearDown() {
MockHttpRequest.Reset();
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void DefaultCtorWithoutContext() {
new OpenIdRelyingParty();
}
[Test]
public void CtorWithNullRequestUri() {
new OpenIdRelyingParty(new ApplicationMemoryStore(), null, null);
}
[Test]
public void CtorWithNullStore() {
var consumer = new OpenIdRelyingParty(null, simpleNonOpenIdRequest, new NameValueCollection());
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void CreateRequestWithoutContext1() {
var consumer = new OpenIdRelyingParty(new ApplicationMemoryStore(), simpleNonOpenIdRequest, new NameValueCollection());
consumer.CreateRequest(simpleOpenId);
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void CreateRequestWithoutContext2() {
var consumer = new OpenIdRelyingParty(new ApplicationMemoryStore(), simpleNonOpenIdRequest, new NameValueCollection());
consumer.CreateRequest(simpleOpenId, realm);
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CreateRequestNullIdentifier() {
var consumer = TestSupport.CreateRelyingParty(null);
consumer.CreateRequest(null, realm, returnTo);
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CreateRequestNullRealm() {
var consumer = TestSupport.CreateRelyingParty(null);
consumer.CreateRequest("=someEndpoint", null, returnTo);
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void CreateRequestNullReturnTo() {
var consumer = TestSupport.CreateRelyingParty(null);
consumer.CreateRequest("=someEndpoint", realm, null);
}
[Test]
public void CreateRequestStripsFragment() {
var consumer = TestSupport.CreateRelyingParty(null);
UriBuilder userSuppliedIdentifier = new UriBuilder((Uri)TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20));
userSuppliedIdentifier.Fragment = "c";
Identifier mockIdentifer = new MockIdentifier(userSuppliedIdentifier.Uri,
TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20).Discover());
Assert.IsTrue(mockIdentifer.ToString().EndsWith("#c"), "Test broken");
IAuthenticationRequest request = consumer.CreateRequest(mockIdentifer, TestSupport.Realm, TestSupport.ReturnTo);
Assert.AreEqual(0, new Uri(request.ClaimedIdentifier).Fragment.Length);
}
[Test]
public void AssociationCreationWithStore() {
TestSupport.ResetStores(); // get rid of existing associations so a new one is created
OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
var directMessageSniffer = new DirectMessageSniffWrapper(rp.DirectMessageChannel);
rp.DirectMessageChannel = directMessageSniffer;
var idUrl = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
DotNetOpenId.RelyingParty.IAuthenticationRequest req;
bool associationMade = false;
directMessageSniffer.Receiving += (provider, fields) => {
if (fields.ContainsKey("assoc_handle") && fields.ContainsKey("session_type"))
associationMade = true;
};
req = rp.CreateRequest(idUrl, realm, returnTo);
Assert.IsTrue(associationMade);
}
[Test]
public void NoAssociationRequestWithoutStore() {
TestSupport.ResetStores(); // get rid of existing associations so a new one is created
OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null, null);
var directMessageSniffer = new DirectMessageSniffWrapper(rp.DirectMessageChannel);
rp.DirectMessageChannel = directMessageSniffer;
var idUrl = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
DotNetOpenId.RelyingParty.IAuthenticationRequest req;
bool associationMade = false;
directMessageSniffer.Receiving += (provider, fields) => {
if (fields.ContainsKey("assoc_handle") && fields.ContainsKey("session_type"))
associationMade = true;
};
req = rp.CreateRequest(idUrl, realm, returnTo);
Assert.IsFalse(associationMade);
}
///
/// Verifies that both the return_to and realm arguments either
/// both explicitly specify the port number when it can be implied
/// or both leave the port number out.
///
///
/// Implying or explicitly specifying the port should not make any difference
/// as long as the port is not different, but some other implementations that
/// we want to interop with have poor comparison functions and a port on one
/// and missing on the other can cause unwanted failures. So we just want to
/// make sure that we do our best to interop with them.
///
[Test]
public void RealmAndReturnToPortImplicitnessAgreement() {
UriBuilder builder = new UriBuilder(TestSupport.GetFullUrl(TestSupport.ConsumerPage));
// set the scheme and port such that the port MAY be implied.
builder.Port = 80;
builder.Scheme = "http";
Uri returnTo = builder.Uri;
testExplicitPortOnRealmAndReturnTo(returnTo, new Realm(builder));
// Add wildcard and test again.
builder.Host = "*." + builder.Host;
testExplicitPortOnRealmAndReturnTo(returnTo, new Realm(builder));
}
private static void testExplicitPortOnRealmAndReturnTo(Uri returnTo, Realm realm) {
var request = TestSupport.CreateRelyingPartyRequest(true, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false);
Protocol protocol = Protocol.Lookup(request.Provider.Version);
var nvc = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
string realmString = nvc[protocol.openid.Realm];
string returnToString = nvc[protocol.openid.return_to];
bool realmPortExplicitlyGiven = realmString.Contains(":80");
bool returnToPortExplicitlyGiven = returnToString.Contains(":80");
if (realmPortExplicitlyGiven ^ returnToPortExplicitlyGiven) {
if (realmPortExplicitlyGiven)
Assert.Fail("Realm port is explicitly specified although it may be implied, and return_to only implies it.");
else
Assert.Fail("Return_to port is explicitly specified although it may be implied, and realm only implies it.");
}
}
[Test]
public void ReturnToUrlEncodingTest() {
var request = TestSupport.CreateRelyingPartyRequest(true, TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false);
Protocol protocol = Protocol.Lookup(request.Provider.Version);
request.AddCallbackArguments("a+b", "c+d");
var requestArgs = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query);
var returnToArgs = HttpUtility.ParseQueryString(requestArgs[protocol.openid.return_to]);
Assert.AreEqual("c+d", returnToArgs["a+b"]);
}
static ServiceEndpoint getServiceEndpoint(int? servicePriority, int? uriPriority) {
Protocol protocol = Protocol.v20;
ServiceEndpoint ep = ServiceEndpoint.CreateForClaimedIdentifier(
TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20),
TestSupport.GetDelegateUrl(TestSupport.Scenarios.AutoApproval),
TestSupport.GetFullUrl(TestSupport.ProviderPage),
new[] { protocol.ClaimedIdentifierServiceTypeURI },
servicePriority,
uriPriority
);
return ep;
}
[Test]
public void DefaultEndpointOrder() {
var consumer = new OpenIdRelyingParty(null, null, null);
Assert.AreSame(OpenIdRelyingParty.DefaultEndpointOrder, consumer.EndpointOrder);
var defaultEndpointOrder = OpenIdRelyingParty.DefaultEndpointOrder;
// Test service priority ordering
Assert.AreEqual(-1, defaultEndpointOrder(getServiceEndpoint(10, null), getServiceEndpoint(20, null)));
Assert.AreEqual(1, defaultEndpointOrder(getServiceEndpoint(20, null), getServiceEndpoint(10, null)));
Assert.AreEqual(0, defaultEndpointOrder(getServiceEndpoint(10, null), getServiceEndpoint(10, null)));
Assert.AreEqual(-1, defaultEndpointOrder(getServiceEndpoint(20, null), getServiceEndpoint(null, null)));
Assert.AreEqual(1, defaultEndpointOrder(getServiceEndpoint(null, null), getServiceEndpoint(10, null)));
Assert.AreEqual(0, defaultEndpointOrder(getServiceEndpoint(null, null), getServiceEndpoint(null, null)));
// Test secondary type uri ordering
Assert.AreEqual(-1, defaultEndpointOrder(getServiceEndpoint(10, 10), getServiceEndpoint(10, 20)));
Assert.AreEqual(1, defaultEndpointOrder(getServiceEndpoint(10, 20), getServiceEndpoint(10, 10)));
Assert.AreEqual(0, defaultEndpointOrder(getServiceEndpoint(10, 5), getServiceEndpoint(10, 5)));
// test that it is secondary...
Assert.AreEqual(1, defaultEndpointOrder(getServiceEndpoint(20, 10), getServiceEndpoint(10, 20)));
Assert.AreEqual(-1, defaultEndpointOrder(getServiceEndpoint(null, 10), getServiceEndpoint(null, 20)));
Assert.AreEqual(1, defaultEndpointOrder(getServiceEndpoint(null, 20), getServiceEndpoint(null, 10)));
Assert.AreEqual(0, defaultEndpointOrder(getServiceEndpoint(null, 10), getServiceEndpoint(null, 10)));
}
[Test]
public void DefaultFilter() {
var consumer = new OpenIdRelyingParty(null, null, null);
Assert.IsNull(consumer.EndpointFilter);
}
[Test]
public void MultipleServiceEndpoints() {
string xrds = @"
=MultipleEndpoint
=!91F2.8153.F600.AE24
=!91F2.8153.F600.AE24
@!7F6F.F50.A4E4.1133
xri://+i-service*(+contact)*($v*1.0)
(+contact)
http://contact.freexri.com/contact/
@!7F6F.F50.A4E4.1133
http://openid.net/signon/1.0
(+login)
http://authn.freexri.com/auth10/
https://authn.freexri.com/auth10/
@!7F6F.F50.A4E4.1133
http://specs.openid.net/auth/2.0/signon
(+login)
http://authn.freexri.com/auth20/
https://authn.freexri.com/auth20/
OpenXRI
";
MockHttpRequest.RegisterMockXrdsResponses(new Dictionary {
{"https://xri.net/=MultipleEndpoint?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
});
OpenIdRelyingParty rp = new OpenIdRelyingParty(null, null, null);
Realm realm = new Realm("http://somerealm");
Uri return_to = new Uri("http://somerealm/return_to");
IAuthenticationRequest request = rp.CreateRequest("=MultipleEndpoint", realm, return_to);
Assert.AreEqual("https://authn.freexri.com/auth20/", request.Provider.Uri.AbsoluteUri);
rp.EndpointOrder = (se1, se2) => -se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
request = rp.CreateRequest("=MultipleEndpoint", realm, return_to);
Assert.AreEqual("https://authn.freexri.com/auth10/", request.Provider.Uri.AbsoluteUri);
// Now test the filter. Auth20 would come out on top, if we didn't select it out with the filter.
rp.EndpointOrder = OpenIdRelyingParty.DefaultEndpointOrder;
rp.EndpointFilter = (se) => se.Uri.AbsoluteUri == "https://authn.freexri.com/auth10/";
request = rp.CreateRequest("=MultipleEndpoint", realm, return_to);
Assert.AreEqual("https://authn.freexri.com/auth10/", request.Provider.Uri.AbsoluteUri);
}
private string stripScheme(string identifier) {
return identifier.Substring(identifier.IndexOf("://") + 3);
}
[Test]
public void RequireSslPrependsHttpsScheme() {
MockHttpRequest.Reset();
OpenIdRelyingParty rp = TestSupport.CreateRelyingParty(null);
rp.Settings.RequireSsl = true;
Identifier mockId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true);
string noSchemeId = stripScheme(mockId);
var request = rp.CreateRequest(noSchemeId, TestSupport.Realm, TestSupport.ReturnTo);
Assert.IsTrue(request.ClaimedIdentifier.ToString().StartsWith("https://", StringComparison.OrdinalIgnoreCase));
}
[Test]
public void DirectedIdentityWithRequireSslSucceeds() {
Uri claimedId = TestSupport.GetFullUrl("/secureClaimedId", null, true);
Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.AutoApproval, claimedId, true, true);
var rp = TestSupport.CreateRelyingParty(null);
rp.Settings.RequireSsl = true;
var rpRequest = rp.CreateRequest(opIdentifier, TestSupport.Realm, TestSupport.ReturnTo);
var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, opRequest => {
opRequest.IsAuthenticated = true;
opRequest.ClaimedIdentifier = claimedId;
});
Assert.AreEqual(AuthenticationStatus.Authenticated, rpResponse.Status);
}
[Test]
public void DirectedIdentityWithRequireSslFailsWithoutSecureIdentity() {
Uri claimedId = TestSupport.GetFullUrl("/insecureClaimedId", null, false);
Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.AutoApproval, claimedId, true, true);
var rp = TestSupport.CreateRelyingParty(null);
rp.Settings.RequireSsl = true;
var rpRequest = rp.CreateRequest(opIdentifier, TestSupport.Realm, TestSupport.ReturnTo);
var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, opRequest => {
opRequest.IsAuthenticated = true;
opRequest.ClaimedIdentifier = claimedId;
});
Assert.AreEqual(AuthenticationStatus.Failed, rpResponse.Status);
}
[Test]
public void DirectedIdentityWithRequireSslFailsWithoutSecureProviderEndpoint() {
Uri claimedId = TestSupport.GetFullUrl("/secureClaimedId", null, true);
// We want to generate an OP Identifier that itself is secure, but whose
// XRDS doc describes an insecure provider endpoint.
Identifier opIdentifier = TestSupport.GetMockOPIdentifier(TestSupport.Scenarios.AutoApproval, claimedId, true, false);
var rp = TestSupport.CreateRelyingParty(null);
rp.Settings.RequireSsl = true;
var rpRequest = rp.CreateRequest(opIdentifier, TestSupport.Realm, TestSupport.ReturnTo);
var rpResponse = TestSupport.CreateRelyingPartyResponseThroughProvider(rpRequest, opRequest => {
opRequest.IsAuthenticated = true;
opRequest.ClaimedIdentifier = claimedId;
});
Assert.AreEqual(AuthenticationStatus.Failed, rpResponse.Status);
}
[Test]
public void UnsolicitedAssertionWithRequireSsl() {
MockHttpRequest.Reset();
Mocks.MockHttpRequest.RegisterMockRPDiscovery();
TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
Identifier claimedId = TestSupport.GetMockIdentifier(scenario, ProtocolVersion.V20, true);
Identifier localId = TestSupport.GetDelegateUrl(scenario, true);
OpenIdProvider op = TestSupport.CreateProvider(null, true);
IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
var opAuthWebResponse = (Response)assertion;
var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
var rp = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, opAuthResponse.RedirectUrl,
opAuthResponse.EncodedFields.ToNameValueCollection());
rp.Settings.RequireSsl = true;
Assert.AreEqual(AuthenticationStatus.Authenticated, rp.Response.Status);
Assert.AreEqual(claimedId, rp.Response.ClaimedIdentifier);
}
[Test]
public void UnsolicitedAssertionWithRequireSslWithoutSecureIdentityUrl() {
MockHttpRequest.Reset();
Mocks.MockHttpRequest.RegisterMockRPDiscovery();
TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
Identifier claimedId = TestSupport.GetMockIdentifier(scenario, ProtocolVersion.V20);
Identifier localId = TestSupport.GetDelegateUrl(scenario);
OpenIdProvider op = TestSupport.CreateProvider(null);
IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
var opAuthWebResponse = (Response)assertion;
var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
var rp = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, opAuthResponse.RedirectUrl,
opAuthResponse.EncodedFields.ToNameValueCollection());
rp.Settings.RequireSsl = true;
Assert.AreEqual(AuthenticationStatus.Failed, rp.Response.Status);
Assert.IsNull(rp.Response.ClaimedIdentifier);
}
[Test]
public void UnsolicitedAssertionWithRequireSslWithSecureIdentityButInsecureProviderEndpoint() {
MockHttpRequest.Reset();
Mocks.MockHttpRequest.RegisterMockRPDiscovery();
TestSupport.Scenarios scenario = TestSupport.Scenarios.AutoApproval;
ProtocolVersion version = ProtocolVersion.V20;
ServiceEndpoint providerEndpoint = TestSupport.GetServiceEndpoint(scenario, version, 10, false);
Identifier claimedId = new MockIdentifier(TestSupport.GetIdentityUrl(scenario, version, true),
new ServiceEndpoint[] { providerEndpoint });
Identifier localId = TestSupport.GetDelegateUrl(scenario, true);
OpenIdProvider op = TestSupport.CreateProvider(null, false);
IResponse assertion = op.PrepareUnsolicitedAssertion(TestSupport.Realm, claimedId, localId);
var opAuthWebResponse = (Response)assertion;
var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
var rp = TestSupport.CreateRelyingParty(TestSupport.RelyingPartyStore, opAuthResponse.RedirectUrl,
opAuthResponse.EncodedFields.ToNameValueCollection());
rp.Settings.RequireSsl = true;
Assert.AreEqual(AuthenticationStatus.Failed, rp.Response.Status);
Assert.IsNull(rp.Response.ClaimedIdentifier);
}
///
/// Verifies that an RP will not "discover" endpoints below OpenID 2.0 when appropriate.
///
[Test, ExpectedException(typeof(OpenIdException))]
public void MinimumOPVersion20() {
MockIdentifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V11);
var rp = TestSupport.CreateRelyingParty(null);
rp.Settings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
rp.CreateRequest(id, TestSupport.Realm, TestSupport.ReturnTo);
}
///
/// Verifies that an RP configured to require 2.0 OPs will fail on communicating with 1.x OPs
/// that merely advertise 2.0 support but don't really have it.
///
[Test]
public void MinimumOPVersion20WithDeceptiveEndpointRealizedAtAuthentication() {
// Create an identifier that claims to have a 2.0 OP endpoint.
MockIdentifier id = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20);
var rp = TestSupport.CreateRelyingParty(null, null);
IAuthenticationRequest req = rp.CreateRequest(id, TestSupport.Realm, TestSupport.ReturnTo);
IResponse providerResponse = TestSupport.CreateProviderResponseToRequest(req, opReq => {
opReq.IsAuthenticated = true;
});
var opAuthWebResponse = (Response)providerResponse;
var opAuthResponse = (DotNetOpenId.Provider.EncodableResponse)opAuthWebResponse.EncodableMessage;
var rp2 =TestSupport. CreateRelyingParty(null, opAuthResponse.RedirectUrl,
opAuthResponse.EncodedFields.ToNameValueCollection());
rp2.Settings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
// Rig an intercept between the provider and RP to make our own Provider LOOK like a 1.x provider.
var sniffer = new DirectMessageSniffWrapper(rp2.DirectMessageChannel);
rp2.DirectMessageChannel = sniffer;
sniffer.Receiving += (endpoint, fields) => {
fields.Remove(Protocol.v20.openidnp.ns);
};
var resp = rp2.Response;
Assert.AreEqual(AuthenticationStatus.Failed, resp.Status, "Authentication should have failed since OP is really a 1.x OP masquerading as a 2.0 OP.");
}
}
}