summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenId/RelyingParty
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenId/RelyingParty')
-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
10 files changed, 386 insertions, 45 deletions
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);
}
}