summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs316
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs357
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs46
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs32
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs70
5 files changed, 821 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
new file mode 100644
index 0000000..18f63d4
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
@@ -0,0 +1,316 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClaimsRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <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.sreg_ns && 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.sreg_ns, 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();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
new file mode 100644
index 0000000..b50833b
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
@@ -0,0 +1,357 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClaimsResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Net.Mail;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Xml.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A struct storing Simple Registration field values describing an
+ /// authenticating user.
+ /// </summary>
+ [Serializable]
+ public sealed class ClaimsResponse : ExtensionBase, IClientScriptExtensionResponse, IMessageWithEvents {
+ /// <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.sreg_ns || Array.IndexOf(Constants.AdditionalTypeUris, typeUri) >= 0) && !isProviderRole) {
+ return new ClaimsResponse(typeUri);
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The allowed format for birthdates.
+ /// </summary>
+ private static readonly Regex birthDateValidator = new Regex(@"^\d\d\d\d-\d\d-\d\d$");
+
+ /// <summary>
+ /// Storage for the raw string birthdate value.
+ /// </summary>
+ private string birthDateRaw;
+
+ /// <summary>
+ /// Backing field for the <see cref="BirthDate"/> property.
+ /// </summary>
+ private DateTime? birthDate;
+
+ /// <summary>
+ /// Backing field for the <see cref="Culture"/> property.
+ /// </summary>
+ private CultureInfo culture;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
+ /// </summary>
+ internal ClaimsResponse()
+ : this(Constants.sreg_ns) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
+ /// </summary>
+ /// <param name="typeUriToUse">
+ /// The type URI that must be used to identify this extension in the response message.
+ /// This value should be the same one the relying party used to send the extension request.
+ /// </param>
+ internal ClaimsResponse(string typeUriToUse)
+ : base(new Version(1, 0), typeUriToUse, Constants.AdditionalTypeUris) {
+ Requires.NotNullOrEmpty(typeUriToUse, "typeUriToUse");
+ }
+
+ /// <summary>
+ /// Gets or sets the nickname the user goes by.
+ /// </summary>
+ [MessagePart(Constants.nickname)]
+ public string Nickname { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user's email address.
+ /// </summary>
+ [MessagePart(Constants.email)]
+ public string Email { get; set; }
+
+ /// <summary>
+ /// Gets or sets the full name of a user as a single string.
+ /// </summary>
+ [MessagePart(Constants.fullname)]
+ public string FullName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user's birthdate.
+ /// </summary>
+ public DateTime? BirthDate {
+ get {
+ return this.birthDate;
+ }
+
+ set {
+ this.birthDate = value;
+
+ // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors.
+ if (value.HasValue) {
+ this.birthDateRaw = value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
+ } else {
+ this.birthDateRaw = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the raw birth date string given by the extension.
+ /// </summary>
+ /// <value>A string in the format yyyy-MM-dd.</value>
+ [MessagePart(Constants.dob)]
+ public string BirthDateRaw {
+ get {
+ return this.birthDateRaw;
+ }
+
+ set {
+ ErrorUtilities.VerifyArgument(value == null || birthDateValidator.IsMatch(value), OpenIdStrings.SregInvalidBirthdate);
+ if (value != null) {
+ // Update the BirthDate property, if possible.
+ // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors.
+ // Some valid sreg dob values like "2000-00-00" will not work as a DateTime struct,
+ // in which case we null it out, but don't show any error.
+ DateTime newBirthDate;
+ if (DateTime.TryParse(value, out newBirthDate)) {
+ this.birthDate = newBirthDate;
+ } else {
+ Logger.OpenId.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);
+ this.birthDate = null;
+ }
+ } else {
+ this.birthDate = null;
+ }
+
+ this.birthDateRaw = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the gender of the user.
+ /// </summary>
+ [MessagePart(Constants.gender, Encoder = typeof(GenderEncoder))]
+ public Gender? Gender { get; set; }
+
+ /// <summary>
+ /// Gets or sets the zip code / postal code of the user.
+ /// </summary>
+ [MessagePart(Constants.postcode)]
+ public string PostalCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the country of the user.
+ /// </summary>
+ [MessagePart(Constants.country)]
+ public string Country { get; set; }
+
+ /// <summary>
+ /// Gets or sets the primary/preferred language of the user.
+ /// </summary>
+ [MessagePart(Constants.language)]
+ public string Language { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user's timezone.
+ /// </summary>
+ [MessagePart(Constants.timezone)]
+ public string TimeZone { get; set; }
+
+ /// <summary>
+ /// Gets a combination of the user's full name and email address.
+ /// </summary>
+ public MailAddress MailAddress {
+ get {
+ if (string.IsNullOrEmpty(this.Email)) {
+ return null;
+ } else if (string.IsNullOrEmpty(this.FullName)) {
+ return new MailAddress(this.Email);
+ } else {
+ return new MailAddress(this.Email, this.FullName);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a combination o the language and country of the user.
+ /// </summary>
+ [XmlIgnore]
+ public CultureInfo Culture {
+ get {
+ if (this.culture == null && !string.IsNullOrEmpty(this.Language)) {
+ string cultureString = string.Empty;
+ cultureString = this.Language;
+ if (!string.IsNullOrEmpty(this.Country)) {
+ cultureString += "-" + this.Country;
+ }
+ this.culture = CultureInfo.GetCultureInfo(cultureString);
+ }
+
+ return this.culture;
+ }
+
+ set {
+ this.culture = value;
+ this.Language = (value != null) ? value.TwoLetterISOLanguageName : null;
+ int indexOfHyphen = (value != null) ? value.Name.IndexOf('-') : -1;
+ this.Country = indexOfHyphen > 0 ? value.Name.Substring(indexOfHyphen + 1) : null;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this extension is signed by the Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByProvider {
+ get { return this.IsSignedByRemoteParty; }
+ }
+
+ /// <summary>
+ /// Tests equality of two <see cref="ClaimsResponse"/> objects.
+ /// </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 ==(ClaimsResponse one, ClaimsResponse other) {
+ return one.EqualsNullSafe(other);
+ }
+
+ /// <summary>
+ /// Tests inequality of two <see cref="ClaimsResponse"/> objects.
+ /// </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 !=(ClaimsResponse one, ClaimsResponse other) {
+ return !(one == other);
+ }
+
+ /// <summary>
+ /// Tests equality of two <see cref="ClaimsResponse"/> objects.
+ /// </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) {
+ ClaimsResponse other = obj as ClaimsResponse;
+ if (other == null) {
+ return false;
+ }
+
+ return
+ this.BirthDateRaw.EqualsNullSafe(other.BirthDateRaw) &&
+ this.Country.EqualsNullSafe(other.Country) &&
+ this.Language.EqualsNullSafe(other.Language) &&
+ this.Email.EqualsNullSafe(other.Email) &&
+ this.FullName.EqualsNullSafe(other.FullName) &&
+ this.Gender.Equals(other.Gender) &&
+ this.Nickname.EqualsNullSafe(other.Nickname) &&
+ this.PostalCode.EqualsNullSafe(other.PostalCode) &&
+ this.TimeZone.EqualsNullSafe(other.TimeZone);
+ }
+
+ /// <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() {
+ return (this.Nickname != null) ? this.Nickname.GetHashCode() : base.GetHashCode();
+ }
+
+ #region IClientScriptExtension Members
+
+ /// <summary>
+ /// Reads the extension information on an authentication response from the provider.
+ /// </summary>
+ /// <param name="response">The incoming OpenID response carrying the extension.</param>
+ /// <returns>
+ /// A Javascript snippet that when executed on the user agent returns an object with
+ /// the information deserialized from the extension response.
+ /// </returns>
+ /// <remarks>
+ /// This method is called <b>before</b> the signature on the assertion response has been
+ /// verified. Therefore all information in these fields should be assumed unreliable
+ /// and potentially falsified.
+ /// </remarks>
+ string IClientScriptExtensionResponse.InitializeJavaScriptData(IProtocolMessageWithExtensions response) {
+ var sreg = new Dictionary<string, string>(15);
+
+ // Although we could probably whip up a trip with MessageDictionary
+ // to avoid explicitly setting each field, doing so would likely
+ // open ourselves up to security exploits from the OP as it would
+ // make possible sending arbitrary javascript in arbitrary field names.
+ sreg[Constants.nickname] = this.Nickname;
+ sreg[Constants.email] = this.Email;
+ sreg[Constants.fullname] = this.FullName;
+ sreg[Constants.dob] = this.BirthDateRaw;
+ sreg[Constants.gender] = this.Gender.HasValue ? this.Gender.Value.ToString() : null;
+ sreg[Constants.postcode] = this.PostalCode;
+ sreg[Constants.country] = this.Country;
+ sreg[Constants.language] = this.Language;
+ sreg[Constants.timezone] = this.TimeZone;
+
+ return MessagingUtilities.CreateJsonObject(sreg, false);
+ }
+
+ #endregion
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ // Null out empty values so we don't send out a lot of empty parameters.
+ this.Country = EmptyToNull(this.Country);
+ this.Email = EmptyToNull(this.Email);
+ this.FullName = EmptyToNull(this.FullName);
+ this.Language = EmptyToNull(this.Language);
+ this.Nickname = EmptyToNull(this.Nickname);
+ this.PostalCode = EmptyToNull(this.PostalCode);
+ this.TimeZone = EmptyToNull(this.TimeZone);
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Translates an empty string value to null, or passes through non-empty values.
+ /// </summary>
+ /// <param name="value">The value to consider changing to null.</param>
+ /// <returns>Either null or a non-empty string.</returns>
+ private static string EmptyToNull(string value) {
+ return string.IsNullOrEmpty(value) ? null : value;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs
new file mode 100644
index 0000000..9e00137
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs
@@ -0,0 +1,46 @@
+// <auto-generated/> // disable StyleCop on this file
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// Simple Registration constants
+ /// </summary>
+ internal static class Constants {
+ internal const string sreg_ns = "http://openid.net/extensions/sreg/1.1";
+ internal const string sreg_ns10 = "http://openid.net/sreg/1.0";
+ internal const string sreg_ns11other = "http://openid.net/sreg/1.1";
+ internal const string sreg_compatibility_alias = "sreg";
+ internal const string policy_url = "policy_url";
+ internal const string optional = "optional";
+ internal const string required = "required";
+ internal const string nickname = "nickname";
+ internal const string email = "email";
+ internal const string fullname = "fullname";
+ internal const string dob = "dob";
+ internal const string gender = "gender";
+ internal const string postcode = "postcode";
+ internal const string country = "country";
+ internal const string language = "language";
+ internal const string timezone = "timezone";
+ internal static class Genders {
+ internal const string Male = "M";
+ internal const string Female = "F";
+ }
+
+ /// <summary>
+ /// Additional type URIs that this extension is sometimes known by remote parties.
+ /// </summary>
+ internal static readonly string[] AdditionalTypeUris = new string[] {
+ Constants.sreg_ns10,
+ Constants.sreg_ns11other,
+ };
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs
new file mode 100644
index 0000000..7129270
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="DemandLevel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ /// <summary>
+ /// Specifies what level of interest a relying party has in obtaining the value
+ /// of a given field offered by the Simple Registration extension.
+ /// </summary>
+ public enum DemandLevel {
+ /// <summary>
+ /// The relying party has no interest in obtaining this field.
+ /// </summary>
+ NoRequest,
+
+ /// <summary>
+ /// The relying party would like the value of this field, but wants
+ /// the Provider to display the field to the user as optionally provided.
+ /// </summary>
+ Request,
+
+ /// <summary>
+ /// The relying party considers this a required field as part of
+ /// authentication. The Provider and/or user agent MAY still choose to
+ /// not provide the value of the field however, according to the
+ /// Simple Registration extension specification.
+ /// </summary>
+ Require,
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs
new file mode 100644
index 0000000..979c481
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------
+// <copyright file="Gender.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// Indicates the gender of a user.
+ /// </summary>
+ public enum Gender {
+ /// <summary>
+ /// The user is male.
+ /// </summary>
+ Male,
+
+ /// <summary>
+ /// The user is female.
+ /// </summary>
+ Female,
+ }
+
+ /// <summary>
+ /// Encodes/decodes the Simple Registration Gender type to its string representation.
+ /// </summary>
+ internal class GenderEncoder : IMessagePartEncoder {
+ #region IMessagePartEncoder Members
+
+ /// <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) {
+ var gender = (Gender?)value;
+ if (gender.HasValue) {
+ switch (gender.Value) {
+ case Gender.Male: return Constants.Genders.Male;
+ case Gender.Female: return Constants.Genders.Female;
+ }
+ }
+
+ return null;
+ }
+
+ /// <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) {
+ switch (value) {
+ case Constants.Genders.Male: return SimpleRegistration.Gender.Male;
+ case Constants.Genders.Female: return SimpleRegistration.Gender.Female;
+ default: throw new FormatException();
+ }
+ }
+
+ #endregion
+ }
+} \ No newline at end of file