This repository has been archived by the owner on Mar 20, 2019. It is now read-only.
/
UriIdentifier.cs
254 lines (240 loc) · 9.99 KB
/
UriIdentifier.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using DotNetOpenId.RelyingParty;
using DotNetOpenId.Yadis;
namespace DotNetOpenId {
[Serializable]
public sealed class UriIdentifier : Identifier {
static readonly string[] allowedSchemes = { "http", "https" };
public static implicit operator Uri(UriIdentifier identifier) {
if (identifier == null) return null;
return identifier.Uri;
}
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(Strings.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(Strings.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;
}
/// <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 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 && (!IsDiscoverySecureEndToEnd || ep.IsSecure)) {
endpoints.Add(ep);
}
}
}
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;
}
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;
}
public override bool Equals(object obj) {
UriIdentifier other = obj as UriIdentifier;
if (other == null) return false;
return this.Uri == other.Uri;
}
public override int GetHashCode() {
return Uri.GetHashCode();
}
public override string ToString() {
return Uri.AbsoluteUri;
}
}
}