summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
blob: 03587281578a2a4ca9d29fe4a79a129ad473259d (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
//-----------------------------------------------------------------------
// <copyright file="ClaimsRequest.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Diagnostics.CodeAnalysis;
	using System.Globalization;
	using System.Text;

	using DotNetOpenAuth.Logging;
	using DotNetOpenAuth.Messaging;
	using DotNetOpenAuth.OpenId.Messages;
	using Validation;

	/// <summary>
	/// Carries the request/require/none demand state of the simple registration fields.
	/// </summary>
	[Serializable]
	public sealed class ClaimsRequest : ExtensionBase {
		/// <summary>
		/// The factory method that may be used in deserialization of this message.
		/// </summary>
		internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
			if (typeUri == Constants.TypeUris.Standard && isProviderRole) {
				return new ClaimsRequest(typeUri);
			}

			return null;
		};

		/// <summary>
		/// The type URI that this particular (deserialized) extension was read in using,
		/// allowing a response to alter be crafted using the same type URI.
		/// </summary>
		private string typeUriDeserializedFrom;

		/// <summary>
		/// Initializes a new instance of the <see cref="ClaimsRequest"/> class.
		/// </summary>
		public ClaimsRequest()
			: base(new Version(1, 0), Constants.TypeUris.Standard, Constants.AdditionalTypeUris) {
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ClaimsRequest"/> class
		/// by deserializing from a message.
		/// </summary>
		/// <param name="typeUri">The type URI this extension was recognized by in the OpenID message.</param>
		internal ClaimsRequest(string typeUri)
			: this() {
			Requires.NotNullOrEmpty(typeUri, "typeUri");

			this.typeUriDeserializedFrom = typeUri;
		}

		/// <summary>
		/// Gets or sets the URL the consumer site provides for the authenticating user to review
		/// for how his claims will be used by the consumer web site.
		/// </summary>
		[MessagePart(Constants.policy_url, IsRequired = false)]
		public Uri PolicyUrl { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the nickname of the user.
		/// </summary>
		public DemandLevel Nickname { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the email of the user.
		/// </summary>
		public DemandLevel Email { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the full name of the user.
		/// </summary>
		public DemandLevel FullName { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the birthdate of the user.
		/// </summary>
		public DemandLevel BirthDate { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the gender of the user.
		/// </summary>
		public DemandLevel Gender { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the postal code of the user.
		/// </summary>
		public DemandLevel PostalCode { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the Country of the user.
		/// </summary>
		public DemandLevel Country { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the language of the user.
		/// </summary>
		public DemandLevel Language { get; set; }

		/// <summary>
		/// Gets or sets the level of interest a relying party has in the time zone of the user.
		/// </summary>
		public DemandLevel TimeZone { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="ClaimsRequest"/> instance
		/// is synthesized from an AX request at the Provider.
		/// </summary>
		internal bool Synthesized { get; set; }

		/// <summary>
		/// Gets or sets the value of the sreg.required parameter.
		/// </summary>
		/// <value>A comma-delimited list of sreg fields.</value>
		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
		[MessagePart(Constants.required, AllowEmpty = true)]
		private string RequiredList {
			get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Require)); }
			set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Require); }
		}

		/// <summary>
		/// Gets or sets the value of the sreg.optional parameter.
		/// </summary>
		/// <value>A comma-delimited list of sreg fields.</value>
		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
		[MessagePart(Constants.optional, AllowEmpty = true)]
		private string OptionalList {
			get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Request)); }
			set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Request); }
		}

		/// <summary>
		/// Tests equality between two <see cref="ClaimsRequest"/> structs.
		/// </summary>
		/// <param name="one">One instance to compare.</param>
		/// <param name="other">Another instance to compare.</param>
		/// <returns>The result of the operator.</returns>
		public static bool operator ==(ClaimsRequest one, ClaimsRequest other) {
			return one.EqualsNullSafe(other);
		}

		/// <summary>
		/// Tests inequality between two <see cref="ClaimsRequest"/> structs.
		/// </summary>
		/// <param name="one">One instance to compare.</param>
		/// <param name="other">Another instance to compare.</param>
		/// <returns>The result of the operator.</returns>
		public static bool operator !=(ClaimsRequest one, ClaimsRequest other) {
			return !(one == other);
		}

		/// <summary>
		/// Tests equality between two <see cref="ClaimsRequest"/> structs.
		/// </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) {
			ClaimsRequest other = obj as ClaimsRequest;
			if (other == null) {
				return false;
			}

			return
				this.BirthDate.Equals(other.BirthDate) &&
				this.Country.Equals(other.Country) &&
				this.Language.Equals(other.Language) &&
				this.Email.Equals(other.Email) &&
				this.FullName.Equals(other.FullName) &&
				this.Gender.Equals(other.Gender) &&
				this.Nickname.Equals(other.Nickname) &&
				this.PostalCode.Equals(other.PostalCode) &&
				this.TimeZone.Equals(other.TimeZone) &&
				this.PolicyUrl.EqualsNullSafe(other.PolicyUrl);
		}

		/// <summary>
		/// Serves as a hash function for a particular type.
		/// </summary>
		/// <returns>
		/// A hash code for the current <see cref="T:System.Object"/>.
		/// </returns>
		public override int GetHashCode() {
			// It's important that if Equals returns true that the hash code also equals,
			// so returning base.GetHashCode() is a BAD option.
			// Return 1 is simple and poor for dictionary storage, but considering that every
			// ClaimsRequest formulated at a single RP will likely have all the same fields,
			// even a good hash code function will likely generate the same hash code.  So
			// we just cut to the chase and return a simple one.
			return 1;
		}

		/// <summary>
		/// Renders the requested information as a string.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString() {
			string format = @"Nickname = '{0}' 
