summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs
blob: 310ebd2397b8b0680d5d261dc4a310a6d40ee82a (plain)
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//-----------------------------------------------------------------------
// <copyright file="Identifier.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.OpenId {
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Diagnostics.CodeAnalysis;
	using System.Diagnostics.Contracts;
	using DotNetOpenAuth.Messaging;
	using DotNetOpenAuth.Messaging.Reflection;
	using Validation;

	/// <summary>
	/// An Identifier is either a "http" or "https" URI, or an XRI.
	/// </summary>
	[Serializable]
	[Pure]
	[DefaultEncoder(typeof(IdentifierEncoder))]
	public abstract class Identifier {
		/// <summary>
		/// Initializes a new instance of the <see cref="Identifier"/> class.
		/// </summary>
		/// <param name="originalString">The original string before any normalization.</param>
		/// <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>
		[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "Emphasis on string instead of the strong-typed Identifier.")]
		protected Identifier(string originalString, bool isDiscoverySecureEndToEnd) {
			this.OriginalString = originalString;
			this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd;
		}

		/// <summary>
		/// Gets the original string that was normalized to create this Identifier.
		/// </summary>
		internal string OriginalString { get; private set; }

		/// <summary>
		/// Gets the Identifier in the form in which it should be serialized.
		/// </summary>
		/// <value>
		/// For Identifiers that were originally deserialized, this is the exact same
		/// string that was deserialized.  For Identifiers instantiated in some other way, this is
		/// the normalized form of the string used to instantiate the identifier.
		/// </value>
		internal virtual string SerializedString {
			get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); }
		}

		/// <summary>
		/// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal
		/// based solely on their string reprsentations.
		/// </summary>
		/// <remarks>
		/// This property serves as a test hook, so that MockIdentifier instances can be considered "equal"
		/// to UriIdentifier instances.
		/// </remarks>
		protected internal static bool EqualityOnStrings { get; set; }

		/// <summary>
		/// Gets a value indicating whether this Identifier will ensure SSL is 
		/// used throughout the discovery phase and initial redirect of authentication.
		/// </summary>
		/// <remarks>
		/// If this is <c>false</c>, a value of <c>true</c> may be obtained by calling 
		/// <see cref="TryRequireSsl"/>.
		/// </remarks>
		protected internal bool IsDiscoverySecureEndToEnd { get; private set; }

		/// <summary>
		/// Gets a value indicating whether this instance was initialized from
		/// deserializing a message.
		/// </summary>
		/// <remarks>
		/// This is interesting because when an Identifier comes from the network,
		/// we can't normalize it and then expect signatures to still verify.  
		/// But if the Identifier is initialized locally, we can and should normalize it
		/// before serializing it.
		/// </remarks>
		protected bool IsDeserializedInstance { get; private set; }

		/// <summary>
		/// Converts the string representation of an Identifier to its strong type.
		/// </summary>
		/// <param name="identifier">The identifier.</param>
		/// <returns>The particular Identifier instance to represent the value given.</returns>
		[SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all identifiers are URIs.")]
		[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")]
		[DebuggerStepThrough]
		public static implicit operator Identifier(string identifier) {
			Requires.That(identifier == null || identifier.Length > 0, "identifier", "Empty string cannot be translated to an identifier.");

			if (identifier == null) {
				return null;
			}
			return Parse(identifier);
		}

		/// <summary>
		/// Converts a given Uri to a strongly-typed Identifier.
		/// </summary>
		/// <param name="identifier">The identifier to convert.</param>
		/// <returns>The result of the conversion.</returns>
		[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")]
		[DebuggerStepThrough]
		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>
		/// <param name="identifier">The identifier to convert to a string.</param>
		/// <returns>The result of the conversion.</returns>
		[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")]
		[DebuggerStepThrough]
		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>
		/// <returns>An <see cref="Identifier"/> instance for the given value.</returns>
		[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
		public static Identifier Parse(string identifier) {
			Requires.NotNullOrEmpty(identifier, "identifier");

			return Parse(identifier, false);
		}

		/// <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>
		/// <param name="serializeExactValue">if set to <c>true</c> this Identifier will serialize exactly as given rather than in its normalized form.</param>
		/// <returns>
		/// An <see cref="Identifier"/> instance for the given value.
		/// </returns>
		[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
		public static Identifier Parse(string identifier, bool serializeExactValue) {
			Requires.NotNullOrEmpty(identifier, "identifier");

			Identifier id;
			if (XriIdentifier.IsValidXri(identifier)) {
				id = new XriIdentifier(identifier);
			} else {
				id = new UriIdentifier(identifier);
			}

			id.IsDeserializedInstance = serializeExactValue;
			return id;
		}

		/// <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 (string.IsNullOrEmpty(value)) {
				result = null;
				return false;
			}

			if (IsValid(value)) {
				result = Parse(value);
				return true;
			} else {
				result = null;
				return false;
			}
		}

		/// <summary>
		/// Checks the validity of a given string representation of some Identifier.
		/// </summary>
		/// <param name="identifier">The identifier.</param>
		/// <returns>
		/// 	<c>true</c> if the specified identifier is valid; otherwise, <c>false</c>.
		/// </returns>
		[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
		public static bool IsValid(string identifier) {
			Requires.NotNullOrEmpty(identifier, "identifier");
			return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier);
		}

		/// <summary>
		/// Tests equality between two <see cref="Identifier"/>s.
		/// </summary>
		/// <param name="id1">The first Identifier.</param>
		/// <param name="id2">The second Identifier.</param>
		/// <returns>
		/// <c>true</c> if the two instances should be considered equal; <c>false</c> otherwise.
		/// </returns>
		public static bool operator ==(Identifier id1, Identifier id2) {
			return id1.EqualsNullSafe(id2);
		}

		/// <summary>
		/// Tests inequality between two <see cref="Identifier"/>s.
		/// </summary>
		/// <param name="id1">The first Identifier.</param>
		/// <param name="id2">The second Identifier.</param>
		/// <returns>
		/// <c>true</c> if the two instances should be considered unequal; <c>false</c> if they are equal.
		/// </returns>
		public static bool operator !=(Identifier id1, Identifier id2) {
			return !id1.EqualsNullSafe(id2);
		}

		/// <summary>
		/// Tests equality between two <see cref="Identifier"/>s.
		/// </summary>
		/// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
		/// <returns>
		/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
		/// </returns>
		/// <exception cref="T:System.NullReferenceException">
		/// The <paramref name="obj"/> parameter is null.
		/// </exception>
		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>
		/// <returns>
		/// A hash code for the current <see cref="T:System.Object"/>.
		/// </returns>
		public override int GetHashCode() {
			Debug.Fail("This should be overridden in every derived class.");
			return base.GetHashCode();
		}

		/// <summary>
		/// Reparses the specified identifier in order to be assured that the concrete type that
		/// implements the identifier is one of the well-known ones.
		/// </summary>
		/// <param name="identifier">The identifier.</param>
		/// <returns>Either <see cref="XriIdentifier"/> or <see cref="UriIdentifier"/>.</returns>
		internal static Identifier Reparse(Identifier identifier) {
			Requires.NotNull(identifier, "identifier");

			return Parse(identifier, identifier.IsDeserializedInstance);
		}

		/// <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>
		/// <returns>A new <see cref="Identifier"/> instance if there was a 
		/// fragment to remove, otherwise this same instance..</returns>
		[Pure]
		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);

		/// <summary>
		/// Provides conversions to and from strings for messages that include members of this type.
		/// </summary>
		private class IdentifierEncoder : IMessagePartOriginalEncoder {
			/// <summary>
			/// Encodes the specified value as the original value that was formerly decoded.
			/// </summary>
			/// <param name="value">The value.  Guaranteed to never be null.</param>
			/// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns>
			public string EncodeAsOriginalString(object value) {
				Requires.NotNull(value, "value");
				return ((Identifier)value).OriginalString;
			}

			/// <summary>
			/// Encodes the specified value.
			/// </summary>
			/// <param name="value">The value.  Guaranteed to never be null.</param>
			/// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns>
			public string Encode(object value) {
				Requires.NotNull(value, "value");
				return ((Identifier)value).SerializedString;
			}

			/// <summary>
			/// Decodes the specified value.
			/// </summary>
			/// <param name="value">The string value carried by the transport.  Guaranteed to never be null, although it may be empty.</param>
			/// <returns>The deserialized form of the given string.</returns>
			/// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
			public object Decode(string value) {
				Requires.NotNull(value, "value");
				ErrorUtilities.VerifyFormat(value.Length > 0, MessagingStrings.NonEmptyStringExpected);
				return Identifier.Parse(value, true);
			}
		}
	}
}