//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- 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; /// /// Carries the request/require/none demand state of the simple registration fields. /// [Serializable] public sealed class ClaimsRequest : ExtensionBase { /// /// The factory method that may be used in deserialization of this message. /// internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { if (typeUri == Constants.TypeUris.Standard && isProviderRole) { return new ClaimsRequest(typeUri); } return null; }; /// /// The type URI that this particular (deserialized) extension was read in using, /// allowing a response to alter be crafted using the same type URI. /// private string typeUriDeserializedFrom; /// /// Initializes a new instance of the class. /// public ClaimsRequest() : base(new Version(1, 0), Constants.TypeUris.Standard, Constants.AdditionalTypeUris) { } /// /// Initializes a new instance of the class /// by deserializing from a message. /// /// The type URI this extension was recognized by in the OpenID message. internal ClaimsRequest(string typeUri) : this() { Requires.NotNullOrEmpty(typeUri, "typeUri"); this.typeUriDeserializedFrom = typeUri; } /// /// 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. /// [MessagePart(Constants.policy_url, IsRequired = false)] public Uri PolicyUrl { get; set; } /// /// Gets or sets the level of interest a relying party has in the nickname of the user. /// public DemandLevel Nickname { get; set; } /// /// Gets or sets the level of interest a relying party has in the email of the user. /// public DemandLevel Email { get; set; } /// /// Gets or sets the level of interest a relying party has in the full name of the user. /// public DemandLevel FullName { get; set; } /// /// Gets or sets the level of interest a relying party has in the birthdate of the user. /// public DemandLevel BirthDate { get; set; } /// /// Gets or sets the level of interest a relying party has in the gender of the user. /// public DemandLevel Gender { get; set; } /// /// Gets or sets the level of interest a relying party has in the postal code of the user. /// public DemandLevel PostalCode { get; set; } /// /// Gets or sets the level of interest a relying party has in the Country of the user. /// public DemandLevel Country { get; set; } /// /// Gets or sets the level of interest a relying party has in the language of the user. /// public DemandLevel Language { get; set; } /// /// Gets or sets the level of interest a relying party has in the time zone of the user. /// public DemandLevel TimeZone { get; set; } /// /// Gets or sets a value indicating whether this instance /// is synthesized from an AX request at the Provider. /// internal bool Synthesized { get; set; } /// /// Gets or sets the value of the sreg.required parameter. /// /// A comma-delimited list of sreg fields. [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); } } /// /// Gets or sets the value of the sreg.optional parameter. /// /// A comma-delimited list of sreg fields. [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); } } /// /// Tests equality between two structs. /// /// One instance to compare. /// Another instance to compare. /// The result of the operator. public static bool operator ==(ClaimsRequest one, ClaimsRequest other) { return one.EqualsNullSafe(other); } /// /// Tests inequality between two structs. /// /// One instance to compare. /// Another instance to compare. /// The result of the operator. public static bool operator !=(ClaimsRequest one, ClaimsRequest other) { return !(one == other); } /// /// Tests equality between two structs. /// /// The to compare with the current . /// /// true if the specified is equal to the current ; otherwise, false. /// /// /// The parameter is null. /// 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); } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// 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; } /// /// Renders the requested information as a string. /// /// /// A that represents the current . /// 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); } /// /// Prepares a Simple Registration response extension that is compatible with the /// version of Simple Registration used in the request message. /// /// The newly created instance. public ClaimsResponse CreateResponse() { if (this.typeUriDeserializedFrom == null) { throw new InvalidOperationException(OpenIdStrings.CallDeserializeBeforeCreateResponse); } return new ClaimsResponse(this.typeUriDeserializedFrom); } /// /// Sets the profile request properties according to a list of /// field names that might have been passed in the OpenId query dictionary. /// /// /// The list of field names that should receive a given /// . These field names should match /// the OpenId specification for field names, omitting the 'openid.sreg' prefix. /// /// The none/request/require state of the listed fields. internal void SetProfileRequestFromList(IEnumerable 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; } } } /// /// Assembles the profile parameter names that have a given . /// /// The demand level (request, require, none). /// An array of the profile parameter names that meet the criteria. [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")] private string[] AssembleProfileFields(DemandLevel level) { List fields = new List(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(); } } }