1 //-----------------------------------------------------------------------
2 // <copyright file="ClaimsResponse.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.OpenId
.Extensions
.SimpleRegistration
{
9 using System
.Collections
.Generic
;
10 using System
.Diagnostics
.CodeAnalysis
;
11 using System
.Globalization
;
12 using System
.Net
.Mail
;
14 using System
.Text
.RegularExpressions
;
15 using System
.Xml
.Serialization
;
16 using DotNetOpenAuth
.Messaging
;
17 using DotNetOpenAuth
.OpenId
.Messages
;
20 /// A struct storing Simple Registration field values describing an
21 /// authenticating user.
23 public sealed class ClaimsResponse
: ExtensionBase
, IClientScriptExtensionResponse
{
25 /// The factory method that may be used in deserialization of this message.
27 internal static readonly OpenIdExtensionFactory
.CreateDelegate Factory
= (typeUri
, data
, baseMessage
) => {
28 if (typeUri
== Constants
.sreg_ns
&& baseMessage
is IndirectSignedResponse
) {
29 return new ClaimsResponse(typeUri
);
36 /// The allowed format for birthdates.
38 private static readonly Regex birthDateValidator
= new Regex(@"^\d\d\d\d-\d\d-\d\d$");
41 /// Storage for the raw string birthdate value.
43 private string birthDateRaw
;
46 /// Backing field for the <see cref="BirthDate"/> property.
48 private DateTime
? birthDate
;
51 /// Backing field for the <see cref="Culture"/> property.
53 private CultureInfo culture
;
56 /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
58 internal ClaimsResponse()
59 : this(Constants
.sreg_ns
) {
63 /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
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.
69 internal ClaimsResponse(string typeUriToUse
)
70 : base(new Version(1, 0), typeUriToUse
, EmptyList
<string>.Instance
) {
71 ErrorUtilities
.VerifyNonZeroLength(typeUriToUse
, "typeUriToUse");
75 /// Gets or sets the nickname the user goes by.
77 [MessagePart(Constants
.nickname
)]
78 public string Nickname { get; set; }
81 /// Gets or sets the user's email address.
83 [MessagePart(Constants
.email
)]
84 public string Email { get; set; }
87 /// Gets or sets the full name of a user as a single string.
89 [MessagePart(Constants
.fullname
)]
90 public string FullName { get; set; }
93 /// Gets or sets the user's birthdate.
95 public DateTime
? BirthDate
{
97 return this.birthDate
;
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
);
107 this.birthDateRaw
= null;
113 /// Gets or sets the raw birth date string given by the extension.
115 /// <value>A string in the format yyyy-MM-dd.</value>
116 [MessagePart(Constants
.dob
)]
117 public string BirthDateRaw
{
119 return this.birthDateRaw
;
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
;
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;
140 this.birthDate
= null;
143 this.birthDateRaw
= value;
148 /// Gets or sets the gender of the user.
150 [MessagePart(Constants
.gender
, Encoder
= typeof(GenderEncoder
))]
151 public Gender
? Gender { get; set; }
154 /// Gets or sets the zip code / postal code of the user.
156 [MessagePart(Constants
.postcode
)]
157 public string PostalCode { get; set; }
160 /// Gets or sets the country of the user.
162 [MessagePart(Constants
.country
)]
163 public string Country { get; set; }
166 /// Gets or sets the primary/preferred language of the user.
168 [MessagePart(Constants
.language
)]
169 public string Language { get; set; }
172 /// Gets or sets the user's timezone.
174 [MessagePart(Constants
.timezone
)]
175 public string TimeZone { get; set; }
178 /// Gets a combination of the user's full name and email address.
180 public MailAddress MailAddress
{
182 if (string.IsNullOrEmpty(this.Email
)) {
184 } else if (string.IsNullOrEmpty(this.FullName
)) {
185 return new MailAddress(this.Email
);
187 return new MailAddress(this.Email
, this.FullName
);
193 /// Gets or sets a combination o the language and country of the user.
196 public CultureInfo Culture
{
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
);
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;
219 /// Tests equality of two <see cref="ClaimsResponse"/> objects.
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
);
229 /// Tests inequality of two <see cref="ClaimsResponse"/> objects.
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
);
239 /// Tests equality of two <see cref="ClaimsResponse"/> objects.
241 /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
243 /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
245 /// <exception cref="T:System.NullReferenceException">
246 /// The <paramref name="obj"/> parameter is null.
248 public override bool Equals(object obj
) {
249 ClaimsResponse other
= obj
as ClaimsResponse
;
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
);
267 /// Serves as a hash function for a particular type.
270 /// A hash code for the current <see cref="T:System.Object"/>.
272 public override int GetHashCode() {
273 return (this.Nickname
!= null) ? this.Nickname
.GetHashCode() : base.GetHashCode();
276 #region IClientScriptExtension Members
279 /// Reads the extension information on an authentication response from the provider.
281 /// <param name="response">The incoming OpenID response carrying the extension.</param>
283 /// A Javascript snippet that when executed on the user agent returns an object with
284 /// the information deserialized from the extension response.
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.
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
);