Email = '{1}' 
FullName = '{2}' 
Birthdate = '{3}'
Gender = '{4}'
PostalCode = '{5}'
Country = '{6}'
Language = '{7}'
TimeZone = '{8}'";
			return string.Format(CultureInfo.CurrentCulture, format, this.Nickname, this.Email, this.FullName, this.BirthDate, this.Gender, this.PostalCode, this.Country, this.Language, this.TimeZone);
		}

		/// <summary>
		/// Prepares a Simple Registration response extension that is compatible with the
		/// version of Simple Registration used in the request message.
		/// </summary>
		/// <returns>The newly created <see cref="ClaimsResponse"/> instance.</returns>
		public ClaimsResponse CreateResponse() {
			if (this.typeUriDeserializedFrom == null) {
				throw new InvalidOperationException(OpenIdStrings.CallDeserializeBeforeCreateResponse);
			}

			return new ClaimsResponse(this.typeUriDeserializedFrom);
		}

		/// <summary>
		/// Sets the profile request properties according to a list of
		/// field names that might have been passed in the OpenId query dictionary.
		/// </summary>
		/// <param name="fieldNames">
		/// The list of field names that should receive a given 
		/// <paramref name="requestLevel"/>.  These field names should match 
		/// the OpenId specification for field names, omitting the 'openid.sreg' prefix.
		/// </param>
		/// <param name="requestLevel">The none/request/require state of the listed fields.</param>
		internal void SetProfileRequestFromList(IEnumerable<string> fieldNames, DemandLevel requestLevel) {
			foreach (string field in fieldNames) {
				switch (field) {
					case "": // this occurs for empty lists
						break;
					case Constants.nickname:
						this.Nickname = requestLevel;
						break;
					case Constants.email:
						this.Email = requestLevel;
						break;
					case Constants.fullname:
						this.FullName = requestLevel;
						break;
					case Constants.dob:
						this.BirthDate = requestLevel;
						break;
					case Constants.gender:
						this.Gender = requestLevel;
						break;
					case Constants.postcode:
						this.PostalCode = requestLevel;
						break;
					case Constants.country:
						this.Country = requestLevel;
						break;
					case Constants.language:
						this.Language = requestLevel;
						break;
					case Constants.timezone:
						this.TimeZone = requestLevel;
						break;
					default:
						Logger.OpenId.WarnFormat("ClaimsRequest.SetProfileRequestFromList: Unrecognized field name '{0}'.", field);
						break;
				}
			}
		}

		/// <summary>
		/// Assembles the profile parameter names that have a given <see cref="DemandLevel"/>.
		/// </summary>
		/// <param name="level">The demand level (request, require, none).</param>
		/// <returns>An array of the profile parameter names that meet the criteria.</returns>
		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
		private string[] AssembleProfileFields(DemandLevel level) {
			List<string> fields = new List<string>(10);
			if (this.Nickname == level) {
				fields.Add(Constants.nickname);
			} if (this.Email == level) {
				fields.Add(Constants.email);
			} if (this.FullName == level) {
				fields.Add(Constants.fullname);
			} if (this.BirthDate == level) {
				fields.Add(Constants.dob);
			} if (this.Gender == level) {
				fields.Add(Constants.gender);
			} if (this.PostalCode == level) {
				fields.Add(Constants.postcode);
			} if (this.Country == level) {
				fields.Add(Constants.country);
			} if (this.Language == level) {
				fields.Add(Constants.language);
			} if (this.TimeZone == level) {
				fields.Add(Constants.timezone);
			}

			return fields.ToArray();
		}
	}
}