summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj4
-rw-r--r--src/DotNetOpenAuth/OpenId/Identifier.cs164
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs4
-rw-r--r--src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs44
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs27
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx9
-rw-r--r--src/DotNetOpenAuth/OpenId/Realm.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/UriIdentifier.cs284
-rw-r--r--src/DotNetOpenAuth/OpenId/XriIdentifier.cs139
9 files changed, 674 insertions, 3 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 0d143eb..fa97e8d 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -163,7 +163,9 @@
<Compile Include="OpenId\ChannelElements\OpenIdChannel.cs" />
<Compile Include="OpenId\ChannelElements\OpenIdMessageFactory.cs" />
<Compile Include="OpenId\Configuration.cs" />
+ <Compile Include="OpenId\Identifier.cs" />
<Compile Include="OpenId\Messages\CheckIdRequest.cs" />
+ <Compile Include="OpenId\NoDiscoveryIdentifier.cs" />
<Compile Include="OpenId\Realm.cs" />
<Compile Include="OpenId\RelyingPartyDescription.cs" />
<Compile Include="OpenId\DiffieHellmanUtilities.cs" />
@@ -202,6 +204,8 @@
<Compile Include="OpenId\Provider\ProviderSecuritySettings.cs" />
<Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" />
<Compile Include="OpenId\SecuritySettings.cs" />
+ <Compile Include="OpenId\UriIdentifier.cs" />
+ <Compile Include="OpenId\XriIdentifier.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="OAuth\Messages\UnauthorizedTokenRequest.cs" />
<Compile Include="OAuth\ChannelElements\RsaSha1SigningBindingElement.cs" />
diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs
new file mode 100644
index 0000000..2eff359
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Identifier.cs
@@ -0,0 +1,164 @@
+//-----------------------------------------------------------------------
+// <copyright file="Identifier.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// An Identifier is either a "http" or "https" URI, or an XRI.
+ /// </summary>
+ [Serializable]
+ public abstract class Identifier {
+ /// <summary>
+ /// Constructs an <see cref="Identifier"/>.
+ /// </summary>
+ /// <param name="isDiscoverySecureEndToEnd">
+ /// Whether the derived class is prepared to guarantee end-to-end discovery
+ /// and initial redirect for authentication is performed using SSL.
+ /// </param>
+ protected Identifier(bool isDiscoverySecureEndToEnd) {
+ IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd;
+ }
+
+ /// <summary>
+ /// Whether this Identifier will ensure SSL is used throughout the discovery phase
+ /// and initial redirect of authentication.
+ /// </summary>
+ /// <remarks>
+ /// If this is False, a value of True may be obtained by calling <see cref="TryRequireSsl"/>.
+ /// </remarks>
+ protected internal bool IsDiscoverySecureEndToEnd { get; private set; }
+
+ /// <summary>
+ /// Converts the string representation of an Identifier to its strong type.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates"), SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads")]
+ public static implicit operator Identifier(string identifier) {
+ if (identifier == null) return null;
+ return Parse(identifier);
+ }
+ /// <summary>
+ /// Returns a strongly-typed Identifier for a given Uri.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")]
+ public static implicit operator Identifier(Uri identifier) {
+ if (identifier == null) return null;
+ return new UriIdentifier(identifier);
+ }
+ /// <summary>
+ /// Converts an Identifier to its string representation.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")]
+ public static implicit operator string(Identifier identifier) {
+ if (identifier == null) return null;
+ return identifier.ToString();
+ }
+ /// <summary>
+ /// Parses an identifier string and automatically determines
+ /// whether it is an XRI or URI.
+ /// </summary>
+ /// <param name="identifier">Either a URI or XRI identifier.</param>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
+ public static Identifier Parse(string identifier) {
+ if (string.IsNullOrEmpty(identifier)) throw new ArgumentNullException("identifier");
+ if (XriIdentifier.IsValidXri(identifier)) {
+ return new XriIdentifier(identifier);
+ } else {
+ return new UriIdentifier(identifier);
+ }
+ }
+ /// <summary>
+ /// Attempts to parse a string for an OpenId Identifier.
+ /// </summary>
+ /// <param name="value">The string to be parsed.</param>
+ /// <param name="result">The parsed Identifier form.</param>
+ /// <returns>
+ /// True if the operation was successful. False if the string was not a valid OpenId Identifier.
+ /// </returns>
+ public static bool TryParse(string value, out Identifier result) {
+ if (IsValid(value)) {
+ result = Parse(value);
+ return true;
+ } else {
+ result = null;
+ return false;
+ }
+ }
+ /// <summary>
+ /// Gets whether a given string represents a valid Identifier format.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
+ public static bool IsValid(string identifier) {
+ return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier);
+ }
+
+#if DISCOVERY // TODO: Add discovery and then re-enable this code block
+ /// <summary>
+ /// Performs discovery on the Identifier.
+ /// </summary>
+ /// <returns>
+ /// An initialized structure containing the discovered provider endpoint information.
+ /// </returns>
+ internal abstract IEnumerable<ServiceEndpoint> Discover();
+#endif
+
+ /// <summary>
+ /// Tests equality between two <see cref="Identifier"/>s.
+ /// </summary>
+ public static bool operator ==(Identifier id1, Identifier id2) {
+ if ((object)id1 == null ^ (object)id2 == null) return false;
+ if ((object)id1 == null) return true;
+ return id1.Equals(id2);
+ }
+ /// <summary>
+ /// Tests inequality between two <see cref="Identifier"/>s.
+ /// </summary>
+ public static bool operator !=(Identifier id1, Identifier id2) {
+ return !(id1 == id2);
+ }
+ /// <summary>
+ /// Tests equality between two <see cref="Identifier"/>s.
+ /// </summary>
+ public override bool Equals(object obj) {
+ Debug.Fail("This should be overridden in every derived class.");
+ return base.Equals(obj);
+ }
+ /// <summary>
+ /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable.
+ /// </summary>
+ public override int GetHashCode() {
+ 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();
+
+ /// <summary>
+ /// Converts a given identifier to its secure equivalent.
+ /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS.
+ /// Discovery is made to require SSL for the entire resolution process.
+ /// </summary>
+ /// <param name="secureIdentifier">
+ /// The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.
+ /// </param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ internal abstract bool TryRequireSsl(out Identifier secureIdentifier);
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
index 034dd65..46a32f6 100644
--- a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
@@ -43,7 +43,7 @@ namespace DotNetOpenAuth.OpenId.Messages {
/// <para>It is RECOMMENDED that OPs accept XRI identifiers with or without the "xri://" prefix, as specified in the Normalization (Normalization) section. </para>
/// </remarks>
[MessagePart("openid.claimed_id", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
- internal string ClaimedIdentifier { get; set; }
+ internal Identifier ClaimedIdentifier { get; set; }
/// <summary>
/// Gets or sets the OP Local Identifier.
@@ -60,7 +60,7 @@ namespace DotNetOpenAuth.OpenId.Messages {
/// see openid.claimed_id above). </para>
/// </remarks>
[MessagePart("openid.identity", IsRequired = false, AllowEmpty = false)]
- internal string LocalIdentifier { get; set; }
+ internal Identifier LocalIdentifier { get; set; }
/// <summary>
/// Gets or sets the handle of the association the RP would like the Provider
diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs
new file mode 100644
index 0000000..9a6d0ac
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs
@@ -0,0 +1,44 @@
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Wraps an existing Identifier and prevents it from performing discovery.
+ /// </summary>
+ class NoDiscoveryIdentifier : Identifier {
+ Identifier wrappedIdentifier;
+ internal NoDiscoveryIdentifier(Identifier wrappedIdentifier)
+ : base(false) {
+ if (wrappedIdentifier == null) throw new ArgumentNullException("wrappedIdentifier");
+
+ this.wrappedIdentifier = wrappedIdentifier;
+ }
+
+#if DISCOVERY // TODO: Add discovery and then re-enable this code block
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ return new ServiceEndpoint[0];
+ }
+#endif
+
+ internal override Identifier TrimFragment() {
+ return new NoDiscoveryIdentifier(wrappedIdentifier.TrimFragment());
+ }
+
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ return wrappedIdentifier.TryRequireSsl(out secureIdentifier);
+ }
+
+ public override string ToString() {
+ return wrappedIdentifier.ToString();
+ }
+
+ public override bool Equals(object obj) {
+ return wrappedIdentifier.Equals(obj);
+ }
+
+ public override int GetHashCode() {
+ return wrappedIdentifier.GetHashCode();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index d6b6de0..9dca2a8 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -106,6 +106,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to URI is not SSL yet requireSslDiscovery is set to true..
+ /// </summary>
+ internal static string ExplicitHttpUriSuppliedWithSslRequirement {
+ get {
+ return ResourceManager.GetString("ExplicitHttpUriSuppliedWithSslRequirement", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Cannot encode &apos;{0}&apos; because it contains an illegal character for Key-Value Form encoding. (line {1}: &apos;{2}&apos;).
/// </summary>
internal static string InvalidCharacterInKeyValueFormInput {
@@ -133,6 +142,24 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is not a valid URI..
+ /// </summary>
+ internal static string InvalidUri {
+ get {
+ return ResourceManager.GetString("InvalidUri", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Not a recognized XRI format: &apos;{0}&apos;..
+ /// </summary>
+ internal static string InvalidXri {
+ get {
+ return ResourceManager.GetString("InvalidXri", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The list of keys do not match the provided dictionary..
/// </summary>
internal static string KeysListAndDictionaryDoNotMatch {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index aae4bad..706d7e4 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -132,6 +132,9 @@
<data name="DiffieHellmanRequiredPropertiesNotSet" xml:space="preserve">
<value>The following properties must be set before the Diffie-Hellman algorithm can generate a public key: {0}</value>
</data>
+ <data name="ExplicitHttpUriSuppliedWithSslRequirement" xml:space="preserve">
+ <value>URI is not SSL yet requireSslDiscovery is set to true.</value>
+ </data>
<data name="InvalidCharacterInKeyValueFormInput" xml:space="preserve">
<value>Cannot encode '{0}' because it contains an illegal character for Key-Value Form encoding. (line {1}: '{2}')</value>
</data>
@@ -141,6 +144,12 @@
<data name="InvalidScheme" xml:space="preserve">
<value>The scheme must be http or https but was '{0}'.</value>
</data>
+ <data name="InvalidUri" xml:space="preserve">
+ <value>The value '{0}' is not a valid URI.</value>
+ </data>
+ <data name="InvalidXri" xml:space="preserve">
+ <value>Not a recognized XRI format: '{0}'.</value>
+ </data>
<data name="KeysListAndDictionaryDoNotMatch" xml:space="preserve">
<value>The list of keys do not match the provided dictionary.</value>
</data>
diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs
index 80c0188..ff38291 100644
--- a/src/DotNetOpenAuth/OpenId/Realm.cs
+++ b/src/DotNetOpenAuth/OpenId/Realm.cs
@@ -273,7 +273,7 @@ namespace DotNetOpenAuth.OpenId {
|| url.PathAndQuery[path_len] == '/';
}
-#if TODO // TODO: Add discovery and then re-enable this code block
+#if DISCOVERY // TODO: Add discovery and then re-enable this code block
/// <summary>
/// Searches for an XRDS document at the realm URL, and if found, searches
/// for a description of a relying party endpoints (OpenId login pages).
diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
new file mode 100644
index 0000000..d4cba3a
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
@@ -0,0 +1,284 @@
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Text.RegularExpressions;
+ using System.Web.UI.HtmlControls;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A URI style of OpenID Identifier.
+ /// </summary>
+ [Serializable]
+ public sealed class UriIdentifier : Identifier {
+ static readonly string[] allowedSchemes = { "http", "https" };
+ /// <summary>
+ /// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance.
+ /// </summary>
+ public static implicit operator Uri(UriIdentifier identifier) {
+ if (identifier == null) return null;
+ return identifier.Uri;
+ }
+ /// <summary>
+ /// Converts a <see cref="Uri"/> instance to a <see cref="UriIdentifier"/> instance.
+ /// </summary>
+ public static implicit operator UriIdentifier(Uri identifier) {
+ if (identifier == null) return null;
+ return new UriIdentifier(identifier);
+ }
+
+ internal UriIdentifier(string uri) : this(uri, false) { }
+ internal UriIdentifier(string uri, bool requireSslDiscovery)
+ : base(requireSslDiscovery) {
+ if (string.IsNullOrEmpty(uri)) throw new ArgumentNullException("uri");
+ Uri canonicalUri;
+ bool schemePrepended;
+ if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended))
+ throw new UriFormatException();
+ if (requireSslDiscovery && canonicalUri.Scheme != Uri.UriSchemeHttps) {
+ throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement);
+ }
+ Uri = canonicalUri;
+ SchemeImplicitlyPrepended = schemePrepended;
+ }
+ internal UriIdentifier(Uri uri) : this(uri, false) { }
+ internal UriIdentifier(Uri uri, bool requireSslDiscovery)
+ : base(requireSslDiscovery) {
+ if (uri == null) throw new ArgumentNullException("uri");
+ if (!TryCanonicalize(new UriBuilder(uri), out uri))
+ throw new UriFormatException();
+ if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) {
+ throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement);
+ }
+ Uri = uri;
+ SchemeImplicitlyPrepended = false;
+ }
+
+ internal Uri Uri { get; private set; }
+ /// <summary>
+ /// Gets whether the scheme was missing when this Identifier was
+ /// created and added automatically as part of the normalization
+ /// process.
+ /// </summary>
+ internal bool SchemeImplicitlyPrepended { get; private set; }
+
+ static bool isAllowedScheme(string uri) {
+ if (string.IsNullOrEmpty(uri)) return false;
+ return Array.FindIndex(allowedSchemes, s => uri.StartsWith(
+ s + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase)) >= 0;
+ }
+ static bool isAllowedScheme(Uri uri) {
+ if (uri == null) return false;
+ return Array.FindIndex(allowedSchemes, s =>
+ uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0;
+ }
+ static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) {
+ canonicalUri = null;
+ schemePrepended = false;
+ try {
+ // Assume http:// scheme if an allowed scheme isn't given, and strip
+ // fragments off. Consistent with spec section 7.2#3
+ if (!isAllowedScheme(uri)) {
+ uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) +
+ Uri.SchemeDelimiter + uri;
+ schemePrepended = true;
+ }
+ // Use a UriBuilder because it helps to normalize the URL as well.
+ return TryCanonicalize(new UriBuilder(uri), out canonicalUri);
+ } catch (UriFormatException) {
+ // We try not to land here with checks in the try block, but just in case.
+ return false;
+ }
+ }
+#if UNUSED
+ static bool TryCanonicalize(string uri, out string canonicalUri) {
+ Uri normalizedUri;
+ bool result = TryCanonicalize(uri, out normalizedUri);
+ canonicalUri = normalizedUri.AbsoluteUri;
+ return result;
+ }
+#endif
+ /// <summary>
+ /// Removes the fragment from a URL and sets the host to lowercase.
+ /// </summary>
+ /// <remarks>
+ /// This does NOT standardize an OpenID URL for storage in a database, as
+ /// it does nothing to convert the URL to a Claimed Identifier, besides the fact
+ /// that it only deals with URLs whereas OpenID 2.0 supports XRIs.
+ /// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier.
+ /// </remarks>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
+ static bool TryCanonicalize(UriBuilder uriBuilder, out Uri canonicalUri) {
+ uriBuilder.Host = uriBuilder.Host.ToLowerInvariant();
+ canonicalUri = uriBuilder.Uri;
+ return true;
+ }
+ internal static bool IsValidUri(string uri) {
+ Uri normalized;
+ bool schemePrepended;
+ return TryCanonicalize(uri, out normalized, false, out schemePrepended);
+ }
+ internal static bool IsValidUri(Uri uri) {
+ if (uri == null) return false;
+ if (!uri.IsAbsoluteUri) return false;
+ if (!isAllowedScheme(uri)) return false;
+ return true;
+ }
+
+#if DISCOVERY // TODO: Add discovery and then re-enable this code block
+ /// <summary>
+ /// Searches HTML for the HEAD META tags that describe OpenID provider services.
+ /// </summary>
+ /// <param name="claimedIdentifier">
+ /// The final URL that provided this HTML document.
+ /// This may not be the same as (this) userSuppliedIdentifier if the
+ /// userSuppliedIdentifier pointed to a 301 Redirect.
+ /// </param>
+ /// <param name="html">The HTML that was downloaded and should be searched.</param>
+ /// <returns>
+ /// An initialized ServiceEndpoint if the OpenID Provider information was
+ /// found. Otherwise null.
+ /// </returns>
+ /// <remarks>
+ /// OpenID 2.0 tags are always used if they are present, otherwise
+ /// OpenID 1.x tags are used if present.
+ /// </remarks>
+ private static ServiceEndpoint DiscoverFromHtml(Uri claimedIdentifier, string html) {
+ Uri providerEndpoint = null;
+ Protocol discoveredProtocol = null;
+ Identifier providerLocalIdentifier = null;
+ var linkTags = new List<HtmlLink>(Yadis.HtmlParser.HeadTags<HtmlLink>(html));
+ foreach (var protocol in Protocol.AllVersions) {
+ foreach (var linkTag in linkTags) {
+ // rel attributes are supposed to be interpreted with case INsensitivity,
+ // and is a space-delimited list of values. (http://www.htmlhelp.com/reference/html40/values.html#linktypes)
+ if (Regex.IsMatch(linkTag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase)) {
+ if (Uri.TryCreate(linkTag.Href, UriKind.Absolute, out providerEndpoint)) {
+ discoveredProtocol = protocol;
+ break;
+ }
+ }
+ }
+ if (providerEndpoint != null) break;
+ }
+ if (providerEndpoint == null)
+ return null; // html did not contain openid.server link
+ // See if a LocalId tag of the discovered version exists
+ foreach (var linkTag in linkTags) {
+ if (Regex.IsMatch(linkTag.Attributes["rel"], @"\b" + Regex.Escape(discoveredProtocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase)) {
+ if (Identifier.IsValid(linkTag.Href)) {
+ providerLocalIdentifier = linkTag.Href;
+ break;
+ } else {
+ Logger.WarnFormat("Skipping endpoint data because local id is badly formed ({0}).", linkTag.Href);
+ return null; // badly formed URL used as LocalId
+ }
+ }
+ }
+
+ // Choose the TypeURI to match the OpenID version detected.
+ string[] typeURIs = { discoveredProtocol.ClaimedIdentifierServiceTypeURI };
+ return ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, providerLocalIdentifier,
+ providerEndpoint, typeURIs, (int?)null, (int?)null);
+ }
+
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>();
+ // Attempt YADIS discovery
+ DiscoveryResult yadisResult = Yadis.Yadis.Discover(this, IsDiscoverySecureEndToEnd);
+ if (yadisResult != null) {
+ if (yadisResult.IsXrds) {
+ XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText);
+ var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri);
+ // Filter out insecure endpoints if high security is required.
+ if (IsDiscoverySecureEndToEnd) {
+ xrdsEndpoints = Util.Where(xrdsEndpoints, se => se.IsSecure);
+ }
+ endpoints.AddRange(xrdsEndpoints);
+ }
+ // Failing YADIS discovery of an XRDS document, we try HTML discovery.
+ if (endpoints.Count == 0) {
+ ServiceEndpoint ep = DiscoverFromHtml(yadisResult.NormalizedUri, yadisResult.ResponseText);
+ if (ep != null) {
+ Logger.Debug("HTML discovery found a service endpoint.");
+ Logger.Debug(ep);
+ if (!IsDiscoverySecureEndToEnd || ep.IsSecure) {
+ endpoints.Add(ep);
+ } else {
+ Logger.Info("Skipping HTML discovered endpoint because it is not secure.");
+ }
+ } else {
+ Logger.Debug("HTML discovery failed to find any endpoints.");
+ }
+ } else {
+ Logger.Debug("Skipping HTML discovery because XRDS contained service endpoints.");
+ }
+ }
+ return endpoints;
+ }
+#endif
+
+ 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;
+ }
+
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ // If this Identifier is already secure, reuse it.
+ if (IsDiscoverySecureEndToEnd) {
+ secureIdentifier = this;
+ return true;
+ }
+
+ // If this identifier already uses SSL for initial discovery, return one
+ // that guarantees it will be used throughout the discovery process.
+ if (String.Equals(Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ secureIdentifier = new UriIdentifier(this.Uri, true);
+ return true;
+ }
+
+ // Otherwise, try to make this Identifier secure by normalizing to HTTPS instead of HTTP.
+ if (SchemeImplicitlyPrepended) {
+ UriBuilder newIdentifierUri = new UriBuilder(this.Uri);
+ newIdentifierUri.Scheme = Uri.UriSchemeHttps;
+ if (newIdentifierUri.Port == 80) {
+ newIdentifierUri.Port = 443;
+ }
+ secureIdentifier = new UriIdentifier(newIdentifierUri.Uri, true);
+ return true;
+ }
+
+ // This identifier is explicitly NOT https, so we cannot change it.
+ secureIdentifier = new NoDiscoveryIdentifier(this);
+ return false;
+ }
+
+ /// <summary>
+ /// Tests equality between this URI and another URI.
+ /// </summary>
+ public override bool Equals(object obj) {
+ UriIdentifier other = obj as UriIdentifier;
+ if (other == null) return false;
+ return this.Uri == other.Uri;
+ }
+
+ /// <summary>
+ /// Returns the hash code of this XRI.
+ /// </summary>
+ public override int GetHashCode() {
+ return Uri.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns the string form of the URI.
+ /// </summary>
+ public override string ToString() {
+ return Uri.AbsoluteUri;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs
new file mode 100644
index 0000000..663bb49
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs
@@ -0,0 +1,139 @@
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Xml;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// An XRI style of OpenID Identifier.
+ /// </summary>
+ [Serializable]
+ public sealed class XriIdentifier : Identifier {
+ internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' };
+ const string xriScheme = "xri://";
+
+ internal XriIdentifier(string xri) : this(xri, false) { }
+ internal XriIdentifier(string xri, bool requireSsl)
+ : base(requireSsl) {
+ if (!IsValidXri(xri))
+ throw new FormatException(string.Format(CultureInfo.CurrentCulture,
+ OpenIdStrings.InvalidXri, xri));
+ xriResolverProxy = xriResolverProxyTemplate;
+ if (requireSsl) {
+ // Indicate to xri.net that we require SSL to be used for delegated resolution
+ // of community i-names.
+ xriResolverProxy += ";https=true";
+ }
+ OriginalXri = xri;
+ CanonicalXri = canonicalizeXri(xri);
+ }
+
+ /// <summary>
+ /// The original XRI supplied to the constructor.
+ /// </summary>
+ internal string OriginalXri { get; private set; }
+ /// <summary>
+ /// The canonical form of the XRI string.
+ /// </summary>
+ internal string CanonicalXri { get; private set; }
+
+ /// <summary>
+ /// Tests whether a given string represents a valid XRI format.
+ /// </summary>
+ internal static bool IsValidXri(string xri) {
+ if (string.IsNullOrEmpty(xri)) throw new ArgumentNullException("xri");
+ // TODO: better validation code here
+ return xri.IndexOfAny(GlobalContextSymbols) == 0
+ || xri.StartsWith("(", StringComparison.Ordinal)
+ || xri.StartsWith(xriScheme, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Takes any valid form of XRI string and returns the canonical form of the same XRI.
+ /// </summary>
+ static string canonicalizeXri(string xri) {
+ xri = xri.Trim();
+ if (xri.StartsWith(xriScheme, StringComparison.OrdinalIgnoreCase))
+ xri = xri.Substring(xriScheme.Length);
+ return xri;
+ }
+
+ /// <summary>
+ /// The magic URL that will provide us an XRDS document for a given XRI identifier.
+ /// </summary>
+ /// <remarks>
+ /// We use application/xrd+xml instead of application/xrds+xml because it gets
+ /// xri.net to automatically give us exactly the right XRD element for community i-names
+ /// automatically, saving us having to choose which one to use out of the result.
+ /// The ssl=true parameter tells the proxy resolver to accept only SSL connections
+ /// when resolving community i-names.
+ /// </remarks>
+ const string xriResolverProxyTemplate = "https://xri.net/{0}?_xrd_r=application/xrd%2Bxml;sep=false";
+ readonly string xriResolverProxy;
+ /// <summary>
+ /// Resolves the XRI to a URL from which an XRDS document may be downloaded.
+ /// </summary>
+ private Uri XrdsUrl {
+ get {
+ return new Uri(string.Format(CultureInfo.InvariantCulture,
+ xriResolverProxy, this));
+ }
+ }
+
+#if DISCOVERY // TODO: Add discovery and then re-enable this code block
+ XrdsDocument downloadXrds() {
+ var xrdsResponse = UntrustedWebRequest.Request(XrdsUrl);
+ XrdsDocument doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream));
+ if (!doc.IsXrdResolutionSuccessful) {
+ throw new OpenIdException(Strings.XriResolutionFailed);
+ }
+ return doc;
+ }
+
+ internal override IEnumerable<ServiceEndpoint> Discover() {
+ return downloadXrds().CreateServiceEndpoints(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(userSuppliedIdentifier);
+ }
+#endif
+
+ internal override Identifier TrimFragment() {
+ return this;
+ }
+
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true);
+ return true;
+ }
+
+ /// <summary>
+ /// Tests equality between this XRI and another XRI.
+ /// </summary>
+ public override bool Equals(object obj) {
+ XriIdentifier other = obj as XriIdentifier;
+ if (other == null) return false;
+ return this.CanonicalXri == other.CanonicalXri;
+ }
+
+ /// <summary>
+ /// Returns the hash code of this XRI.
+ /// </summary>
+ public override int GetHashCode() {
+ return CanonicalXri.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns the canonical string form of the XRI.
+ /// </summary>
+ public override string ToString() {
+ return CanonicalXri;
+ }
+ }
+}