summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenId
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenId')
-rw-r--r--src/DotNetOpenId/DotNetOpenId.csproj1
-rw-r--r--src/DotNetOpenId/Identifier.cs9
-rw-r--r--src/DotNetOpenId/MessageEncoder.cs9
-rw-r--r--src/DotNetOpenId/Protocol.cs11
-rw-r--r--src/DotNetOpenId/Provider/CheckIdRequest.cs31
-rw-r--r--src/DotNetOpenId/Provider/IAuthenticationRequest.cs16
-rw-r--r--src/DotNetOpenId/RelyingParty/AuthenticationRequest.cs99
-rw-r--r--src/DotNetOpenId/RelyingParty/AuthenticationResponse.cs26
-rw-r--r--src/DotNetOpenId/RelyingParty/FailedAuthenticationResponse.cs4
-rw-r--r--src/DotNetOpenId/RelyingParty/IAuthenticationResponse.cs44
-rw-r--r--src/DotNetOpenId/RelyingParty/IProviderEndpoint.cs2
-rw-r--r--src/DotNetOpenId/RelyingParty/IXrdsProviderEndpoint.cs24
-rw-r--r--src/DotNetOpenId/RelyingParty/OpenIdRelyingParty.cs98
-rw-r--r--src/DotNetOpenId/RelyingParty/OpenIdTextBox.cs6
-rw-r--r--src/DotNetOpenId/RelyingParty/ServiceEndpoint.cs117
-rw-r--r--src/DotNetOpenId/RelyingParty/Token.cs11
-rw-r--r--src/DotNetOpenId/Response.cs3
-rw-r--r--src/DotNetOpenId/Strings.Designer.cs18
-rw-r--r--src/DotNetOpenId/Strings.resx6
-rw-r--r--src/DotNetOpenId/UriIdentifier.cs40
-rw-r--r--src/DotNetOpenId/Util.cs17
-rw-r--r--src/DotNetOpenId/XriIdentifier.cs16
-rw-r--r--src/DotNetOpenId/Yadis/XrdsDocument.cs107
-rw-r--r--src/DotNetOpenId/Yadis/XrdsNode.cs11
-rw-r--r--src/DotNetOpenId/Yadis/Yadis.cs1
25 files changed, 622 insertions, 105 deletions
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) {