diff options
49 files changed, 1126 insertions, 189 deletions
diff --git a/samples/RelyingPartyPortal/Code/State.cs b/samples/RelyingPartyPortal/Code/State.cs index 642780c..234c7ac 100644 --- a/samples/RelyingPartyPortal/Code/State.cs +++ b/samples/RelyingPartyPortal/Code/State.cs @@ -5,8 +5,16 @@ using DotNetOpenId.Extensions.SimpleRegistration; /// Strong-typed bag of session state.
/// </summary>
public class State {
+ public static void Clear() {
+ ProfileFields = null;
+ FriendlyLoginName = null;
+ }
public static ClaimsResponse ProfileFields {
get { return HttpContext.Current.Session["ProfileFields"] as ClaimsResponse; }
set { HttpContext.Current.Session["ProfileFields"] = value; }
}
+ public static string FriendlyLoginName {
+ get { return HttpContext.Current.Session["FriendlyUsername"] as string; }
+ set { HttpContext.Current.Session["FriendlyUsername"] = value; }
+ }
}
diff --git a/samples/RelyingPartyPortal/Default.aspx b/samples/RelyingPartyPortal/Default.aspx index 1fb831f..bdb9b27 100644 --- a/samples/RelyingPartyPortal/Default.aspx +++ b/samples/RelyingPartyPortal/Default.aspx @@ -10,5 +10,4 @@ <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/MembersOnly/Default.aspx"
Text="Members Only" />
area. (This will trigger a login demo). </p>
- <asp:LoginStatus ID="LoginStatus1" runat="server" />
</asp:Content>
diff --git a/samples/RelyingPartyPortal/MembersOnly/Default.aspx b/samples/RelyingPartyPortal/MembersOnly/Default.aspx index c9b43e8..9c3652d 100644 --- a/samples/RelyingPartyPortal/MembersOnly/Default.aspx +++ b/samples/RelyingPartyPortal/MembersOnly/Default.aspx @@ -8,7 +8,6 @@ Congratulations, <b><asp:LoginName ID="LoginName1" runat="server" /></b>.
You have completed the OpenID login process.
</p>
- <asp:LoginStatus ID="LoginStatus1" runat="server" />
<% if (State.ProfileFields != null) { %>
<p>
diff --git a/samples/RelyingPartyPortal/RelyingPartyPortal.csproj b/samples/RelyingPartyPortal/RelyingPartyPortal.csproj index 4a0d795..9799cac 100644 --- a/samples/RelyingPartyPortal/RelyingPartyPortal.csproj +++ b/samples/RelyingPartyPortal/RelyingPartyPortal.csproj @@ -108,6 +108,7 @@ <Content Include="styles.css" />
</ItemGroup>
<ItemGroup>
+ <Content Include="images\openid_login.gif" />
<Content Include="Site.Master" />
<Content Include="TracePage.aspx" />
</ItemGroup>
diff --git a/samples/RelyingPartyPortal/Site.Master b/samples/RelyingPartyPortal/Site.Master index 944cc70..bbb7bc3 100644 --- a/samples/RelyingPartyPortal/Site.Master +++ b/samples/RelyingPartyPortal/Site.Master @@ -1,6 +1,17 @@ <%@ Master Language="C#" AutoEventWireup="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<script runat="server">
+ protected void Page_Load(object sender, EventArgs e) {
+ friendlyUsername.Text = State.FriendlyLoginName;
+ }
+
+ protected void LoginStatus1_LoggedOut(object sender, EventArgs e) {
+ State.Clear();
+ }
+</script>
+
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>OpenID Relying Party, by DotNetOpenId</title>
@@ -9,9 +20,17 @@ </head>
<body>
<form id="form1" runat="server">
- <div><a href="http://dotnetopenid.googlecode.com">
- <img runat="server" src="~/images/dotnetopenid_tiny.gif" title="Jump to the project web site."
- alt="DotNetOpenId" border='0' /></a> </div>
+ <span style="float: right">
+ <asp:Image runat="server" ID="openIdUsernameImage" ImageUrl="~/images/openid_login.gif"
+ EnableViewState="false" />
+ <asp:Label runat="server" ID="friendlyUsername" Text="" EnableViewState="false" />
+ <asp:LoginStatus ID="LoginStatus1" runat="server" OnLoggedOut="LoginStatus1_LoggedOut" />
+ </span>
+ <div>
+ <a href="http://dotnetopenid.googlecode.com">
+ <img runat="server" src="~/images/dotnetopenid_tiny.gif" title="Jump to the project web site."
+ alt="DotNetOpenId" border='0' /></a>
+ </div>
<div>
<asp:ContentPlaceHolder ID="Main" runat="server" />
</div>
diff --git a/samples/RelyingPartyPortal/images/openid_login.gif b/samples/RelyingPartyPortal/images/openid_login.gif Binary files differnew file mode 100644 index 0000000..cde836c --- /dev/null +++ b/samples/RelyingPartyPortal/images/openid_login.gif diff --git a/samples/RelyingPartyPortal/login.aspx.cs b/samples/RelyingPartyPortal/login.aspx.cs index a3c062d..0713c7a 100644 --- a/samples/RelyingPartyPortal/login.aspx.cs +++ b/samples/RelyingPartyPortal/login.aspx.cs @@ -1,6 +1,7 @@ using System;
using System.Web.UI;
using DotNetOpenId.RelyingParty;
+using DotNetOpenId.Extensions.SimpleRegistration;
public partial class login : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
@@ -13,7 +14,8 @@ public partial class login : System.Web.UI.Page { /// Note, that straight after login, forms auth will redirect the user to their original page. So this page may never be rendererd.
/// </summary>
protected void OpenIdLogin1_LoggedIn(object sender, OpenIdEventArgs e) {
- State.ProfileFields = e.ProfileFields;
+ State.FriendlyLoginName = e.Response.FriendlyIdentifierForDisplay;
+ State.ProfileFields = e.Response.GetExtension<ClaimsResponse>();
}
protected void OpenIdLogin1_Failed(object sender, OpenIdEventArgs e) {
loginFailedLabel.Visible = true;
diff --git a/samples/RelyingPartyPortal/logout.aspx b/samples/RelyingPartyPortal/logout.aspx index ce4fdf8..40b655c 100644 --- a/samples/RelyingPartyPortal/logout.aspx +++ b/samples/RelyingPartyPortal/logout.aspx @@ -3,9 +3,11 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
-protected void Page_Load(object sender, EventArgs e)
-{
- System.Web.Security.FormsAuthentication.SignOut();
- Response.Redirect("~/");
-}
+ protected void Page_Load(object sender, EventArgs e) {
+ State.FriendlyLoginName = null;
+ State.ProfileFields = null;
+ System.Web.Security.FormsAuthentication.SignOut();
+ Response.Redirect("~/");
+ }
</script>
+
diff --git a/src/DotNetOpenId.Test/EndToEndTesting.cs b/src/DotNetOpenId.Test/EndToEndTesting.cs index 8f94744..1997638 100644 --- a/src/DotNetOpenId.Test/EndToEndTesting.cs +++ b/src/DotNetOpenId.Test/EndToEndTesting.cs @@ -25,10 +25,16 @@ namespace DotNetOpenId.Test { void parameterizedTest(UriIdentifier identityUrl,
AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
bool tryReplayAttack, bool provideStore) {
- parameterizedProgrammaticTest(identityUrl, requestMode, expectedResult, tryReplayAttack, provideStore);
+ parameterizedProgrammaticTest(identityUrl, identityUrl, requestMode, expectedResult, tryReplayAttack, provideStore);
parameterizedWebClientTest(identityUrl, requestMode, expectedResult, tryReplayAttack, provideStore);
}
- void parameterizedProgrammaticTest(UriIdentifier identityUrl,
+ void parameterizedTest(UriIdentifier opIdentifier, UriIdentifier claimedIdentifier,
+ AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
+ bool tryReplayAttack, bool provideStore) {
+ parameterizedProgrammaticTest(opIdentifier, claimedIdentifier, requestMode, expectedResult, tryReplayAttack, provideStore);
+ parameterizedWebClientTest(opIdentifier, requestMode, expectedResult, tryReplayAttack, provideStore);
+ }
+ void parameterizedProgrammaticTest(UriIdentifier identityUrl, UriIdentifier claimedUrl,
AuthenticationRequestMode requestMode, AuthenticationStatus expectedResult,
bool tryReplayAttack, bool provideStore) {
var store = provideStore ? appStore : null;
@@ -74,7 +80,7 @@ namespace DotNetOpenId.Test { }
consumer = new OpenIdRelyingParty(store, redirectUrl, HttpUtility.ParseQueryString(redirectUrl.Query));
Assert.AreEqual(expectedResult, consumer.Response.Status);
- Assert.AreEqual(identityUrl, consumer.Response.ClaimedIdentifier);
+ Assert.AreEqual(claimedUrl, consumer.Response.ClaimedIdentifier);
// Try replay attack
if (tryReplayAttack) {
@@ -268,5 +274,40 @@ namespace DotNetOpenId.Test { false
);
}
+ [Test]
+ public void Pass_Setup_ApproveOnSetup_DirectedIdentity_20() {
+ parameterizedTest(
+ TestSupport.GetOPIdentityUrl(TestSupport.Scenarios.ApproveOnSetup),
+ TestSupport.GetDirectedIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
+ AuthenticationRequestMode.Setup,
+ AuthenticationStatus.Authenticated,
+ true,
+ true);
+ }
+ [Test]
+ public void Pass_NoStore_ApproveOnSetup_DirectedIdentity_20() {
+ parameterizedTest(
+ TestSupport.GetOPIdentityUrl(TestSupport.Scenarios.ApproveOnSetup),
+ TestSupport.GetDirectedIdentityUrl(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20),
+ AuthenticationRequestMode.Setup,
+ AuthenticationStatus.Authenticated,
+ true,
+ false);
+ }
+
+ [Test]
+ public void ProviderAddedFragmentRemainsInClaimedIdentifier() {
+ Uri userSuppliedIdentifier = TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApprovalAddFragment, ProtocolVersion.V20);
+ UriBuilder claimedIdentifier = new UriBuilder(userSuppliedIdentifier);
+ claimedIdentifier.Fragment = "frag";
+ parameterizedProgrammaticTest(
+ userSuppliedIdentifier,
+ claimedIdentifier.Uri,
+ AuthenticationRequestMode.Setup,
+ AuthenticationStatus.Authenticated,
+ false,
+ true
+ );
+ }
}
}
diff --git a/src/DotNetOpenId.Test/IdentifierTests.cs b/src/DotNetOpenId.Test/IdentifierTests.cs index 0f13ae2..562ed90 100644 --- a/src/DotNetOpenId.Test/IdentifierTests.cs +++ b/src/DotNetOpenId.Test/IdentifierTests.cs @@ -51,9 +51,6 @@ namespace DotNetOpenId.Test { id = Identifier.Parse(uriNoScheme);
Assert.IsInstanceOfType(typeof(UriIdentifier), id);
Assert.AreEqual(uri, ((UriIdentifier)id).Uri.AbsoluteUri);
- // verify that fragments are stripped
- id = Identifier.Parse(uri + "#fragment");
- Assert.AreEqual(uri, ((UriIdentifier)id).Uri.AbsoluteUri);
}
[Test, ExpectedException(typeof(ArgumentNullException))]
diff --git a/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs b/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs index a2ca984..2503207 100644 --- a/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs +++ b/src/DotNetOpenId.Test/RelyingParty/OpenIdRelyingPartyTest.cs @@ -1,9 +1,10 @@ using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Web;
using DotNetOpenId.RelyingParty;
using NUnit.Framework;
using ProviderMemoryStore = DotNetOpenId.AssociationMemoryStore<DotNetOpenId.AssociationRelyingPartyType>;
-using System.Web;
-using System.Collections.Specialized;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
@@ -21,6 +22,11 @@ namespace DotNetOpenId.Test.RelyingParty { UntrustedWebRequest.WhitelistHosts.Add("localhost");
}
+ [TearDown]
+ public void TearDown() {
+ UntrustedWebRequest.MockRequests = null;
+ }
+
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void DefaultCtorWithoutContext() {
@@ -52,6 +58,15 @@ namespace DotNetOpenId.Test.RelyingParty { }
[Test]
+ public void CreateRequestStripsFragment() {
+ var consumer = new OpenIdRelyingParty(store, simpleNonOpenIdRequest, new NameValueCollection());
+ UriBuilder userSuppliedIdentifier = new UriBuilder((Uri)TestSupport.GetIdentityUrl(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20));
+ userSuppliedIdentifier.Fragment = "c";
+ IAuthenticationRequest request = consumer.CreateRequest(userSuppliedIdentifier.Uri, realm, returnTo);
+ Assert.AreEqual(0, new Uri(request.ClaimedIdentifier).Fragment.Length);
+ }
+
+ [Test]
public void AssociationCreationWithStore() {
var providerStore = new ProviderMemoryStore();
@@ -142,5 +157,103 @@ namespace DotNetOpenId.Test.RelyingParty { 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 = @"<?xml version='1.0' encoding='UTF-8'?>
+<XRD xmlns='xri://$xrd*($v*2.0)'>
+ <Query>=MultipleEndpoint</Query>
+ <ProviderID>=!91F2.8153.F600.AE24</ProviderID>
+ <CanonicalID>=!91F2.8153.F600.AE24</CanonicalID>
+ <Service>
+ <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID>
+ <Type select='true'>xri://+i-service*(+contact)*($v*1.0)</Type>
+ <Type match='null'/>
+ <Path select='true'>(+contact)</Path>
+ <Path match='null'/>
+ <MediaType match='default'/>
+ <URI append='qxri'>http://contact.freexri.com/contact/</URI>
+ </Service>
+ <Service priority='20'>
+ <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID>
+ <Type select='true'>http://openid.net/signon/1.0</Type>
+ <Path select='true'>(+login)</Path>
+ <Path match='default'/>
+ <MediaType match='default'/>
+ <URI append='none' priority='2'>http://authn.freexri.com/auth10/</URI>
+ <URI append='none' priority='1'>https://authn.freexri.com/auth10/</URI>
+ </Service>
+ <Service priority='10'>
+ <ProviderID>@!7F6F.F50.A4E4.1133</ProviderID>
+ <Type select='true'>http://specs.openid.net/auth/2.0/signon</Type>
+ <Path select='true'>(+login)</Path>
+ <Path match='default'/>
+ <MediaType match='default'/>
+ <URI append='none' priority='2'>http://authn.freexri.com/auth20/</URI>
+ <URI append='none' priority='1'>https://authn.freexri.com/auth20/</URI>
+ </Service>
+ <ServedBy>OpenXRI</ServedBy>
+</XRD>";
+ UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(new Dictionary<string, string> {
+ {"https://xri.net/=MultipleEndpoint?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
+ {"https://xri.net/=!91F2.8153.F600.AE24?_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);
+ }
}
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs b/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs index b85044b..cb2a61b 100644 --- a/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs +++ b/src/DotNetOpenId.Test/RelyingParty/ServiceEndpointTests.cs @@ -1,32 +1,37 @@ using System;
using System.Collections.Generic;
-using System.Linq;
+using System.IO;
using System.Text;
-using NUnit.Framework;
using DotNetOpenId.RelyingParty;
-using System.IO;
+using NUnit.Framework;
+using System.Diagnostics;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
public class ServiceEndpointTests {
- Identifier claimedId = "http://claimedid.justatest.com";
+ UriIdentifier claimedId = new UriIdentifier("http://claimedid.justatest.com");
+ XriIdentifier claimedXri = new XriIdentifier("=!9B72.7DD1.50A9.5CCD");
+ XriIdentifier userSuppliedXri = new XriIdentifier("=Arnot");
Uri providerEndpoint = new Uri("http://someprovider.com");
Identifier localId = "http://localid.someprovider.com";
string[] v20TypeUris = { Protocol.v20.ClaimedIdentifierServiceTypeURI };
string[] v11TypeUris = { Protocol.v11.ClaimedIdentifierServiceTypeURI };
+ int servicePriority = 10;
+ int uriPriority = 10;
[Test]
public void Ctor() {
- ServiceEndpoint se = new ServiceEndpoint(claimedId, providerEndpoint, localId, v20TypeUris);
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
Assert.AreSame(claimedId, se.ClaimedIdentifier);
Assert.AreSame(providerEndpoint, se.ProviderEndpoint);
Assert.AreSame(localId, se.ProviderLocalIdentifier);
Assert.AreSame(v20TypeUris, se.ProviderSupportedServiceTypeUris);
+ Assert.AreEqual(servicePriority, ((IXrdsProviderEndpoint)se).ServicePriority);
}
[Test]
public void CtorImpliedLocalIdentifier() {
- ServiceEndpoint se = new ServiceEndpoint(claimedId, providerEndpoint, null, v20TypeUris);
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, null, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
Assert.AreSame(claimedId, se.ClaimedIdentifier);
Assert.AreSame(providerEndpoint, se.ProviderEndpoint);
Assert.AreSame(claimedId, se.ProviderLocalIdentifier);
@@ -35,25 +40,26 @@ namespace DotNetOpenId.Test.RelyingParty { [Test]
public void ProtocolDetection() {
- ServiceEndpoint se = new ServiceEndpoint(claimedId, providerEndpoint, localId, v20TypeUris);
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
Assert.AreSame(Protocol.v20, se.Protocol);
- se = new ServiceEndpoint(claimedId, providerEndpoint, localId,
- new[] { Protocol.v20.OPIdentifierServiceTypeURI });
+ se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint,
+ new[] { Protocol.v20.OPIdentifierServiceTypeURI }, servicePriority, uriPriority);
Assert.AreSame(Protocol.v20, se.Protocol);
- se = new ServiceEndpoint(claimedId, providerEndpoint, localId, v11TypeUris);
+ se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v11TypeUris, servicePriority, uriPriority);
Assert.AreSame(Protocol.v11, se.Protocol);
}
[Test, ExpectedException(typeof(InvalidOperationException))]
public void ProtocolDetectionWithoutClues() {
- ServiceEndpoint se = new ServiceEndpoint(claimedId, providerEndpoint, localId,
- new[] { Protocol.v20.HtmlDiscoveryLocalIdKey }); // random type URI irrelevant to detection
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(
+ claimedId, localId, providerEndpoint,
+ new[] { Protocol.v20.HtmlDiscoveryLocalIdKey }, servicePriority, uriPriority); // random type URI irrelevant to detection
Protocol p = se.Protocol;
}
[Test]
- public void Serialization() {
- ServiceEndpoint se = new ServiceEndpoint(claimedId, providerEndpoint, localId, v20TypeUris);
+ public void SerializationWithUri() {
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb)) {
se.Serialize(sw);
@@ -62,25 +68,77 @@ namespace DotNetOpenId.Test.RelyingParty { ServiceEndpoint se2 = ServiceEndpoint.Deserialize(sr);
Assert.AreEqual(se, se2);
Assert.AreEqual(se.Protocol.Version, se2.Protocol.Version, "Particularly interested in this, since type URIs are not serialized but version info is.");
+ Assert.AreEqual(se.UserSuppliedIdentifier, se2.UserSuppliedIdentifier);
+ Assert.AreEqual(se.FriendlyIdentifierForDisplay, se2.FriendlyIdentifierForDisplay);
+ }
+ }
+
+ [Test]
+ public void SerializationWithXri() {
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(claimedXri, userSuppliedXri, localId, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
+ StringBuilder sb = new StringBuilder();
+ using (StringWriter sw = new StringWriter(sb)) {
+ se.Serialize(sw);
+ }
+ using (StringReader sr = new StringReader(sb.ToString())) {
+ ServiceEndpoint se2 = ServiceEndpoint.Deserialize(sr);
+ Assert.AreEqual(se, se2);
+ Assert.AreEqual(se.Protocol.Version, se2.Protocol.Version, "Particularly interested in this, since type URIs are not serialized but version info is.");
+ Assert.AreEqual(se.UserSuppliedIdentifier, se2.UserSuppliedIdentifier);
+ Assert.AreEqual(se.FriendlyIdentifierForDisplay, se2.FriendlyIdentifierForDisplay);
}
}
[Test]
public void EqualsTests() {
- ServiceEndpoint se = new ServiceEndpoint(claimedId, providerEndpoint, localId, v20TypeUris);
- ServiceEndpoint se2 = new ServiceEndpoint(claimedId, providerEndpoint, localId, v20TypeUris);
+ ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
+ ServiceEndpoint se2 = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v20TypeUris, (int?)null, (int?)null);
Assert.AreEqual(se2, se);
Assert.AreNotEqual(se, null);
Assert.AreNotEqual(null, se);
- ServiceEndpoint se3 = new ServiceEndpoint(claimedId + "a", providerEndpoint, localId, v20TypeUris);
+ ServiceEndpoint se3 = ServiceEndpoint.CreateForClaimedIdentifier(new UriIdentifier(claimedId + "a"), localId, providerEndpoint, v20TypeUris, servicePriority, uriPriority);
Assert.AreNotEqual(se, se3);
- se3 = new ServiceEndpoint(claimedId, new Uri(providerEndpoint.AbsoluteUri + "a"), localId, v20TypeUris);
+ se3 = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, new Uri(providerEndpoint.AbsoluteUri + "a"), v20TypeUris, servicePriority, uriPriority);
Assert.AreNotEqual(se, se3);
- se3 = new ServiceEndpoint(claimedId, providerEndpoint, localId + "a", v20TypeUris);
+ se3 = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId + "a", providerEndpoint, v20TypeUris, servicePriority, uriPriority);
Assert.AreNotEqual(se, se3);
- se3 = new ServiceEndpoint(claimedId, providerEndpoint, localId, v11TypeUris);
+ se3 = ServiceEndpoint.CreateForClaimedIdentifier(claimedId, localId, providerEndpoint, v11TypeUris, servicePriority, uriPriority);
Assert.AreNotEqual(se, se3);
+
+ // make sure that Collection<T>.Contains works as desired.
+ List<ServiceEndpoint> list = new List<ServiceEndpoint>();
+ list.Add(se);
+ Assert.IsTrue(list.Contains(se2));
+ }
+
+ [Test]
+ public void FriendlyIdentifierForDisplay() {
+ Uri providerEndpoint= new Uri("http://someprovider");
+ Identifier localId = "someuser";
+ string[] serviceTypeUris = new string[0];
+ ServiceEndpoint se;
+
+ // strip of protocol and fragment
+ se = ServiceEndpoint.CreateForClaimedIdentifier("http://someprovider.somedomain.com:79/someuser#frag",
+ localId, providerEndpoint, serviceTypeUris, null, null);
+ Assert.AreEqual("someprovider.somedomain.com:79/someuser", se.FriendlyIdentifierForDisplay);
+
+ // unescape characters
+ Uri foreignUri = new Uri("http://server崎/村");
+ se = ServiceEndpoint.CreateForClaimedIdentifier(foreignUri, localId, providerEndpoint, serviceTypeUris, null, null);
+ Assert.AreEqual("server崎/村", se.FriendlyIdentifierForDisplay);
+
+ // restore user supplied identifier to XRIs
+ se = ServiceEndpoint.CreateForClaimedIdentifier(new XriIdentifier("=!9B72.7DD1.50A9.5CCD"),
+ new XriIdentifier("=Arnott崎村"), localId, providerEndpoint, serviceTypeUris, null, null);
+ Assert.AreEqual("=!9B72.7DD1.50A9.5CCD (=Arnott崎村)", se.FriendlyIdentifierForDisplay);
+
+ // If UserSuppliedIdentifier is the same as the ClaimedIdentifier, don't display it twice...
+ se = ServiceEndpoint.CreateForClaimedIdentifier(
+ new XriIdentifier("=!9B72.7DD1.50A9.5CCD"), new XriIdentifier("=!9B72.7DD1.50A9.5CCD"),
+ localId, providerEndpoint, serviceTypeUris, null, null);
+ Assert.AreEqual("=!9B72.7DD1.50A9.5CCD", se.FriendlyIdentifierForDisplay);
}
}
}
diff --git a/src/DotNetOpenId.Test/RelyingParty/TokenTest.cs b/src/DotNetOpenId.Test/RelyingParty/TokenTest.cs index b83c47f..c57f68a 100644 --- a/src/DotNetOpenId.Test/RelyingParty/TokenTest.cs +++ b/src/DotNetOpenId.Test/RelyingParty/TokenTest.cs @@ -1,20 +1,18 @@ -using System;
-using System.Collections.Generic;
-using System.Text;
+using DotNetOpenId.RelyingParty;
using NUnit.Framework;
-using DotNetOpenId.RelyingParty;
-using System.Threading;
namespace DotNetOpenId.Test.RelyingParty {
[TestFixture]
public class TokenTest {
static ServiceEndpoint getServiceEndpoint(TestSupport.Scenarios scenario, ProtocolVersion version) {
Protocol protocol = Protocol.Lookup(version);
- ServiceEndpoint ep = new ServiceEndpoint(
+ ServiceEndpoint ep = ServiceEndpoint.CreateForClaimedIdentifier(
TestSupport.GetIdentityUrl(scenario, version),
- TestSupport.GetFullUrl(TestSupport.ProviderPage),
TestSupport.GetDelegateUrl(scenario),
- new[] { protocol.ClaimedIdentifierServiceTypeURI }
+ TestSupport.GetFullUrl(TestSupport.ProviderPage),
+ new[] { protocol.ClaimedIdentifierServiceTypeURI },
+ 10,
+ 10
);
return ep;
}
diff --git a/src/DotNetOpenId.Test/TestSupport.cs b/src/DotNetOpenId.Test/TestSupport.cs index be17494..7022941 100644 --- a/src/DotNetOpenId.Test/TestSupport.cs +++ b/src/DotNetOpenId.Test/TestSupport.cs @@ -16,12 +16,14 @@ public class TestSupport { public static readonly string TestWebDirectory = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\src\DotNetOpenId.TestWeb"));
public const string HostTestPage = "HostTest.aspx";
const string identityPage = "IdentityEndpoint.aspx";
+ const string directedIdentityPage = "DirectedIdentityEndpoint.aspx";
public const string ProviderPage = "ProviderEndpoint.aspx";
public const string MobileConsumerPage = "RelyingPartyMobile.aspx";
public const string ConsumerPage = "RelyingParty.aspx";
public enum Scenarios {
// Authentication test scenarios
AutoApproval,
+ AutoApprovalAddFragment,
ApproveOnSetup,
AlwaysDeny,
@@ -35,12 +37,24 @@ public class TestSupport { /// </summary>
ExtensionPartialCooperation,
}
+ internal static UriIdentifier GetOPIdentityUrl(Scenarios scenario) {
+ UriBuilder builder = new UriBuilder(Host.BaseUri);
+ builder.Path = "/opdefault.aspx";
+ builder.Query = "user=" + scenario;
+ return new UriIdentifier(builder.Uri);
+ }
internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) {
UriBuilder builder = new UriBuilder(Host.BaseUri);
builder.Path = "/" + identityPage;
builder.Query = "user=" + scenario + "&version=" + providerVersion;
return new UriIdentifier(builder.Uri);
}
+ internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) {
+ UriBuilder builder = new UriBuilder(Host.BaseUri);
+ builder.Path = "/" + directedIdentityPage;
+ builder.Query = "user=" + scenario + "&version=" + providerVersion;
+ return new UriIdentifier(builder.Uri);
+ }
public static Identifier GetDelegateUrl(Scenarios scenario) {
return new UriIdentifier(new Uri(Host.BaseUri, "/" + scenario));
}
@@ -56,6 +70,25 @@ public class TestSupport { internal static AspNetHost Host { get; private set; }
internal static EncodingInterceptor Interceptor { get; private set; }
+ internal static UntrustedWebRequest.MockRequestResponse GenerateMockXrdsResponses(IDictionary<string, string> requestUriAndResponseBody) {
+ return (uri, body, acceptTypes) => {
+ string contentType = "text/xml; saml=false; https=false; charset=UTF-8";
+ string contentEncoding = null;
+ MemoryStream stream = new MemoryStream();
+ StreamWriter sw = new StreamWriter(stream);
+ Assert.IsNull(body);
+ string responseBody;
+ if (!requestUriAndResponseBody.TryGetValue(uri.AbsoluteUri, out responseBody)) {
+ Assert.Fail("Unexpected HTTP request: {0}", uri);
+ }
+ sw.Write(responseBody);
+ sw.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+ return new UntrustedWebResponse(uri, uri, new WebHeaderCollection(),
+ HttpStatusCode.OK, contentType, contentEncoding, stream);
+ };
+ }
+
[SetUp]
public void SetUp() {
Host = AspNetHost.CreateHost(TestSupport.TestWebDirectory);
@@ -92,6 +125,12 @@ public class TestSupport { }
nvc[protocol.openid.sig] = Convert.ToBase64String(assoc.Sign(subsetDictionary, signed));
}
+
+ public static IAssociationStore<AssociationRelyingPartyType> ProviderStoreContext {
+ get {
+ return DotNetOpenId.Provider.OpenIdProvider.HttpApplicationStore;
+ }
+ }
}
static class TestExtensions {
diff --git a/src/DotNetOpenId.Test/UriIdentifierTests.cs b/src/DotNetOpenId.Test/UriIdentifierTests.cs index cd42d48..16d8b51 100644 --- a/src/DotNetOpenId.Test/UriIdentifierTests.cs +++ b/src/DotNetOpenId.Test/UriIdentifierTests.cs @@ -1,10 +1,9 @@ using System;
-using System.Collections.Generic;
-using System.Text;
-using NUnit.Framework;
-using DotNetOpenId.RelyingParty;
+using System.Linq;
using System.Net;
using DotNetOpenId.Extensions.SimpleRegistration;
+using DotNetOpenId.RelyingParty;
+using NUnit.Framework;
namespace DotNetOpenId.Test {
[TestFixture]
@@ -45,6 +44,21 @@ namespace DotNetOpenId.Test { Assert.AreEqual(new Uri(goodUri), uri.Uri);
}
+ /// <summary>
+ /// Verifies that the fragment is not stripped from an Identifier.
+ /// </summary>
+ /// <remarks>
+ /// Although fragments should be stripped from user supplied identifiers,
+ /// they should NOT be stripped from claimed identifiers. So the UriIdentifier
+ /// class, which serves both identifier types, must not do the stripping.
+ /// </remarks>
+ [Test]
+ public void DoesNotStripFragment() {
+ Uri original = new Uri("http://a/b#c");
+ UriIdentifier identifier = new UriIdentifier(original);
+ Assert.AreEqual(original.Fragment, identifier.Uri.Fragment);
+ }
+
[Test]
public void IsValid() {
Assert.IsTrue(UriIdentifier.IsValidUri(goodUri));
@@ -53,6 +67,14 @@ namespace DotNetOpenId.Test { }
[Test]
+ public void TrimFragment() {
+ Identifier noFragment = UriIdentifier.Parse("http://a/b");
+ Identifier fragment = UriIdentifier.Parse("http://a/b#c");
+ Assert.AreSame(noFragment, noFragment.TrimFragment());
+ Assert.AreEqual(noFragment, fragment.TrimFragment());
+ }
+
+ [Test]
public void ToStringTest() {
Assert.AreEqual(goodUri, new UriIdentifier(goodUri).ToString());
}
@@ -60,6 +82,8 @@ namespace DotNetOpenId.Test { [Test]
public void EqualsTest() {
Assert.AreEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri));
+ // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok.
+ Assert.AreEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri + "#frag"));
Assert.AreNotEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri + "a"));
Assert.AreNotEqual(null, new UriIdentifier(goodUri));
Assert.AreNotEqual(goodUri, new UriIdentifier(goodUri));
@@ -71,6 +95,7 @@ namespace DotNetOpenId.Test { Assert.IsTrue(UriIdentifier.IsValidUri(unicodeUrl));
Identifier id;
Assert.IsTrue(UriIdentifier.TryParse(unicodeUrl, out id));
+ Assert.AreEqual("/opaffirmative/%E5%B4%8E%E6%9D%91.aspx", ((UriIdentifier)id).Uri.AbsolutePath);
Assert.AreEqual(Uri.EscapeUriString(unicodeUrl), id.ToString());
}
@@ -83,7 +108,7 @@ namespace DotNetOpenId.Test { Identifier idToDiscover = useRedirect ? userSuppliedIdentifier : claimedId;
// confirm the page exists (validates the test)
WebRequest.Create(idToDiscover).GetResponse().Close();
- ServiceEndpoint se = idToDiscover.Discover();
+ ServiceEndpoint se = idToDiscover.Discover().FirstOrDefault();
Assert.IsNotNull(se, url + " failed to be discovered.");
Assert.AreSame(protocol, se.Protocol);
Assert.AreEqual(claimedId, se.ClaimedIdentifier);
@@ -107,7 +132,7 @@ namespace DotNetOpenId.Test { void failDiscover(string url) {
UriIdentifier userSuppliedId = TestSupport.GetFullUrl(url);
WebRequest.Create((Uri)userSuppliedId).GetResponse().Close(); // confirm the page exists ...
- Assert.IsNull(userSuppliedId.Discover()); // ... but that no endpoint info is discoverable
+ Assert.AreEqual(0, userSuppliedId.Discover().Count()); // ... but that no endpoint info is discoverable
}
void failDiscoverHtml(string scenario) {
failDiscover("htmldiscovery/" + scenario + ".aspx");
diff --git a/src/DotNetOpenId.Test/XriIdentifierTests.cs b/src/DotNetOpenId.Test/XriIdentifierTests.cs index 72ae1c1..8ce2726 100644 --- a/src/DotNetOpenId.Test/XriIdentifierTests.cs +++ b/src/DotNetOpenId.Test/XriIdentifierTests.cs @@ -1,10 +1,10 @@ using System;
using System.Collections.Generic;
-using System.Text;
-using NUnit.Framework;
-using DotNetOpenId.RelyingParty;
-using System.Net;
using System.IO;
+using System.Linq;
+using System.Net;
+using DotNetOpenId.RelyingParty;
+using NUnit.Framework;
namespace DotNetOpenId.Test {
[TestFixture]
@@ -56,6 +56,12 @@ namespace DotNetOpenId.Test { }
[Test]
+ public void TrimFragment() {
+ Identifier xri = new XriIdentifier(goodXri);
+ Assert.AreSame(xri, xri.TrimFragment());
+ }
+
+ [Test]
public void ToStringTest() {
Assert.AreEqual(goodXri, new XriIdentifier(goodXri).ToString());
}
@@ -69,7 +75,7 @@ namespace DotNetOpenId.Test { }
private ServiceEndpoint verifyCanonicalId(Identifier iname, string expectedClaimedIdentifier) {
- ServiceEndpoint se = iname.Discover();
+ ServiceEndpoint se = iname.Discover().FirstOrDefault();
if (expectedClaimedIdentifier != null) {
Assert.IsNotNull(se);
Assert.AreEqual(expectedClaimedIdentifier, se.ClaimedIdentifier.ToString(), "i-name {0} discovery resulted in unexpected CanonicalId", iname);
@@ -80,25 +86,6 @@ namespace DotNetOpenId.Test { return se;
}
- UntrustedWebRequest.MockRequestResponse generateMockXrdsResponses(IDictionary<string, string> requestUriAndResponseBody) {
- return (uri, body, acceptTypes) => {
- string contentType = "text/xml; saml=false; https=false; charset=UTF-8";
- string contentEncoding = null;
- MemoryStream stream = new MemoryStream();
- StreamWriter sw = new StreamWriter(stream);
- Assert.IsNull(body);
- string responseBody;
- if (!requestUriAndResponseBody.TryGetValue(uri.AbsoluteUri, out responseBody)) {
- Assert.Fail("Unexpected HTTP request: {0}", uri);
- }
- sw.Write(responseBody);
- sw.Flush();
- stream.Seek(0, SeekOrigin.Begin);
- return new UntrustedWebResponse(uri, uri, new WebHeaderCollection(),
- HttpStatusCode.OK, contentType, contentEncoding, stream);
- };
- }
-
[Test]
public void Discover() {
string xrds = @"<?xml version='1.0' encoding='UTF-8'?>
@@ -136,13 +123,14 @@ namespace DotNetOpenId.Test { {"https://xri.net/=Arnott?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
{"https://xri.net/=!9B72.7DD1.50A9.5CCD?_xrd_r=application/xrd%2Bxml;sep=false", xrds},
};
- UntrustedWebRequest.MockRequests = generateMockXrdsResponses(mocks);
+ UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(mocks);
string expectedCanonicalId = "=!9B72.7DD1.50A9.5CCD";
ServiceEndpoint se = verifyCanonicalId("=Arnott", expectedCanonicalId);
Assert.AreEqual(Protocol.v10, se.Protocol);
Assert.AreEqual("http://1id.com/sso", se.ProviderEndpoint.ToString());
Assert.AreEqual(se.ClaimedIdentifier, se.ProviderLocalIdentifier);
+ Assert.AreEqual("=!9B72.7DD1.50A9.5CCD (=Arnott)", se.FriendlyIdentifierForDisplay);
}
[Test]
@@ -361,7 +349,7 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy </X509Data>
</KeyInfo>
</XRD>";
- UntrustedWebRequest.MockRequests = generateMockXrdsResponses(new Dictionary<string, string> {
+ UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(new Dictionary<string, string> {
{ "https://xri.net/@llli?_xrd_r=application/xrd%2Bxml;sep=false", llliResponse},
{ "https://xri.net/@!72CD.A072.157E.A9C6?_xrd_r=application/xrd%2Bxml;sep=false", llliResponse},
@@ -386,7 +374,7 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy [Test]
public void DiscoveryCommunityInameDelegateWithoutCanonicalID() {
- UntrustedWebRequest.MockRequests = generateMockXrdsResponses(new Dictionary<string, string> {
+ UntrustedWebRequest.MockRequests = TestSupport.GenerateMockXrdsResponses(new Dictionary<string, string> {
{ "https://xri.net/=Web*andrew.arnott?_xrd_r=application/xrd%2Bxml;sep=false", @"<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='xri://$xrd*($v*2.0)'>
<Query>*andrew.arnott</Query>
diff --git a/src/DotNetOpenId.TestWeb/DirectedIdentityEndpoint.aspx b/src/DotNetOpenId.TestWeb/DirectedIdentityEndpoint.aspx new file mode 100644 index 0000000..ae55a71 --- /dev/null +++ b/src/DotNetOpenId.TestWeb/DirectedIdentityEndpoint.aspx @@ -0,0 +1,27 @@ +<%@ Page Language="C#" %>
+
+<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId.Provider" TagPrefix="openid" %>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<script runat="server">
+ protected override void OnLoad(EventArgs e) {
+ base.OnLoad(e);
+
+ IdentityEndpoint1.ProviderEndpointUrl += "?user=" + Request.QueryString["user"];
+ IdentityEndpoint1.ProviderVersion = (DotNetOpenId.ProtocolVersion)
+ Enum.Parse(typeof(DotNetOpenId.ProtocolVersion), Request.QueryString["version"]);
+ }
+</script>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head runat="server">
+ <title>Identity page</title>
+ <openid:IdentityEndpoint ID="IdentityEndpoint1" runat="server" EnableViewState="false"
+ ProviderEndpointUrl="~/DirectedProviderEndpoint.aspx" />
+</head>
+<body>
+ <form id="form1" runat="server">
+ </form>
+</body>
+</html>
diff --git a/src/DotNetOpenId.TestWeb/DirectedProviderEndpoint.aspx b/src/DotNetOpenId.TestWeb/DirectedProviderEndpoint.aspx new file mode 100644 index 0000000..753f031 --- /dev/null +++ b/src/DotNetOpenId.TestWeb/DirectedProviderEndpoint.aspx @@ -0,0 +1,16 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DirectedProviderEndpoint.aspx.cs" Inherits="DirectedProviderEndpoint" %>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head runat="server">
+ <title>Untitled Page</title>
+</head>
+<body>
+ <form id="form1" runat="server">
+ <div>
+
+ </div>
+ </form>
+</body>
+</html>
diff --git a/src/DotNetOpenId.TestWeb/DirectedProviderEndpoint.aspx.cs b/src/DotNetOpenId.TestWeb/DirectedProviderEndpoint.aspx.cs new file mode 100644 index 0000000..2dae40e --- /dev/null +++ b/src/DotNetOpenId.TestWeb/DirectedProviderEndpoint.aspx.cs @@ -0,0 +1,45 @@ +using System;
+using System.Web.UI;
+using DotNetOpenId;
+using DotNetOpenId.Provider;
+
+public partial class DirectedProviderEndpoint : System.Web.UI.Page {
+ protected void Page_Load(object sender, EventArgs e) {
+ TestSupport.Scenarios scenario = (TestSupport.Scenarios)Enum.Parse(typeof(TestSupport.Scenarios), Request.QueryString["user"]);
+ UriBuilder providerEndpoint = new UriBuilder(Request.Url);
+ providerEndpoint.Query = "user=" + scenario;
+ OpenIdProvider provider = new OpenIdProvider(TestSupport.ProviderStoreContext, providerEndpoint.Uri,
+ Request.Url, Request.HttpMethod == "GET" ? Request.QueryString : Request.Form);
+ if (provider.Request != null) {
+ if (!provider.Request.IsResponseReady) {
+ var idreq = provider.Request as IAuthenticationRequest;
+ idreq.ClaimedIdentifier = new Uri(Request.Url, Page.ResolveUrl("~/DirectedIdentityEndpoint.aspx?user=" + scenario + "&version=" + ProtocolVersion.V20));
+
+ switch (scenario) {
+ case TestSupport.Scenarios.AutoApproval:
+ // immediately approve
+ idreq.IsAuthenticated = true;
+ break;
+ case TestSupport.Scenarios.AutoApprovalAddFragment:
+ idreq.SetClaimedIdentifierFragment("frag");
+ idreq.IsAuthenticated = true;
+ break;
+ case TestSupport.Scenarios.ApproveOnSetup:
+ idreq.IsAuthenticated = !idreq.Immediate;
+ break;
+ case TestSupport.Scenarios.AlwaysDeny:
+ idreq.IsAuthenticated = false;
+ break;
+ case TestSupport.Scenarios.ExtensionFullCooperation:
+ case TestSupport.Scenarios.ExtensionPartialCooperation:
+ throw new NotImplementedException();
+ //idreq.IsAuthenticated = true;
+ //break;
+ default:
+ throw new InvalidOperationException("Unrecognized scenario");
+ }
+ }
+ provider.Request.Response.Send();
+ }
+ }
+}
diff --git a/src/DotNetOpenId.TestWeb/IdentityEndpoint.aspx b/src/DotNetOpenId.TestWeb/IdentityEndpoint.aspx index a70742f..f25355a 100644 --- a/src/DotNetOpenId.TestWeb/IdentityEndpoint.aspx +++ b/src/DotNetOpenId.TestWeb/IdentityEndpoint.aspx @@ -17,7 +17,7 @@ <html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Identity page</title>
- <openid:IdentityEndpoint ID="IdentityEndpoint1" runat="server"
+ <openid:IdentityEndpoint ID="IdentityEndpoint1" runat="server" EnableViewState="false"
ProviderEndpointUrl="~/ProviderEndpoint.aspx" />
</head>
<body>
diff --git a/src/DotNetOpenId.TestWeb/OPDefault.aspx b/src/DotNetOpenId.TestWeb/OPDefault.aspx new file mode 100644 index 0000000..5ce5804 --- /dev/null +++ b/src/DotNetOpenId.TestWeb/OPDefault.aspx @@ -0,0 +1,25 @@ +<%@ Page Language="C#" %>
+
+<%@ Register Assembly="DotNetOpenId" Namespace="DotNetOpenId" TagPrefix="openid" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<script runat="server">
+ protected override void OnLoad(EventArgs e) {
+ base.OnLoad(e);
+
+ xrdsPublisher.XrdsUrl += "?user=" + Request.QueryString["user"];
+ }
+</script>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head runat="server">
+ <title>Untitled Page</title>
+ <!-- this page is the directed identity OP Identifier -->
+ <openid:XrdsPublisher runat="server" ID="xrdsPublisher" XrdsUrl="~/op_xrds.aspx" EnableViewState="false" />
+</head>
+<body>
+ <form id="form1" runat="server">
+ Test home page, used for OP discovery.
+ </form>
+</body>
+</html>
diff --git a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs index 098a812..8eab9c7 100644 --- a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs +++ b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs @@ -5,6 +5,7 @@ using DotNetOpenId.Extensions.SimpleRegistration; using SregDemandLevel = DotNetOpenId.Extensions.SimpleRegistration.DemandLevel;
using DotNetOpenId.Extensions.ProviderAuthenticationPolicy;
using System.Globalization;
+using DotNetOpenId;
public partial class ProviderEndpoint : System.Web.UI.Page {
const string nicknameTypeUri = WellKnownAttributes.Name.Alias;
@@ -101,17 +102,21 @@ public partial class ProviderEndpoint : System.Web.UI.Page { }
protected void ProviderEndpoint1_AuthenticationChallenge(object sender, DotNetOpenId.Provider.AuthenticationChallengeEventArgs e) {
- TestSupport.Scenarios scenario = (TestSupport.Scenarios)Enum.Parse(typeof(TestSupport.Scenarios),
- new Uri(e.Request.LocalIdentifier.ToString()).AbsolutePath.TrimStart('/'));
if (!e.Request.IsReturnUrlDiscoverable) {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
"return_to could not be verified using RP discovery realm {0}.", e.Request.Realm));
}
+ TestSupport.Scenarios scenario = (TestSupport.Scenarios)Enum.Parse(typeof(TestSupport.Scenarios),
+ new Uri(e.Request.LocalIdentifier).AbsolutePath.TrimStart('/'));
switch (scenario) {
case TestSupport.Scenarios.AutoApproval:
// immediately approve
e.Request.IsAuthenticated = true;
break;
+ case TestSupport.Scenarios.AutoApprovalAddFragment:
+ e.Request.SetClaimedIdentifierFragment("frag");
+ e.Request.IsAuthenticated = true;
+ break;
case TestSupport.Scenarios.ApproveOnSetup:
e.Request.IsAuthenticated = !e.Request.Immediate;
break;
diff --git a/src/DotNetOpenId.TestWeb/op_xrds.aspx b/src/DotNetOpenId.TestWeb/op_xrds.aspx new file mode 100644 index 0000000..bef77d6 --- /dev/null +++ b/src/DotNetOpenId.TestWeb/op_xrds.aspx @@ -0,0 +1,13 @@ +<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/server</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/DirectedProviderEndpoint.aspx?user=" + Request.QueryString["user"]))%></URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenId/DotNetOpenId.csproj b/src/DotNetOpenId/DotNetOpenId.csproj index 15ab091..6916d24 100644 --- a/src/DotNetOpenId/DotNetOpenId.csproj +++ b/src/DotNetOpenId/DotNetOpenId.csproj @@ -96,6 +96,7 @@ <Compile Include="RelyingParty\CheckAuthResponse.cs" />
<Compile Include="RelyingParty\ApplicationMemoryStore.cs" />
<Compile Include="RelyingParty\IProviderEndpoint.cs" />
+ <Compile Include="RelyingParty\IXrdsProviderEndpoint.cs" />
<Compile Include="RelyingParty\OpenIdMobileTextBox.cs" />
<Compile Include="RelyingParty\DirectRequest.cs" />
<Compile Include="RelyingParty\DirectResponse.cs" />
diff --git a/src/DotNetOpenId/Identifier.cs b/src/DotNetOpenId/Identifier.cs index b72502e..54e9c36 100644 --- a/src/DotNetOpenId/Identifier.cs +++ b/src/DotNetOpenId/Identifier.cs @@ -78,7 +78,7 @@ namespace DotNetOpenId { /// <returns>
/// An initialized structure containing the discovered provider endpoint information.
/// </returns>
- internal abstract ServiceEndpoint Discover();
+ internal abstract IEnumerable<ServiceEndpoint> Discover();
/// <summary>
/// Tests equality between two <see cref="Identifier"/>s.
@@ -108,5 +108,12 @@ namespace DotNetOpenId { Debug.Fail("This should be overridden in every derived class.");
return base.GetHashCode();
}
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ internal abstract Identifier TrimFragment();
}
}
diff --git a/src/DotNetOpenId/MessageEncoder.cs b/src/DotNetOpenId/MessageEncoder.cs index bbcbdf0..10ad740 100644 --- a/src/DotNetOpenId/MessageEncoder.cs +++ b/src/DotNetOpenId/MessageEncoder.cs @@ -14,6 +14,10 @@ namespace DotNetOpenId { /// </summary>
internal class MessageEncoder {
/// <summary>
+ /// The HTTP Content-Type to use in Key-Value Form responses.
+ /// </summary>
+ const string KeyValueFormContentType = "application/x-openid-kvf";
+ /// <summary>
/// The maximum allowable size for a 301 Redirect response before we send
/// a 200 OK response with a scripted form POST with the parameters instead
/// in order to ensure successfully sending a large payload to another server
@@ -49,6 +53,11 @@ namespace DotNetOpenId { Environment.NewLine, Util.ToString(message.EncodedFields));
HttpStatusCode code = (message is Exception) ?
HttpStatusCode.BadRequest : HttpStatusCode.OK;
+ // Key-Value Encoding is how response bodies are sent.
+ // Setting the content-type to something other than text/html or text/plain
+ // prevents free hosted sites like GoDaddy's from automatically appending
+ // the <script/> at the end that adds a banner, and invalidating our response.
+ headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType);
wr = new Response(code, headers, ProtocolMessages.KeyValueForm.GetBytes(message.EncodedFields), message);
break;
case EncodingType.IndirectMessage:
diff --git a/src/DotNetOpenId/Protocol.cs b/src/DotNetOpenId/Protocol.cs index f0af0ea..a9b7cf2 100644 --- a/src/DotNetOpenId/Protocol.cs +++ b/src/DotNetOpenId/Protocol.cs @@ -114,6 +114,15 @@ namespace DotNetOpenId { if (Query == null) throw new ArgumentNullException("Query");
return Query.ContainsKey(v20.openid.ns) ? v20 : v11;
}
+ /// <summary>
+ /// Attemps to detect the highest OpenID protocol version supported given a set
+ /// of XRDS Service Type URIs included for some service.
+ /// </summary>
+ internal static Protocol Detect(string[] serviceTypeURIs) {
+ if (serviceTypeURIs == null) throw new ArgumentNullException("serviceTypeURIs");
+ return Util.FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ??
+ Util.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs);
+ }
/// <summary>
/// The OpenID version that this <see cref="Protocol"/> instance describes.
@@ -266,7 +275,7 @@ namespace DotNetOpenId { /// <summary>
/// A preference order list of signature algorithms we support.
/// </summary>
- public string[] All { get { return new [] { HMAC_SHA256, HMAC_SHA1 }; } }
+ public string[] All { get { return new[] { HMAC_SHA256, HMAC_SHA1 }; } }
public string HMAC_SHA1 = "HMAC-SHA1";
public string HMAC_SHA256 = null;
}
diff --git a/src/DotNetOpenId/Provider/CheckIdRequest.cs b/src/DotNetOpenId/Provider/CheckIdRequest.cs index 464b131..314e813 100644 --- a/src/DotNetOpenId/Provider/CheckIdRequest.cs +++ b/src/DotNetOpenId/Provider/CheckIdRequest.cs @@ -131,6 +131,37 @@ namespace DotNetOpenId.Provider { }
}
}
+
+ /// <summary>
+ /// Adds an optional fragment (#fragment) portion to a URI ClaimedIdentifier.
+ /// Useful for identifier recycling.
+ /// </summary>
+ /// <param name="fragment">
+ /// Should not include the # prefix character as that will be added internally.
+ /// May be null or the empty string to clear a previously set fragment.
+ /// </param>
+ /// <remarks>
+ /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if
+ /// using directed identity, this method can be called on any URI claimed identifier.</para>
+ /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled,
+ /// this method should<i>not</i> be called for XRIs.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown when this method is called on an XRI, or on a directed identity request
+ /// before the <see cref="ClaimedIdentifier"/> property is set.</exception>
+ public void SetClaimedIdentifierFragment(string fragment) {
+ if (IsDirectedIdentity && ClaimedIdentifier == null) {
+ throw new InvalidOperationException(Strings.ClaimedIdentifierMustBeSetFirst);
+ }
+ if (ClaimedIdentifier is XriIdentifier) {
+ throw new InvalidOperationException(Strings.FragmentNotAllowedOnXRIs);
+ }
+
+ UriBuilder builder = new UriBuilder(ClaimedIdentifier);
+ builder.Fragment = fragment;
+ claimedIdentifier = builder.Uri;
+ }
+
/// <summary>
/// The URL to redirect the user agent to after the authentication attempt.
/// This must fall "under" the realm URL.
diff --git a/src/DotNetOpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenId/Provider/IAuthenticationRequest.cs index 7e0fd1c..38f4be0 100644 --- a/src/DotNetOpenId/Provider/IAuthenticationRequest.cs +++ b/src/DotNetOpenId/Provider/IAuthenticationRequest.cs @@ -62,6 +62,22 @@ namespace DotNetOpenId.Provider { /// </remarks>
Identifier ClaimedIdentifier { get; set; }
/// <summary>
+ /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier.
+ /// Useful for identifier recycling.
+ /// </summary>
+ /// <param name="fragment">
+ /// Should not include the # prefix character as that will be added internally.
+ /// May be null or the empty string to clear a previously set fragment.
+ /// </param>
+ /// <remarks>
+ /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if
+ /// using directed identity, this method can be called on any URI claimed identifier.</para>
+ /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled,
+ /// this method should<i>not</i> be called for XRIs.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">Thrown when this method is called on an XRI.</exception>
+ void SetClaimedIdentifierFragment(string fragment);
+ /// <summary>
/// Gets/sets whether the provider has determined that the
/// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user
/// and wishes to share this information with the consumer.
diff --git a/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs index 2ce5a0e..fddd397 100644 --- a/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs @@ -1,12 +1,10 @@ using System;
using System.Collections.Generic;
-using System.Text;
-using DotNetOpenId;
+using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Globalization;
using System.Web;
-using System.Diagnostics;
-using DotNetOpenId.Extensions;
namespace DotNetOpenId.RelyingParty {
/// <summary>
@@ -53,12 +51,14 @@ namespace DotNetOpenId.RelyingParty { AddCallbackArguments(DotNetOpenId.RelyingParty.Token.TokenKey, token);
}
internal static AuthenticationRequest Create(Identifier userSuppliedIdentifier,
- Realm realm, Uri returnToUrl, IRelyingPartyApplicationStore store, MessageEncoder encoder) {
+ OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IRelyingPartyApplicationStore store) {
if (userSuppliedIdentifier == null) throw new ArgumentNullException("userSuppliedIdentifier");
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
if (realm == null) throw new ArgumentNullException("realm");
+ userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment();
Logger.InfoFormat("Creating authentication request for user supplied Identifier: {0}",
- userSuppliedIdentifier);
+ userSuppliedIdentifier);
Logger.DebugFormat("Realm: {0}", realm);
Logger.DebugFormat("Return To: {0}", returnToUrl);
@@ -72,7 +72,8 @@ namespace DotNetOpenId.RelyingParty { }
}
- var endpoint = userSuppliedIdentifier.Discover();
+ var endpoints = new List<ServiceEndpoint>(userSuppliedIdentifier.Discover());
+ ServiceEndpoint endpoint = selectEndpoint(endpoints.AsReadOnly(), relyingParty, store);
if (endpoint == null)
throw new OpenIdException(Strings.OpenIdEndpointNotFound);
Logger.DebugFormat("Discovered provider endpoint: {0}", endpoint);
@@ -84,17 +85,87 @@ namespace DotNetOpenId.RelyingParty { throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.ReturnToNotUnderRealm, returnToUrl, realm));
+ string token = new Token(endpoint).Serialize(store);
+ // Retrieve the association, but don't create one, as a creation was already
+ // attempted by the selectEndpoint method.
+ Association association = store != null ? getAssociation(endpoint, store, false) : null;
+
return new AuthenticationRequest(
- new Token(endpoint).Serialize(store),
- store != null ? getAssociation(endpoint, store) : null,
- endpoint, realm, returnToUrl, encoder);
+ token, association, endpoint, realm, returnToUrl, relyingParty.Encoder);
+ }
+
+ /// <summary>
+ /// Returns a filtered and sorted list of the available OP endpoints for a discovered Identifier.
+ /// </summary>
+ private static List<ServiceEndpoint> filterAndSortEndpoints(ReadOnlyCollection<ServiceEndpoint> endpoints,
+ OpenIdRelyingParty relyingParty) {
+ if (endpoints == null) throw new ArgumentNullException("endpoints");
+ if (relyingParty == null) throw new ArgumentNullException("relyingParty");
+
+ // Filter the endpoints based on criteria given by the host web site.
+ List<IXrdsProviderEndpoint> filteredEndpoints = new List<IXrdsProviderEndpoint>(endpoints.Count);
+ var filter = relyingParty.EndpointFilter;
+ foreach (ServiceEndpoint endpoint in endpoints) {
+ if (filter == null || filter(endpoint)) {
+ filteredEndpoints.Add(endpoint);
+ }
+ }
+
+ // Sort endpoints so that the first one in the list is the most preferred one.
+ filteredEndpoints.Sort(relyingParty.EndpointOrder);
+
+ List<ServiceEndpoint> endpointList = new List<ServiceEndpoint>(filteredEndpoints.Count);
+ foreach (ServiceEndpoint endpoint in filteredEndpoints) {
+ endpointList.Add(endpoint);
+ }
+ return endpointList;
+ }
+
+ /// <summary>
+ /// Chooses which provider endpoint is the best one to use.
+ /// </summary>
+ /// <returns>The best endpoint, or null if no acceptable endpoints were found.</returns>
+ private static ServiceEndpoint selectEndpoint(ReadOnlyCollection<ServiceEndpoint> endpoints,
+ OpenIdRelyingParty relyingParty, IRelyingPartyApplicationStore store) {
+
+ List<ServiceEndpoint> filteredEndpoints = filterAndSortEndpoints(endpoints, relyingParty);
+
+ // If there are no endpoint candidates...
+ if (filteredEndpoints.Count == 0) {
+ return null;
+ }
+
+ // If we don't have an application store, we have no place to record an association to
+ // and therefore can only take our best shot at one of the endpoints.
+ if (store == null) {
+ return filteredEndpoints[0];
+ }
+
+ // Go through each endpoint until we find one that we can successfully create
+ // an association with. This is our only hint about whether an OP is up and running.
+ // The idea here is that we don't want to redirect the user to a dead OP for authentication.
+ // If the user has multiple OPs listed in his/her XRDS document, then we'll go down the list
+ // and try each one until we find one that's good.
+ foreach (ServiceEndpoint endpointCandidate in filteredEndpoints) {
+ // One weakness of this method is that an OP that's down, but with whom we already
+ // created an association in the past will still pass this "are you alive?" test.
+ Association association = getAssociation(endpointCandidate, store, true);
+ if (association != null) {
+ // We have a winner!
+ return endpointCandidate;
+ }
+ }
+
+ // Since all OPs failed to form an association with us, just return the first endpoint
+ // and hope for the best.
+ return endpoints[0];
}
- static Association getAssociation(ServiceEndpoint provider, IRelyingPartyApplicationStore store) {
+ static Association getAssociation(ServiceEndpoint provider, IRelyingPartyApplicationStore store, bool createNewAssociationIfNeeded) {
if (provider == null) throw new ArgumentNullException("provider");
if (store == null) throw new ArgumentNullException("store");
Association assoc = store.GetAssociation(provider.ProviderEndpoint);
- if (assoc == null || !assoc.HasUsefulLifeRemaining) {
+ if ((assoc == null || !assoc.HasUsefulLifeRemaining) && createNewAssociationIfNeeded) {
var req = AssociateRequest.Create(provider);
if (req.Response != null) {
// try again if we failed the first time and have a worthy second-try.
@@ -170,7 +241,7 @@ namespace DotNetOpenId.RelyingParty { qsArgs.Add(protocol.openid.assoc_handle, this.assoc.Handle);
// Add on extension arguments
- foreach(var pair in OutgoingExtensions.GetArgumentsToSend(true))
+ foreach (var pair in OutgoingExtensions.GetArgumentsToSend(true))
qsArgs.Add(pair.Key, pair.Value);
var request = new IndirectMessageRequest(this.endpoint.ProviderEndpoint, qsArgs);
@@ -212,7 +283,7 @@ namespace DotNetOpenId.RelyingParty { /// This method requires an ASP.NET HttpContext.
/// </remarks>
public void RedirectToProvider() {
- if (HttpContext.Current == null || HttpContext.Current.Response == null)
+ if (HttpContext.Current == null || HttpContext.Current.Response == null)
throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
RedirectingResponse.Send();
}
diff --git a/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs index 3ccfc97..87424fa 100644 --- a/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs +++ b/src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs @@ -74,9 +74,21 @@ namespace DotNetOpenId.RelyingParty { /// An Identifier that the end user claims to own.
/// </summary>
public Identifier ClaimedIdentifier {
+ [DebuggerStepThrough]
get { return Provider.ClaimedIdentifier; }
}
/// <summary>
+ /// Gets a user-friendly OpenID Identifier for display purposes ONLY.
+ /// </summary>
+ /// <remarks>
+ /// See <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/>.
+ /// </remarks>
+ public string FriendlyIdentifierForDisplay {
+ [DebuggerStepThrough]
+ get { return Provider.FriendlyIdentifierForDisplay; }
+ }
+
+ /// <summary>
/// The discovered endpoint information.
/// </summary>
internal ServiceEndpoint Provider { get; private set; }
@@ -169,8 +181,11 @@ namespace DotNetOpenId.RelyingParty { throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.MissingInternalQueryParameter, Token.TokenKey));
} else {
- // 2.0 OPs provide enough information to assemble the entire endpoint info
- responseEndpoint = ServiceEndpoint.ParseFromAuthResponse(query);
+ // 2.0 OPs provide enough information to assemble the entire endpoint info,
+ // except perhaps for the original user supplied identifier, which if available
+ // allows us to display a friendly XRI.
+ Identifier friendlyIdentifier = tokenEndpoint != null ? tokenEndpoint.UserSuppliedIdentifier : null;
+ responseEndpoint = ServiceEndpoint.ParseFromAuthResponse(query, friendlyIdentifier);
// If this is a solicited assertion, we'll have a token with endpoint data too,
// which we can use to more quickly confirm the validity of the claimed
// endpoint info.
@@ -269,10 +284,11 @@ namespace DotNetOpenId.RelyingParty { if (tokenEndpoint == null ||
tokenEndpoint.ClaimedIdentifier == tokenEndpoint.Protocol.ClaimedIdentifierForOPIdentifier) {
Identifier claimedIdentifier = Util.GetRequiredArg(query, responseEndpoint.Protocol.openid.claimed_id);
- ServiceEndpoint claimedEndpoint = claimedIdentifier.Discover();
- // Compare the two ServiceEndpoints to make sure they are the same.
- if (responseEndpoint != claimedEndpoint)
+ List<ServiceEndpoint> discoveredEndpoints = new List<ServiceEndpoint>(claimedIdentifier.Discover());
+ // Make sure the response endpoint matches one of the discovered endpoints.
+ if (!discoveredEndpoints.Contains(responseEndpoint)) {
throw new OpenIdException(Strings.IssuedAssertionFailsIdentifierDiscovery);
+ }
} else {
// Check that the assertion matches the service endpoint we know about.
if (responseEndpoint != tokenEndpoint)
diff --git a/src/DotNetOpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/FailedAuthenticationResponse.cs index 289760e..8954fd4 100644 --- a/src/DotNetOpenId/RelyingParty/FailedAuthenticationResponse.cs +++ b/src/DotNetOpenId/RelyingParty/FailedAuthenticationResponse.cs @@ -24,6 +24,10 @@ namespace DotNetOpenId.RelyingParty { get { return null; }
}
+ public string FriendlyIdentifierForDisplay {
+ get { return null; }
+ }
+
public AuthenticationStatus Status {
get { return AuthenticationStatus.Failed; }
}
diff --git a/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs index 382b080..26b6c03 100644 --- a/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs +++ b/src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs @@ -1,6 +1,7 @@ using System;
using System.Collections.Generic;
using System.Text;
+using System.Web;
using DotNetOpenId.Extensions;
namespace DotNetOpenId.RelyingParty {
@@ -28,10 +29,51 @@ namespace DotNetOpenId.RelyingParty { /// <returns>The extension, if it is found. Null otherwise.</returns>
IExtensionResponse GetExtension(Type extensionType);
/// <summary>
- /// An Identifier that the end user claims to own.
+ /// An Identifier that the end user claims to own. For use with user database storage and lookup.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the secure identifier that should be used for database storage and lookup.
+ /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects
+ /// user identities against spoofing and other attacks.
+ /// </para>
+ /// <para>
+ /// For user-friendly identifiers to display, use the
+ /// <see cref="FriendlyIdentifierForDisplay"/> property.
+ /// </para>
+ /// </remarks>
Identifier ClaimedIdentifier { get; }
/// <summary>
+ /// Gets a user-friendly OpenID Identifier for display purposes ONLY.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before
+ /// sending to a browser to secure against javascript injection attacks.
+ /// </para>
+ /// <para>
+ /// This property retains some aspects of the user-supplied identifier that get lost
+ /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied
+ /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD).
+ /// For display purposes, such as text on a web page that says "You're logged in as ...",
+ /// this property serves to provide the =Arnott string, or whatever else is the most friendly
+ /// string close to what the user originally typed in.
+ /// </para>
+ /// <para>
+ /// If the user-supplied identifier is a URI, this property will be the URI after all
+ /// redirects, and with the protocol and fragment trimmed off.
+ /// If the user-supplied identifier is an XRI, this property will be the original XRI.
+ /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com),
+ /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI.
+ /// </para>
+ /// <para>
+ /// It is <b>very</b> important that this property <i>never</i> be used for database storage
+ /// or lookup to avoid identity spoofing and other security risks. For database storage
+ /// and lookup please use the <see cref="ClaimedIdentifier"/> property.
+ /// </para>
+ /// </remarks>
+ string FriendlyIdentifierForDisplay { get; }
+ /// <summary>
/// The detailed success or failure status of the authentication attempt.
/// </summary>
AuthenticationStatus Status { get; }
diff --git a/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs b/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs index 322158f..6ba0704 100644 --- a/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs +++ b/src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs @@ -1,6 +1,4 @@ using System;
-using System.Collections.Generic;
-using System.Text;
namespace DotNetOpenId.RelyingParty {
/// <summary>
diff --git a/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs b/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs new file mode 100644 index 0000000..bc25747 --- /dev/null +++ b/src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.CodeAnalysis;
+
+namespace DotNetOpenId.RelyingParty {
+ /// <summary>
+ /// An <see cref="IProviderEndpoint"/> interface with additional members for use
+ /// in sorting for most preferred endpoint.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds")]
+ public interface IXrdsProviderEndpoint : IProviderEndpoint {
+ /// <summary>
+ /// Gets the priority associated with this service that may have been given
+ /// in the XRDS document.
+ /// </summary>
+ int? ServicePriority { get; }
+ /// <summary>
+ /// Gets the priority associated with the service endpoint URL.
+ /// </summary>
+ /// <remarks>
+ /// When sorting by priority, this property should be considered second after
+ /// <see cref="ServicePriority"/>.
+ /// </remarks>
+ int? UriPriority { get; }
+ }
+}
diff --git a/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs index 1729de2..1e727a8 100644 --- a/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs @@ -1,6 +1,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Diagnostics;
using System.Web;
@@ -86,7 +87,7 @@ namespace DotNetOpenId.RelyingParty { IRelyingPartyApplicationStore store;
Uri request;
IDictionary<string, string> query;
- MessageEncoder encoder;
+ MessageEncoder encoder = new MessageEncoder();
/// <summary>
/// Constructs an OpenId consumer that uses the current HttpContext request
@@ -125,6 +126,7 @@ namespace DotNetOpenId.RelyingParty { /// which must therefore share the nonce information in the application
/// state store in order to stop the intruder.
/// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIdRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, NameValueCollection query) :
this(store, requestUrl, Util.NameValueCollectionToDictionary(query)) {
}
@@ -138,7 +140,6 @@ namespace DotNetOpenId.RelyingParty { this.request = requestUrl;
this.query = query;
}
- this.encoder = new MessageEncoder();
}
/// <summary>
@@ -162,7 +163,7 @@ namespace DotNetOpenId.RelyingParty { /// send to the user agent to initiate the authentication.
/// </returns>
public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
- return AuthenticationRequest.Create(userSuppliedIdentifier, realm, returnToUrl, store, encoder);
+ return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, store);
}
/// <summary>
@@ -268,7 +269,7 @@ namespace DotNetOpenId.RelyingParty { /// Gets the result of a user agent's visit to his OpenId provider in an
/// authentication attempt. Null if no response is available.
/// </summary>
- [DebuggerBrowsable(DebuggerBrowsableState.Never)] // getter does work
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)] // getter does lots of processing, so avoid debugger calling it.
public IAuthenticationResponse Response {
get {
if (response == null && isAuthenticationResponseReady) {
@@ -282,11 +283,90 @@ namespace DotNetOpenId.RelyingParty { }
}
+ /// <summary>
+ /// The message encoder to use.
+ /// </summary>
+ internal MessageEncoder Encoder { get { return encoder; } }
+
+ private Comparison<IXrdsProviderEndpoint> endpointOrder = DefaultEndpointOrder;
+ /// <summary>
+ /// Gets/sets the ordering routine that will determine which XRDS
+ /// Service element to try first
+ /// </summary>
+ /// <remarks>
+ /// This may never be null. To reset to default behavior this property
+ /// can be set to the value of <see cref="DefaultEndpointOrder"/>.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public Comparison<IXrdsProviderEndpoint> EndpointOrder {
+ get { return endpointOrder; }
+ set {
+ if (value == null) throw new ArgumentNullException("value");
+ endpointOrder = value;
+ }
+ }
+ /// <summary>
+ /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority
+ /// attribute to determine order.
+ /// </summary>
+ /// <remarks>
+ /// Endpoints lacking any priority value are sorted to the end of the list.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static Comparison<IXrdsProviderEndpoint> DefaultEndpointOrder {
+ get {
+ // Sort first by Service/@priority, then by Service/Uri/@priority
+ return (se1, se2) => {
+ if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) {
+ int result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
+ if (result != 0) return result;
+ if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
+ return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
+ } else if (se1.UriPriority.HasValue) {
+ return -1;
+ } else if (se2.UriPriority.HasValue) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ if (se1.ServicePriority.HasValue) {
+ return -1;
+ } else if (se2.ServicePriority.HasValue) {
+ return 1;
+ } else {
+ // neither service defines a priority, so base ordering by uri priority.
+ if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
+ return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
+ } else if (se1.UriPriority.HasValue) {
+ return -1;
+ } else if (se2.UriPriority.HasValue) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ };
+ }
+ }
+
+ /// <summary>
+ /// Provides a way to optionally filter the providers that may be used in authenticating a user.
+ /// </summary>
+ /// <remarks>
+ /// If provided, the delegate should return true to accept an endpoint, and false to reject it.
+ /// If null, all identity providers will be accepted. This is the default.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public EndpointSelector EndpointFilter { get; set; }
+
const string associationStoreKey = "DotNetOpenId.RelyingParty.RelyingParty.AssociationStore";
/// <summary>
/// The standard state storage mechanism that uses ASP.NET's HttpApplication state dictionary
/// to store associations and nonces.
/// </summary>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
public static IRelyingPartyApplicationStore HttpApplicationStore {
get {
HttpContext context = HttpContext.Current;
@@ -307,4 +387,14 @@ namespace DotNetOpenId.RelyingParty { }
}
}
+
+ /// <summary>
+ /// A delegate that decides whether a given OpenID Provider endpoint may be
+ /// considered for authenticating a user.
+ /// </summary>
+ /// <returns>
+ /// True if the endpoint should be considered.
+ /// False to remove it from the pool of acceptable providers.
+ /// </returns>
+ public delegate bool EndpointSelector(IXrdsProviderEndpoint endpoint);
}
diff --git a/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs index 2bd136a..b4b55c8 100644 --- a/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs @@ -815,7 +815,6 @@ namespace DotNetOpenId.RelyingParty if (response == null) throw new ArgumentNullException("response");
Response = response;
ClaimedIdentifier = response.ClaimedIdentifier;
- ProfileFields = response.GetExtension<ClaimsResponse>();
}
/// <summary>
/// Cancels the OpenID authentication and/or login process.
@@ -841,10 +840,5 @@ namespace DotNetOpenId.RelyingParty /// Gets the details of the OpenID authentication response.
/// </summary>
public IAuthenticationResponse Response { get; private set; }
- /// <summary>
- /// Gets the simple registration (sreg) extension fields given
- /// by the provider, if any.
- /// </summary>
- public ClaimsResponse ProfileFields { get; private set; }
}
}
diff --git a/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs b/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs index cbc5c07..f6946bf 100644 --- a/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs +++ b/src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs @@ -15,7 +15,7 @@ namespace DotNetOpenId.RelyingParty { /// Represents information discovered about a user-supplied Identifier.
/// </summary>
[DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, ProviderEndpoint: {ProviderEndpoint}, OpenId: {Protocol.Version}")]
- internal class ServiceEndpoint : IProviderEndpoint {
+ internal class ServiceEndpoint : IXrdsProviderEndpoint {
/// <summary>
/// The URL which accepts OpenID Authentication protocol messages.
/// </summary>
@@ -30,6 +30,7 @@ namespace DotNetOpenId.RelyingParty { /// An Identifier for an OpenID Provider.
/// </summary>
public Identifier ProviderIdentifier { get; private set; }
+ */
/// <summary>
/// An Identifier that was presented by the end user to the Relying Party,
/// or selected by the user at the OpenID Provider.
@@ -38,7 +39,7 @@ namespace DotNetOpenId.RelyingParty { /// is used, the OP may then assist the end user in selecting an Identifier
/// to share with the Relying Party.
/// </summary>
- public Identifier UserSuppliedIdentifier { get; private set; }*/
+ public Identifier UserSuppliedIdentifier { get; private set; }
/// <summary>
/// The Identifier that the end user claims to own.
/// </summary>
@@ -49,30 +50,98 @@ namespace DotNetOpenId.RelyingParty { /// control.
/// </summary>
public Identifier ProviderLocalIdentifier { get; private set; }
+ string friendlyIdentifierForDisplay;
+ /// <summary>
+ /// Supports the <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/> property.
+ /// </summary>
+ public string FriendlyIdentifierForDisplay {
+ get {
+ if (friendlyIdentifierForDisplay == null) {
+ XriIdentifier xri = ClaimedIdentifier as XriIdentifier;
+ UriIdentifier uri = ClaimedIdentifier as UriIdentifier;
+ if (xri != null) {
+ if (UserSuppliedIdentifier == null || String.Equals(UserSuppliedIdentifier, ClaimedIdentifier, StringComparison.OrdinalIgnoreCase)) {
+ friendlyIdentifierForDisplay = ClaimedIdentifier;
+ } else {
+ friendlyIdentifierForDisplay = String.Format(CultureInfo.CurrentCulture, "{0} ({1})",
+ ClaimedIdentifier, UserSuppliedIdentifier);
+ }
+ } else if (uri != null) {
+ string displayUri = uri.Uri.Authority + uri.Uri.PathAndQuery;
+ displayUri = displayUri.TrimEnd('/');
+ // Multi-byte unicode characters get encoded by the Uri class for transit.
+ // Since this is for display purposes, we want to reverse this and display a readable
+ // representation of these foreign characters.
+ friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri);
+ } else {
+ Debug.Fail("Doh! We never should have reached here.");
+ friendlyIdentifierForDisplay = ClaimedIdentifier;
+ }
+ }
+ return friendlyIdentifierForDisplay;
+ }
+ }
/// <summary>
/// Gets the list of services available at this OP Endpoint for the
/// claimed Identifier.
/// </summary>
public string[] ProviderSupportedServiceTypeUris { get; private set; }
- internal ServiceEndpoint(Identifier claimedIdentifier, Uri providerEndpoint,
- Identifier providerLocalIdentifier, string[] providerSupportedServiceTypeUris) {
+ ServiceEndpoint(Identifier claimedIdentifier, Identifier userSuppliedIdentifier,
+ Uri providerEndpoint, Identifier providerLocalIdentifier,
+ string[] providerSupportedServiceTypeUris, int? servicePriority, int? uriPriority) {
if (claimedIdentifier == null) throw new ArgumentNullException("claimedIdentifier");
if (providerEndpoint == null) throw new ArgumentNullException("providerEndpoint");
if (providerSupportedServiceTypeUris == null) throw new ArgumentNullException("providerSupportedServiceTypeUris");
ClaimedIdentifier = claimedIdentifier;
+ UserSuppliedIdentifier = userSuppliedIdentifier;
ProviderEndpoint = providerEndpoint;
ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier;
ProviderSupportedServiceTypeUris = providerSupportedServiceTypeUris;
+ this.servicePriority = servicePriority;
+ this.uriPriority = uriPriority;
}
- ServiceEndpoint(Identifier claimedIdentifier, Uri providerEndpoint,
- Identifier providerLocalIdentifier, Protocol protocol) {
+ /// <summary>
+ /// Used for deserializing <see cref="ServiceEndpoint"/> from authentication responses.
+ /// </summary>
+ ServiceEndpoint(Identifier claimedIdentifier, Identifier userSuppliedIdentifier,
+ Uri providerEndpoint, Identifier providerLocalIdentifier, Protocol protocol) {
ClaimedIdentifier = claimedIdentifier;
+ UserSuppliedIdentifier = userSuppliedIdentifier;
ProviderEndpoint = providerEndpoint;
ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier;
this.protocol = protocol;
}
+ internal static ServiceEndpoint CreateForProviderIdentifier(
+ Identifier providerIdentifier, Uri providerEndpoint,
+ string[] providerSupportedServiceTypeUris, int? servicePriority, int? uriPriority) {
+
+ Protocol protocol = Protocol.Detect(providerSupportedServiceTypeUris);
+
+ return new ServiceEndpoint(protocol.ClaimedIdentifierForOPIdentifier, providerIdentifier,
+ providerEndpoint, protocol.ClaimedIdentifierForOPIdentifier,
+ providerSupportedServiceTypeUris, servicePriority, uriPriority);
+ }
+
+ internal static ServiceEndpoint CreateForClaimedIdentifier(
+ Identifier claimedIdentifier, Identifier providerLocalIdentifier,
+ Uri providerEndpoint,
+ string[] providerSupportedServiceTypeUris, int? servicePriority, int? uriPriority) {
+
+ return CreateForClaimedIdentifier(claimedIdentifier, null, providerLocalIdentifier,
+ providerEndpoint, providerSupportedServiceTypeUris, servicePriority, uriPriority);
+ }
+
+ internal static ServiceEndpoint CreateForClaimedIdentifier(
+ Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier,
+ Uri providerEndpoint,
+ string[] providerSupportedServiceTypeUris, int? servicePriority, int? uriPriority) {
+
+ return new ServiceEndpoint(claimedIdentifier, userSuppliedIdentifier, providerEndpoint,
+ providerLocalIdentifier, providerSupportedServiceTypeUris, servicePriority, uriPriority);
+ }
+
Protocol protocol;
/// <summary>
/// Gets the OpenID protocol used by the Provider.
@@ -80,9 +149,7 @@ namespace DotNetOpenId.RelyingParty { public Protocol Protocol {
get {
if (protocol == null) {
- protocol =
- Util.FindBestVersion(p => p.OPIdentifierServiceTypeURI, ProviderSupportedServiceTypeUris) ??
- Util.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, ProviderSupportedServiceTypeUris);
+ protocol = Protocol.Detect(ProviderSupportedServiceTypeUris);
}
if (protocol != null) return protocol;
throw new InvalidOperationException("Unable to determine the version of OpenID the Provider supports.");
@@ -137,8 +204,10 @@ namespace DotNetOpenId.RelyingParty { internal void Serialize(TextWriter writer) {
writer.WriteLine(ClaimedIdentifier);
writer.WriteLine(ProviderLocalIdentifier);
+ writer.WriteLine(UserSuppliedIdentifier);
writer.WriteLine(ProviderEndpoint);
writer.WriteLine(Protocol.Version);
+ // No reason to serialize priority. We only needed priority to decide whether to use this endpoint.
}
/// <summary>
@@ -153,16 +222,19 @@ namespace DotNetOpenId.RelyingParty { internal static ServiceEndpoint Deserialize(TextReader reader) {
var claimedIdentifier = Identifier.Parse(reader.ReadLine());
var providerLocalIdentifier = Identifier.Parse(reader.ReadLine());
+ string userSuppliedIdentifier = reader.ReadLine();
+ if (userSuppliedIdentifier.Length == 0) userSuppliedIdentifier = null;
var providerEndpoint = new Uri(reader.ReadLine());
var protocol = Util.FindBestVersion(p => p.Version, new[] { new Version(reader.ReadLine()) });
- return new ServiceEndpoint(claimedIdentifier, providerEndpoint,
- providerLocalIdentifier, protocol);
+ return new ServiceEndpoint(claimedIdentifier, userSuppliedIdentifier,
+ providerEndpoint, providerLocalIdentifier, protocol);
}
- internal static ServiceEndpoint ParseFromAuthResponse(IDictionary<string, string> query) {
+ internal static ServiceEndpoint ParseFromAuthResponse(IDictionary<string, string> query, Identifier userSuppliedIdentifier) {
Protocol protocol = Protocol.Detect(query);
Debug.Assert(protocol.openid.op_endpoint != null, "This method should only be called in OpenID 2.0 contexts.");
return new ServiceEndpoint(
Util.GetRequiredArg(query, protocol.openid.claimed_id),
+ userSuppliedIdentifier,
new Uri(Util.GetRequiredArg(query, protocol.openid.op_endpoint)),
Util.GetRequiredArg(query, protocol.openid.identity),
protocol);
@@ -180,6 +252,7 @@ namespace DotNetOpenId.RelyingParty { ServiceEndpoint other = obj as ServiceEndpoint;
if (other == null) return false;
// We specifically do not check our ProviderSupportedServiceTypeUris array
+ // or the priority field
// as that is not persisted in our tokens, and it is not part of the
// important assertion validation that is part of the spec.
return
@@ -194,5 +267,25 @@ namespace DotNetOpenId.RelyingParty { public override string ToString() {
return ProviderEndpoint.AbsoluteUri;
}
+
+ #region IXrdsProviderEndpoint Members
+
+ private int? servicePriority;
+ /// <summary>
+ /// Gets the priority associated with this service that may have been given
+ /// in the XRDS document.
+ /// </summary>
+ int? IXrdsProviderEndpoint.ServicePriority {
+ get { return servicePriority; }
+ }
+ private int? uriPriority;
+ /// <summary>
+ /// Gets the priority associated with the service endpoint URL.
+ /// </summary>
+ int? IXrdsProviderEndpoint.UriPriority {
+ get { return uriPriority; }
+ }
+
+ #endregion
}
}
\ No newline at end of file diff --git a/src/DotNetOpenId/RelyingParty/Token.cs b/src/DotNetOpenId/RelyingParty/Token.cs index 172f5bb..c546cb1 100644 --- a/src/DotNetOpenId/RelyingParty/Token.cs +++ b/src/DotNetOpenId/RelyingParty/Token.cs @@ -161,7 +161,16 @@ namespace DotNetOpenId.RelyingParty { /// verification, this is the only alternative.
/// </remarks>
static void verifyEndpointByDiscovery(ServiceEndpoint endpoint) {
- if (!endpoint.Equals(endpoint.ClaimedIdentifier.Discover())) {
+ // If the user entered an OP Identifier then the ClaimedIdentifier will be the special
+ // identifier that we can't perform discovery on. We need to be careful about that.
+ Identifier identifierToDiscover;
+ if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) {
+ identifierToDiscover = endpoint.UserSuppliedIdentifier;
+ } else {
+ identifierToDiscover = endpoint.ClaimedIdentifier;
+ }
+ var discoveredEndpoints = new List<ServiceEndpoint>(identifierToDiscover.Discover());
+ if (!discoveredEndpoints.Contains(endpoint)) {
throw new OpenIdException(Strings.InvalidSignature);
}
}
diff --git a/src/DotNetOpenId/Response.cs b/src/DotNetOpenId/Response.cs index d4469ec..112aa4b 100644 --- a/src/DotNetOpenId/Response.cs +++ b/src/DotNetOpenId/Response.cs @@ -42,8 +42,7 @@ namespace DotNetOpenId { if (HttpContext.Current == null) throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = (int)Code;
- foreach (string headerName in Headers)
- HttpContext.Current.Response.AddHeader(headerName, Headers[headerName]);
+ Util.ApplyHeadersToResponse(Headers, HttpContext.Current.Response);
if (Body != null && Body.Length > 0) {
HttpContext.Current.Response.OutputStream.Write(Body, 0, Body.Length);
HttpContext.Current.Response.OutputStream.Flush();
diff --git a/src/DotNetOpenId/Strings.Designer.cs b/src/DotNetOpenId/Strings.Designer.cs index 8a500ef..8382b9b 100644 --- a/src/DotNetOpenId/Strings.Designer.cs +++ b/src/DotNetOpenId/Strings.Designer.cs @@ -88,6 +88,15 @@ namespace DotNetOpenId { }
/// <summary>
+ /// Looks up a localized string similar to The ClaimedIdentifier property must be set first..
+ /// </summary>
+ internal static string ClaimedIdentifierMustBeSetFirst {
+ get {
+ return ResourceManager.GetString("ClaimedIdentifierMustBeSetFirst", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to An authentication request has already been created using CreateRequest()..
/// </summary>
internal static string CreateRequestAlreadyCalled {
@@ -160,6 +169,15 @@ namespace DotNetOpenId { }
/// <summary>
+ /// Looks up a localized string similar to Fragment segments do not apply to XRI identifiers..
+ /// </summary>
+ internal static string FragmentNotAllowedOnXRIs {
+ get {
+ return ResourceManager.GetString("FragmentNotAllowedOnXRIs", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to No current ASP.NET HttpContext was detected. Use an overload that does not require one..
/// </summary>
internal static string HttpContextRequiredForThisOverload {
diff --git a/src/DotNetOpenId/Strings.resx b/src/DotNetOpenId/Strings.resx index 6455819..53b3d4a 100644 --- a/src/DotNetOpenId/Strings.resx +++ b/src/DotNetOpenId/Strings.resx @@ -126,6 +126,9 @@ <data name="BadAssociationPrivateData" xml:space="preserve">
<value>The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted.</value>
</data>
+ <data name="ClaimedIdentifierMustBeSetFirst" xml:space="preserve">
+ <value>The ClaimedIdentifier property must be set first.</value>
+ </data>
<data name="CreateRequestAlreadyCalled" xml:space="preserve">
<value>An authentication request has already been created using CreateRequest().</value>
</data>
@@ -150,6 +153,9 @@ <data name="FieldMustBeSigned" xml:space="preserve">
<value>The OpenID parameter '{0}' must be signed by the OpenID Provider, but was not.</value>
</data>
+ <data name="FragmentNotAllowedOnXRIs" xml:space="preserve">
+ <value>Fragment segments do not apply to XRI identifiers.</value>
+ </data>
<data name="HttpContextRequiredForThisOverload" xml:space="preserve">
<value>No current ASP.NET HttpContext was detected. Use an overload that does not require one.</value>
</data>
diff --git a/src/DotNetOpenId/UriIdentifier.cs b/src/DotNetOpenId/UriIdentifier.cs index 006e576..6371a59 100644 --- a/src/DotNetOpenId/UriIdentifier.cs +++ b/src/DotNetOpenId/UriIdentifier.cs @@ -1,12 +1,10 @@ using System;
using System.Collections.Generic;
-using System.Text;
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using System.Web.UI.HtmlControls;
using DotNetOpenId.RelyingParty;
using DotNetOpenId.Yadis;
-using System.Collections.Specialized;
-using System.Web.UI.HtmlControls;
-using System.Text.RegularExpressions;
-using System.Diagnostics;
namespace DotNetOpenId {
class UriIdentifier : Identifier {
@@ -79,7 +77,6 @@ namespace DotNetOpenId { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
static bool TryCanonicalize(UriBuilder uriBuilder, out Uri canonicalUri) {
uriBuilder.Host = uriBuilder.Host.ToLowerInvariant();
- uriBuilder.Fragment = null;
canonicalUri = uriBuilder.Uri;
return true;
}
@@ -146,23 +143,39 @@ namespace DotNetOpenId { // Choose the TypeURI to match the OpenID version detected.
string[] typeURIs = { discoveredProtocol.ClaimedIdentifierServiceTypeURI };
- return new ServiceEndpoint(claimedIdentifier, providerEndpoint,
- providerLocalIdentifier, typeURIs);
+ return ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, providerLocalIdentifier,
+ providerEndpoint, typeURIs, (int?)null, (int?)null);
}
- internal override ServiceEndpoint Discover() {
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
// Attempt YADIS discovery
DiscoveryResult yadisResult = Yadis.Yadis.Discover(this);
if (yadisResult != null) {
if (yadisResult.IsXrds) {
XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText);
- ServiceEndpoint ep = xrds.CreateServiceEndpoint(yadisResult.NormalizedUri);
- if (ep != null) return ep;
+ endpoints.AddRange(xrds.CreateServiceEndpoints(yadisResult.NormalizedUri));
}
// Failing YADIS discovery of an XRDS document, we try HTML discovery.
- return DiscoverFromHtml(yadisResult.NormalizedUri, yadisResult.ResponseText);
+ if (endpoints.Count == 0) {
+ ServiceEndpoint ep = DiscoverFromHtml(yadisResult.NormalizedUri, yadisResult.ResponseText);
+ if (ep != null) {
+ endpoints.Add(ep);
+ }
+ }
}
- return null;
+ return endpoints;
+ }
+
+ internal override Identifier TrimFragment() {
+ // If there is no fragment, we have no need to rebuild the Identifier.
+ if (Uri.Fragment == null || Uri.Fragment.Length == 0)
+ return this;
+
+ // Strip the fragment.
+ UriBuilder builder = new UriBuilder(Uri);
+ builder.Fragment = null;
+ return builder.Uri;
}
public override bool Equals(object obj) {
@@ -176,6 +189,5 @@ namespace DotNetOpenId { public override string ToString() {
return Uri.AbsoluteUri;
}
-
}
}
diff --git a/src/DotNetOpenId/Util.cs b/src/DotNetOpenId/Util.cs index 61986c6..1eb7c5f 100644 --- a/src/DotNetOpenId/Util.cs +++ b/src/DotNetOpenId/Util.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
+using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
@@ -148,6 +149,22 @@ namespace DotNetOpenId { // session, but not the URL rewriting problem.
}
+ public static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) {
+ if (headers == null) throw new ArgumentNullException("headers");
+ if (response == null) throw new ArgumentNullException("response");
+ foreach (string headerName in headers) {
+ switch (headerName) {
+ case "Content-Type":
+ response.ContentType = headers[HttpResponseHeader.ContentType];
+ break;
+ // Add more special cases here as necessary.
+ default:
+ response.AddHeader(headerName, headers[headerName]);
+ break;
+ }
+ }
+ }
+
public static string GetRequiredArg(IDictionary<string, string> query, string key) {
if (query == null) throw new ArgumentNullException("query");
if (key == null) throw new ArgumentNullException("key");
diff --git a/src/DotNetOpenId/XriIdentifier.cs b/src/DotNetOpenId/XriIdentifier.cs index d6e7830..b412e79 100644 --- a/src/DotNetOpenId/XriIdentifier.cs +++ b/src/DotNetOpenId/XriIdentifier.cs @@ -74,8 +74,20 @@ namespace DotNetOpenId { return new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream));
}
- internal override ServiceEndpoint Discover() {
- return downloadXrds().CreateServiceEndpoint(this);
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ return downloadXrds().CreateServiceEndpoints(this, this);
+ }
+
+ /// <summary>
+ /// Performs discovery on THIS identifier, but generates <see cref="ServiceEndpoint"/>
+ /// instances that treat another given identifier as the user-supplied identifier.
+ /// </summary>
+ internal IEnumerable<ServiceEndpoint> Discover(XriIdentifier userSuppliedIdentifier) {
+ return downloadXrds().CreateServiceEndpoints(this, userSuppliedIdentifier);
+ }
+
+ internal override Identifier TrimFragment() {
+ return this;
}
public override bool Equals(object obj) {
diff --git a/src/DotNetOpenId/Yadis/XrdsDocument.cs b/src/DotNetOpenId/Yadis/XrdsDocument.cs index 67bec24..d3510e5 100644 --- a/src/DotNetOpenId/Yadis/XrdsDocument.cs +++ b/src/DotNetOpenId/Yadis/XrdsDocument.cs @@ -1,11 +1,10 @@ -using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.XPath;
-using System.Collections.Generic;
-using DotNetOpenId.RelyingParty;
using DotNetOpenId.Provider;
-using System.Diagnostics;
+using DotNetOpenId.RelyingParty;
namespace DotNetOpenId.Yadis {
class XrdsDocument : XrdsNode {
@@ -31,63 +30,91 @@ namespace DotNetOpenId.Yadis { }
} else {
XPathNavigator node = Node.SelectSingleNode("/xrd:XRD", XmlNamespaceResolver);
- yield return new XrdElement(node, this);
+ if (node != null) {
+ yield return new XrdElement(node, this);
+ }
}
}
}
- internal ServiceEndpoint CreateServiceEndpoint(UriIdentifier claimedIdentifier) {
- return createServiceEndpoint(claimedIdentifier);
+ internal IEnumerable<ServiceEndpoint> CreateServiceEndpoints(UriIdentifier claimedIdentifier) {
+ List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
+ endpoints.AddRange(generateOPIdentifierServiceEndpoints(claimedIdentifier));
+ // If any OP Identifier service elements were found, we must not proceed
+ // to return any Claimed Identifier services.
+ if (endpoints.Count == 0) {
+ endpoints.AddRange(generateClaimedIdentifierServiceEndpoints(claimedIdentifier));
+ }
+ return endpoints;
}
- internal ServiceEndpoint CreateServiceEndpoint(XriIdentifier userSuppliedIdentifier) {
- return createServiceEndpoint(userSuppliedIdentifier);
+ internal IEnumerable<ServiceEndpoint> CreateServiceEndpoints(XriIdentifier discoveredIdentifier, XriIdentifier userSuppliedIdentifier) {
+ List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
+ endpoints.AddRange(generateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
+ // If any OP Identifier service elements were found, we must not proceed
+ // to return any Claimed Identifier services.
+ if (endpoints.Count == 0) {
+ endpoints.AddRange(generateClaimedIdentifierServiceEndpoints(discoveredIdentifier, userSuppliedIdentifier));
+ }
+ return endpoints;
}
- const bool performCIDVerification = true;
-
- ServiceEndpoint createServiceEndpoint(Identifier userSuppliedOrClaimedIdentifier) {
- // First search for OP Identifier service elements
+ IEnumerable<ServiceEndpoint> generateOPIdentifierServiceEndpoints(Identifier opIdentifier) {
foreach (var service in findOPIdentifierServices()) {
foreach (var uri in service.UriElements) {
var protocol = Util.FindBestVersion(p => p.OPIdentifierServiceTypeURI, service.TypeElementUris);
- return new ServiceEndpoint(protocol.ClaimedIdentifierForOPIdentifier, uri.Uri,
- protocol.ClaimedIdentifierForOPIdentifier, service.TypeElementUris);
+ yield return ServiceEndpoint.CreateForProviderIdentifier(
+ opIdentifier, uri.Uri, service.TypeElementUris,
+ service.Priority, uri.Priority);
+ }
+ }
+ }
+
+ IEnumerable<ServiceEndpoint> generateClaimedIdentifierServiceEndpoints(UriIdentifier claimedIdentifier) {
+ foreach (var service in findClaimedIdentifierServices()) {
+ foreach (var uri in service.UriElements) {
+ yield return ServiceEndpoint.CreateForClaimedIdentifier(
+ claimedIdentifier, service.ProviderLocalIdentifier,
+ uri.Uri, service.TypeElementUris, service.Priority, uri.Priority);
}
}
- // Since we could not find an OP Identifier service element,
- // search for a Claimed Identifier element.
+ }
+
+ IEnumerable<ServiceEndpoint> generateClaimedIdentifierServiceEndpoints(XriIdentifier claimedIdentifier, XriIdentifier userSuppliedIdentifier) {
foreach (var service in findClaimedIdentifierServices()) {
foreach (var uri in service.UriElements) {
// spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution
- if (userSuppliedOrClaimedIdentifier is XriIdentifier) {
- if (service.Xrd.CanonicalID == null) {
- Logger.WarnFormat(Strings.MissingCanonicalIDElement, userSuppliedOrClaimedIdentifier);
- return null;
- }
- // In the case of XRI names, the ClaimedId is actually the CanonicalID.
- // Per http://dev.inames.net/wiki/XRI_CanonicalID_Verification as of 6/20/08,
- // we need to perform CanonicalId verification when using xri.net as our proxy resolver
- // to protect ourselves against a security vulnerability.
- // We do this by asking the proxy to resolve again, based on the CanonicalId that we
- // just got from the XRI i-name. We SHOULD get the same document back, but in case
- // of the attack it would be a different document, and the second document would be
- // the reliable one.
- if (performCIDVerification && userSuppliedOrClaimedIdentifier != service.Xrd.CanonicalID) {
- Logger.InfoFormat("Performing XRI CanonicalID verification on user supplied identifier {0}, canonical id {1}.", userSuppliedOrClaimedIdentifier, service.Xrd.CanonicalID);
- Identifier canonicalId = service.Xrd.CanonicalID;
- return canonicalId.Discover();
- } else {
- userSuppliedOrClaimedIdentifier = service.Xrd.CanonicalID;
+ if (service.Xrd.CanonicalID == null) {
+ Logger.WarnFormat(Strings.MissingCanonicalIDElement, userSuppliedIdentifier);
+ break; // skip on to next service
+ }
+ // In the case of XRI names, the ClaimedId is actually the CanonicalID.
+ // Per http://dev.inames.net/wiki/XRI_CanonicalID_Verification as of 6/20/08,
+ // we need to perform CanonicalId verification when using xri.net as our proxy resolver
+ // to protect ourselves against a security vulnerability.
+ // We do this by asking the proxy to resolve again, based on the CanonicalId that we
+ // just got from the XRI i-name. We SHOULD get the same document back, but in case
+ // of the attack it would be a different document, and the second document would be
+ // the reliable one.
+ if (performCIDVerification && claimedIdentifier != service.Xrd.CanonicalID) {
+ Logger.InfoFormat("Performing XRI CanonicalID verification on user supplied identifier {0}, canonical id {1}.", userSuppliedIdentifier, service.Xrd.CanonicalID);
+ XriIdentifier canonicalId = new XriIdentifier(service.Xrd.CanonicalID);
+ foreach (var endpoint in canonicalId.Discover(userSuppliedIdentifier)) {
+ yield return endpoint;
}
+ yield break;
+ } else {
+ claimedIdentifier = new XriIdentifier(service.Xrd.CanonicalID);
}
- return new ServiceEndpoint(userSuppliedOrClaimedIdentifier, uri.Uri,
- service.ProviderLocalIdentifier, service.TypeElementUris);
+ yield return ServiceEndpoint.CreateForClaimedIdentifier(
+ claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier,
+ uri.Uri, service.TypeElementUris, service.Priority, uri.Priority);
}
}
- return null;
}
+ const bool performCIDVerification = true;
+
internal IEnumerable<RelyingPartyReceivingEndpoint> FindRelyingPartyReceivingEndpoints() {
foreach (var service in findReturnToServices()) {
foreach (var uri in service.UriElements) {
@@ -118,7 +145,7 @@ namespace DotNetOpenId.Yadis { IEnumerable<ServiceElement> findReturnToServices() {
foreach (var xrd in XrdElements) {
- foreach( var service in xrd.OpenIdRelyingPartyReturnToServices) {
+ foreach (var service in xrd.OpenIdRelyingPartyReturnToServices) {
yield return service;
}
}
diff --git a/src/DotNetOpenId/Yadis/XrdsNode.cs b/src/DotNetOpenId/Yadis/XrdsNode.cs index c3e8f3f..5ad5379 100644 --- a/src/DotNetOpenId/Yadis/XrdsNode.cs +++ b/src/DotNetOpenId/Yadis/XrdsNode.cs @@ -10,11 +10,22 @@ namespace DotNetOpenId.Yadis { internal const string XrdsNamespace = "xri://$xrds";
protected XrdsNode(XPathNavigator node, XrdsNode parentNode) {
+ if (node == null) {
+ throw new ArgumentNullException("node");
+ }
+ if (parentNode == null) {
+ throw new ArgumentNullException("parentNode");
+ }
+
Node = node;
ParentNode = parentNode;
XmlNamespaceResolver = ParentNode.XmlNamespaceResolver;
}
protected XrdsNode(XPathNavigator document) {
+ if (document == null) {
+ throw new ArgumentNullException("document");
+ }
+
Node = document;
XmlNamespaceResolver = new XmlNamespaceManager(document.NameTable);
}
diff --git a/src/DotNetOpenId/Yadis/Yadis.cs b/src/DotNetOpenId/Yadis/Yadis.cs index 1ae477d..ac0d3da 100644 --- a/src/DotNetOpenId/Yadis/Yadis.cs +++ b/src/DotNetOpenId/Yadis/Yadis.cs @@ -19,6 +19,7 @@ namespace DotNetOpenId.Yadis { response = UntrustedWebRequest.Request(uri, null,
new[] { ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds });
if (response.StatusCode != System.Net.HttpStatusCode.OK) {
+ Logger.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.StatusCode, response.StatusCode, uri);
return null;
}
} catch (ArgumentException ex) {
diff --git a/src/version.txt b/src/version.txt index 21bb5e1..0bee604 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1 +1 @@ -2.2.5 +2.3.3 |