//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId {
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId.Messages;
using DotNetOpenAuth.OpenId.RelyingParty;
///
/// Represents a single OP endpoint from discovery on some OpenID Identifier.
///
[DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, ProviderEndpoint: {ProviderEndpoint}, OpenId: {Protocol.Version}")]
public sealed class IdentifierDiscoveryResult : IProviderEndpoint {
///
/// Backing field for the property.
///
private Protocol protocol;
///
/// Backing field for the property.
///
private Identifier claimedIdentifier;
///
/// Backing field for the property.
///
private string friendlyIdentifierForDisplay;
///
/// Initializes a new instance of the class.
///
/// The provider endpoint.
/// The Claimed Identifier.
/// The User-supplied Identifier.
/// The Provider Local Identifier.
/// The service priority.
/// The URI priority.
private IdentifierDiscoveryResult(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) {
Requires.NotNull(providerEndpoint, "providerEndpoint");
Requires.NotNull(claimedIdentifier, "claimedIdentifier");
this.ProviderEndpoint = providerEndpoint.Uri;
this.Capabilities = new ReadOnlyCollection(providerEndpoint.Capabilities);
this.Version = providerEndpoint.Version;
this.ClaimedIdentifier = claimedIdentifier;
this.ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier;
this.UserSuppliedIdentifier = userSuppliedIdentifier;
this.ServicePriority = servicePriority;
this.ProviderEndpointPriority = uriPriority;
}
///
/// Gets the detected version of OpenID implemented by the Provider.
///
public Version Version { get; private set; }
///
/// Gets the Identifier that was presented by the end user to the Relying Party,
/// or selected by the user at the OpenID Provider.
/// During the initiation phase of the protocol, an end user may enter
/// either their own Identifier or an OP Identifier. If an OP Identifier
/// is used, the OP may then assist the end user in selecting an Identifier
/// to share with the Relying Party.
///
public Identifier UserSuppliedIdentifier { get; private set; }
///
/// Gets the Identifier that the end user claims to control.
///
public Identifier ClaimedIdentifier {
get {
return this.claimedIdentifier;
}
internal set {
// Take care to reparse the incoming identifier to make sure it's
// not a derived type that will override expected behavior.
// Elsewhere in this class, we count on the fact that this property
// is either UriIdentifier or XriIdentifier. MockIdentifier messes it up.
this.claimedIdentifier = value != null ? Identifier.Reparse(value) : null;
}
}
///
/// Gets an alternate Identifier for an end user that is local to a
/// particular OP and thus not necessarily under the end user's
/// control.
///
public Identifier ProviderLocalIdentifier { get; private set; }
///
/// Gets a more user-friendly (but NON-secure!) string to display to the user as his identifier.
///
/// A human-readable, abbreviated (but not secure) identifier the user MAY recognize as his own.
public string FriendlyIdentifierForDisplay {
get {
if (this.friendlyIdentifierForDisplay == null) {
XriIdentifier xri = this.ClaimedIdentifier as XriIdentifier;
UriIdentifier uri = this.ClaimedIdentifier as UriIdentifier;
if (xri != null) {
if (this.UserSuppliedIdentifier == null || String.Equals(this.UserSuppliedIdentifier, this.ClaimedIdentifier, StringComparison.OrdinalIgnoreCase)) {
this.friendlyIdentifierForDisplay = this.ClaimedIdentifier;
} else {
this.friendlyIdentifierForDisplay = this.UserSuppliedIdentifier;
}
} else if (uri != null) {
if (uri != this.Protocol.ClaimedIdentifierForOPIdentifier) {
string displayUri = uri.Uri.Host;
// We typically want to display the path, because that will often have the username in it.
// As Google Apps for Domains and the like become more popular, a standard /openid path
// will often appear, which is not helpful to identifying the user so we'll avoid including
// that path if it's present.
if (!string.Equals(uri.Uri.AbsolutePath, "/openid", StringComparison.OrdinalIgnoreCase)) {
displayUri += uri.Uri.AbsolutePath.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.
this.friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri);
}
} else {
ErrorUtilities.ThrowInternal("ServiceEndpoint.ClaimedIdentifier neither XRI nor URI.");
this.friendlyIdentifierForDisplay = this.ClaimedIdentifier;
}
}
return this.friendlyIdentifierForDisplay;
}
}
///
/// Gets the provider endpoint.
///
public Uri ProviderEndpoint { get; private set; }
///
/// Gets the @priority given in the XRDS document for this specific OP endpoint.
///
public int? ProviderEndpointPriority { get; private set; }
///
/// Gets the @priority given in the XRDS document for this service
/// (which may consist of several endpoints).
///
public int? ServicePriority { get; private set; }
///
/// Gets the collection of service type URIs found in the XRDS document describing this Provider.
///
/// Should never be null, but may be empty.
public ReadOnlyCollection Capabilities { get; private set; }
#region IProviderEndpoint Members
///
/// Gets the URL that the OpenID Provider receives authentication requests at.
///
/// This value MUST be an absolute HTTP or HTTPS URL.
Uri IProviderEndpoint.Uri {
get { return this.ProviderEndpoint; }
}
#endregion
///
/// Gets an XRDS sorting routine that uses the XRDS Service/@Priority
/// attribute to determine order.
///
///
/// Endpoints lacking any priority value are sorted to the end of the list.
///
internal static Comparison EndpointOrder {
get {
// Sort first by service type (OpenID 2.0, 1.1, 1.0),
// then by Service/@priority, then by Service/Uri/@priority
return (se1, se2) => {
int result = GetEndpointPrecedenceOrderByServiceType(se1).CompareTo(GetEndpointPrecedenceOrderByServiceType(se2));
if (result != 0) {
return result;
}
if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) {
result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
if (result != 0) {
return result;
}
if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) {
return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value);
} else if (se1.ProviderEndpointPriority.HasValue) {
return -1;
} else if (se2.ProviderEndpointPriority.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.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) {
return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value);
} else if (se1.ProviderEndpointPriority.HasValue) {
return -1;
} else if (se2.ProviderEndpointPriority.HasValue) {
return 1;
} else {
return 0;
}
}
}
};
}
}
///
/// Gets the protocol used by the OpenID Provider.
///
internal Protocol Protocol {
get {
if (this.protocol == null) {
this.protocol = Protocol.Lookup(this.Version);
}
return this.protocol;
}
}
///
/// Implements the operator ==.
///
/// The first service endpoint.
/// The second service endpoint.
/// The result of the operator.
public static bool operator ==(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) {
return se1.EqualsNullSafe(se2);
}
///
/// Implements the operator !=.
///
/// The first service endpoint.
/// The second service endpoint.
/// The result of the operator.
public static bool operator !=(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) {
return !(se1 == se2);
}
///
/// Determines whether the specified is equal to the current .
///
/// The to compare with the current .
///
/// true if the specified is equal to the current ; otherwise, false.
///
///
/// The parameter is null.
///
public override bool Equals(object obj) {
var other = obj as IdentifierDiscoveryResult;
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
this.ClaimedIdentifier == other.ClaimedIdentifier &&
this.ProviderEndpoint == other.ProviderEndpoint &&
this.ProviderLocalIdentifier == other.ProviderLocalIdentifier &&
this.Protocol.EqualsPractically(other.Protocol);
}
///
/// Serves as a hash function for a particular type.
///
///
/// A hash code for the current .
///
public override int GetHashCode() {
return this.ClaimedIdentifier.GetHashCode();
}
///
/// Returns a that represents the current .
///
///
/// A that represents the current .
///
public override string ToString() {
StringBuilder builder = new StringBuilder();
builder.AppendLine("ClaimedIdentifier: " + this.ClaimedIdentifier);
builder.AppendLine("ProviderLocalIdentifier: " + this.ProviderLocalIdentifier);
builder.AppendLine("ProviderEndpoint: " + this.ProviderEndpoint);
builder.AppendLine("OpenID version: " + this.Version);
builder.AppendLine("Service Type URIs:");
foreach (string serviceTypeUri in this.Capabilities) {
builder.Append("\t");
builder.AppendLine(serviceTypeUri);
}
builder.Length -= Environment.NewLine.Length; // trim last newline
return builder.ToString();
}
///
/// Checks whether the OpenId Identifier claims support for a given extension.
///
/// The extension whose support is being queried.
///
/// True if support for the extension is advertised. False otherwise.
///
///
/// Note that a true or false return value is no guarantee of a Provider's
/// support for or lack of support for an extension. The return value is
/// determined by how the authenticating user filled out his/her XRDS document only.
/// The only way to be sure of support for a given extension is to include
/// the extension in the request and see if a response comes back for that extension.
///
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")]
public bool IsExtensionSupported() where T : IOpenIdMessageExtension, new() {
T extension = new T();
return this.IsExtensionSupported(extension);
}
///
/// Checks whether the OpenId Identifier claims support for a given extension.
///
/// The extension whose support is being queried.
///
/// True if support for the extension is advertised. False otherwise.
///
///
/// Note that a true or false return value is no guarantee of a Provider's
/// support for or lack of support for an extension. The return value is
/// determined by how the authenticating user filled out his/her XRDS document only.
/// The only way to be sure of support for a given extension is to include
/// the extension in the request and see if a response comes back for that extension.
///
public bool IsExtensionSupported(Type extensionType) {
var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType);
return this.IsExtensionSupported(extension);
}
///
/// Determines whether a given extension is supported by this endpoint.
///
/// An instance of the extension to check support for.
///
/// true if the extension is supported by this endpoint; otherwise, false.
///
public bool IsExtensionSupported(IOpenIdMessageExtension extension) {
Requires.NotNull(extension, "extension");
// Consider the primary case.
if (this.IsTypeUriPresent(extension.TypeUri)) {
return true;
}
// Consider the secondary cases.
if (extension.AdditionalSupportedTypeUris != null) {
if (extension.AdditionalSupportedTypeUris.Any(typeUri => this.IsTypeUriPresent(typeUri))) {
return true;
}
}
return false;
}
///
/// Creates a instance to represent some OP Identifier.
///
/// The provider identifier (actually the user-supplied identifier).
/// The provider endpoint.
/// The service priority.
/// The URI priority.
/// The created instance
internal static IdentifierDiscoveryResult CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) {
Requires.NotNull(providerEndpoint, "providerEndpoint");
Protocol protocol = Protocol.Lookup(providerEndpoint.Version);
return new IdentifierDiscoveryResult(
providerEndpoint,
protocol.ClaimedIdentifierForOPIdentifier,
providerIdentifier,
protocol.ClaimedIdentifierForOPIdentifier,
servicePriority,
uriPriority);
}
///
/// Creates a instance to represent some Claimed Identifier.
///
/// The claimed identifier.
/// The provider local identifier.
/// The provider endpoint.
/// The service priority.
/// The URI priority.
/// The created instance
internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) {
return CreateForClaimedIdentifier(claimedIdentifier, null, providerLocalIdentifier, providerEndpoint, servicePriority, uriPriority);
}
///
/// Creates a instance to represent some Claimed Identifier.
///
/// The claimed identifier.
/// The user supplied identifier.
/// The provider local identifier.
/// The provider endpoint.
/// The service priority.
/// The URI priority.
/// The created instance
internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) {
return new IdentifierDiscoveryResult(providerEndpoint, claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, servicePriority, uriPriority);
}
///
/// Determines whether a given type URI is present on the specified provider endpoint.
///
/// The type URI.
///
/// true if the type URI is present on the specified provider endpoint; otherwise, false.
///
internal bool IsTypeUriPresent(string typeUri) {
Requires.NotNullOrEmpty(typeUri, "typeUri");
return this.Capabilities.Contains(typeUri);
}
///
/// Sets the Capabilities property (this method is a test hook.)
///
/// The value.
/// The publicize.exe tool should work for the unit tests, but for some reason it fails on the build server.
internal void SetCapabilitiesForTestHook(ReadOnlyCollection value) {
this.Capabilities = value;
}
///
/// Gets the priority rating for a given type of endpoint, allowing a
/// priority sorting of endpoints.
///
/// The endpoint to prioritize.
/// An arbitary integer, which may be used for sorting against other returned values from this method.
private static double GetEndpointPrecedenceOrderByServiceType(IdentifierDiscoveryResult endpoint) {
// The numbers returned from this method only need to compare against other numbers
// from this method, which makes them arbitrary but relational to only others here.
if (endpoint.Capabilities.Contains(Protocol.V20.OPIdentifierServiceTypeURI)) {
return 0;
}
if (endpoint.Capabilities.Contains(Protocol.V20.ClaimedIdentifierServiceTypeURI)) {
return 1;
}
if (endpoint.Capabilities.Contains(Protocol.V11.ClaimedIdentifierServiceTypeURI)) {
return 2;
}
if (endpoint.Capabilities.Contains(Protocol.V10.ClaimedIdentifierServiceTypeURI)) {
return 3;
}
return 10;
}
#if CONTRACTS_FULL
///
/// Verifies conditions that should be true for any valid state of this object.
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
private void ObjectInvariant() {
Contract.Invariant(this.ProviderEndpoint != null);
Contract.Invariant(this.ClaimedIdentifier != null);
Contract.Invariant(this.ProviderLocalIdentifier != null);
Contract.Invariant(this.Capabilities != null);
Contract.Invariant(this.Version != null);
Contract.Invariant(this.Protocol != null);
}
#endif
}
}