FxCop fixes.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / Extensions / SimpleRegistration / ClaimsResponse.cs
blob7cd8efc723702c7ef79381a2388910eb2f84c906
1 //-----------------------------------------------------------------------
2 // <copyright file="ClaimsResponse.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
8 using System;
9 using System.Collections.Generic;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Globalization;
12 using System.Net.Mail;
13 using System.Text;
14 using System.Text.RegularExpressions;
15 using System.Xml.Serialization;
16 using DotNetOpenAuth.Messaging;
17 using DotNetOpenAuth.OpenId.Messages;
19 /// <summary>
20 /// A struct storing Simple Registration field values describing an
21 /// authenticating user.
22 /// </summary>
23 public sealed class ClaimsResponse : ExtensionBase, IClientScriptExtensionResponse {
24 /// <summary>
25 /// The factory method that may be used in deserialization of this message.
26 /// </summary>
27 internal static readonly OpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage) => {
28 if (typeUri == Constants.sreg_ns && baseMessage is IndirectSignedResponse) {
29 return new ClaimsResponse(typeUri);
32 return null;
35 /// <summary>
36 /// The allowed format for birthdates.
37 /// </summary>
38 private static readonly Regex birthDateValidator = new Regex(@"^\d\d\d\d-\d\d-\d\d$");
40 /// <summary>
41 /// Storage for the raw string birthdate value.
42 /// </summary>
43 private string birthDateRaw;
45 /// <summary>
46 /// Backing field for the <see cref="BirthDate"/> property.
47 /// </summary>
48 private DateTime? birthDate;
50 /// <summary>
51 /// Backing field for the <see cref="Culture"/> property.
52 /// </summary>
53 private CultureInfo culture;
55 /// <summary>
56 /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
57 /// </summary>
58 internal ClaimsResponse()
59 : this(Constants.sreg_ns) {
62 /// <summary>
63 /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
64 /// </summary>
65 /// <param name="typeUriToUse">
66 /// The type URI that must be used to identify this extension in the response message.
67 /// This value should be the same one the relying party used to send the extension request.
68 /// </param>
69 internal ClaimsResponse(string typeUriToUse)
70 : base(new Version(1, 0), typeUriToUse, EmptyList<string>.Instance) {
71 ErrorUtilities.VerifyNonZeroLength(typeUriToUse, "typeUriToUse");
74 /// <summary>
75 /// Gets or sets the nickname the user goes by.
76 /// </summary>
77 [MessagePart(Constants.nickname)]
78 public string Nickname { get; set; }
80 /// <summary>
81 /// Gets or sets the user's email address.
82 /// </summary>
83 [MessagePart(Constants.email)]
84 public string Email { get; set; }
86 /// <summary>
87 /// Gets or sets the full name of a user as a single string.
88 /// </summary>
89 [MessagePart(Constants.fullname)]
90 public string FullName { get; set; }
92 /// <summary>
93 /// Gets or sets the user's birthdate.
94 /// </summary>
95 public DateTime? BirthDate {
96 get {
97 return this.birthDate;
100 set {
101 this.birthDate = value;
103 // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors.
104 if (value.HasValue) {
105 this.birthDateRaw = value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
106 } else {
107 this.birthDateRaw = null;
112 /// <summary>
113 /// Gets or sets the raw birth date string given by the extension.
114 /// </summary>
115 /// <value>A string in the format yyyy-MM-dd.</value>
116 [MessagePart(Constants.dob)]
117 public string BirthDateRaw {
118 get {
119 return this.birthDateRaw;
122 set {
123 if (value != null) {
124 if (!birthDateValidator.IsMatch(value)) {
125 throw new ArgumentException(OpenIdStrings.SregInvalidBirthdate, "value");
128 // Update the BirthDate property, if possible.
129 // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors.
130 // Some valid sreg dob values like "2000-00-00" will not work as a DateTime struct,
131 // in which case we null it out, but don't show any error.
132 DateTime newBirthDate;
133 if (DateTime.TryParse(value, out newBirthDate)) {
134 this.birthDate = newBirthDate;
135 } else {
136 Logger.WarnFormat("Simple Registration birthdate '{0}' could not be parsed into a DateTime and may not include month and/or day information. Setting BirthDate property to null.", value);
137 this.birthDate = null;
139 } else {
140 this.birthDate = null;
143 this.birthDateRaw = value;
147 /// <summary>
148 /// Gets or sets the gender of the user.
149 /// </summary>
150 [MessagePart(Constants.gender, Encoder = typeof(GenderEncoder))]
151 public Gender? Gender { get; set; }
153 /// <summary>
154 /// Gets or sets the zip code / postal code of the user.
155 /// </summary>
156 [MessagePart(Constants.postcode)]
157 public string PostalCode { get; set; }
159 /// <summary>
160 /// Gets or sets the country of the user.
161 /// </summary>
162 [MessagePart(Constants.country)]
163 public string Country { get; set; }
165 /// <summary>
166 /// Gets or sets the primary/preferred language of the user.
167 /// </summary>
168 [MessagePart(Constants.language)]
169 public string Language { get; set; }
171 /// <summary>
172 /// Gets or sets the user's timezone.
173 /// </summary>
174 [MessagePart(Constants.timezone)]
175 public string TimeZone { get; set; }
177 /// <summary>
178 /// Gets a combination of the user's full name and email address.
179 /// </summary>
180 public MailAddress MailAddress {
181 get {
182 if (string.IsNullOrEmpty(this.Email)) {
183 return null;
184 } else if (string.IsNullOrEmpty(this.FullName)) {
185 return new MailAddress(this.Email);
186 } else {
187 return new MailAddress(this.Email, this.FullName);
192 /// <summary>
193 /// Gets or sets a combination o the language and country of the user.
194 /// </summary>
195 [XmlIgnore]
196 public CultureInfo Culture {
197 get {
198 if (this.culture == null && !string.IsNullOrEmpty(this.Language)) {
199 string cultureString = string.Empty;
200 cultureString = this.Language;
201 if (!string.IsNullOrEmpty(this.Country)) {
202 cultureString += "-" + this.Country;
204 this.culture = CultureInfo.GetCultureInfo(cultureString);
207 return this.culture;
210 set {
211 this.culture = value;
212 this.Language = (value != null) ? value.TwoLetterISOLanguageName : null;
213 int indexOfHyphen = (value != null) ? value.Name.IndexOf('-') : -1;
214 this.Country = indexOfHyphen > 0 ? value.Name.Substring(indexOfHyphen + 1) : null;
218 /// <summary>
219 /// Tests equality of two <see cref="ClaimsResponse"/> objects.
220 /// </summary>
221 /// <param name="one">One instance to compare.</param>
222 /// <param name="other">Another instance to compare.</param>
223 /// <returns>The result of the operator.</returns>
224 public static bool operator ==(ClaimsResponse one, ClaimsResponse other) {
225 return one.EqualsNullSafe(other);
228 /// <summary>
229 /// Tests inequality of two <see cref="ClaimsResponse"/> objects.
230 /// </summary>
231 /// <param name="one">One instance to compare.</param>
232 /// <param name="other">Another instance to compare.</param>
233 /// <returns>The result of the operator.</returns>
234 public static bool operator !=(ClaimsResponse one, ClaimsResponse other) {
235 return !(one == other);
238 /// <summary>
239 /// Tests equality of two <see cref="ClaimsResponse"/> objects.
240 /// </summary>
241 /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
242 /// <returns>
243 /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
244 /// </returns>
245 /// <exception cref="T:System.NullReferenceException">
246 /// The <paramref name="obj"/> parameter is null.
247 /// </exception>
248 public override bool Equals(object obj) {
249 ClaimsResponse other = obj as ClaimsResponse;
250 if (other == null) {
251 return false;
254 return
255 this.BirthDateRaw.EqualsNullSafe(other.BirthDateRaw) &&
256 this.Country.EqualsNullSafe(other.Country) &&
257 this.Language.EqualsNullSafe(other.Language) &&
258 this.Email.EqualsNullSafe(other.Email) &&
259 this.FullName.EqualsNullSafe(other.FullName) &&
260 this.Gender.Equals(other.Gender) &&
261 this.Nickname.EqualsNullSafe(other.Nickname) &&
262 this.PostalCode.EqualsNullSafe(other.PostalCode) &&
263 this.TimeZone.EqualsNullSafe(other.TimeZone);
266 /// <summary>
267 /// Serves as a hash function for a particular type.
268 /// </summary>
269 /// <returns>
270 /// A hash code for the current <see cref="T:System.Object"/>.
271 /// </returns>
272 public override int GetHashCode() {
273 return (this.Nickname != null) ? this.Nickname.GetHashCode() : base.GetHashCode();
276 #region IClientScriptExtension Members
278 /// <summary>
279 /// Reads the extension information on an authentication response from the provider.
280 /// </summary>
281 /// <param name="response">The incoming OpenID response carrying the extension.</param>
282 /// <returns>
283 /// A Javascript snippet that when executed on the user agent returns an object with
284 /// the information deserialized from the extension response.
285 /// </returns>
286 /// <remarks>
287 /// This method is called <b>before</b> the signature on the assertion response has been
288 /// verified. Therefore all information in these fields should be assumed unreliable
289 /// and potentially falsified.
290 /// </remarks>
291 string IClientScriptExtensionResponse.InitializeJavaScriptData(IProtocolMessageWithExtensions response) {
292 var sreg = new Dictionary<string, string>(15);
294 // Although we could probably whip up a trip with MessageDictionary
295 // to avoid explicitly setting each field, doing so would likely
296 // open ourselves up to security exploits from the OP as it would
297 // make possible sending arbitrary javascript in arbitrary field names.
298 sreg[Constants.nickname] = this.Nickname;
299 sreg[Constants.email] = this.Email;
300 sreg[Constants.fullname] = this.FullName;
301 sreg[Constants.dob] = this.BirthDateRaw;
302 sreg[Constants.gender] = this.Gender.HasValue ? this.Gender.Value.ToString() : null;
303 sreg[Constants.postcode] = this.PostalCode;
304 sreg[Constants.country] = this.Country;
305 sreg[Constants.language] = this.Language;
306 sreg[Constants.timezone] = this.TimeZone;
308 return MessagingUtilities.CreateJsonObject(sreg);
311 #endregion