summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs45
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs144
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs156
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs114
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs18
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs285
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs207
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs127
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs164
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs323
10 files changed, 1583 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs
new file mode 100644
index 0000000..decd296
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------
+// <copyright file="AXAttributeFormats.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+
+ /// <summary>
+ /// The various Type URI formats an AX attribute may use by various remote parties.
+ /// </summary>
+ [Flags]
+ public enum AXAttributeFormats {
+ /// <summary>
+ /// No attribute format.
+ /// </summary>
+ None = 0x0,
+
+ /// <summary>
+ /// AX attributes should use the Type URI format starting with <c>http://axschema.org/</c>.
+ /// </summary>
+ AXSchemaOrg = 0x1,
+
+ /// <summary>
+ /// AX attributes should use the Type URI format starting with <c>http://schema.openid.net/</c>.
+ /// </summary>
+ SchemaOpenIdNet = 0x2,
+
+ /// <summary>
+ /// AX attributes should use the Type URI format starting with <c>http://openid.net/schema/</c>.
+ /// </summary>
+ OpenIdNetSchema = 0x4,
+
+ /// <summary>
+ /// All known schemas.
+ /// </summary>
+ All = AXSchemaOrg | SchemaOpenIdNet | OpenIdNetSchema,
+
+ /// <summary>
+ /// The most common schemas.
+ /// </summary>
+ Common = AXSchemaOrg | SchemaOpenIdNet,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs
new file mode 100644
index 0000000..96cc437
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs
@@ -0,0 +1,144 @@
+//-----------------------------------------------------------------------
+// <copyright file="AXUtilities.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Helper methods shared by multiple messages in the Attribute Exchange extension.
+ /// </summary>
+ public static class AXUtilities {
+ /// <summary>
+ /// Adds a request for an attribute considering it 'required'.
+ /// </summary>
+ /// <param name="collection">The attribute request collection.</param>
+ /// <param name="typeUri">The type URI of the required attribute.</param>
+ public static void AddRequired(this ICollection<AttributeRequest> collection, string typeUri) {
+ Requires.NotNull(collection, "collection");
+ collection.Add(new AttributeRequest(typeUri, true));
+ }
+
+ /// <summary>
+ /// Adds a request for an attribute without considering it 'required'.
+ /// </summary>
+ /// <param name="collection">The attribute request collection.</param>
+ /// <param name="typeUri">The type URI of the requested attribute.</param>
+ public static void AddOptional(this ICollection<AttributeRequest> collection, string typeUri) {
+ Requires.NotNull(collection, "collection");
+ collection.Add(new AttributeRequest(typeUri, false));
+ }
+
+ /// <summary>
+ /// Adds a given attribute with one or more values to the request for storage.
+ /// Applicable to Relying Parties only.
+ /// </summary>
+ /// <param name="collection">The collection of <see cref="AttributeValues"/> to add to.</param>
+ /// <param name="typeUri">The type URI of the attribute.</param>
+ /// <param name="values">The attribute values.</param>
+ public static void Add(this ICollection<AttributeValues> collection, string typeUri, params string[] values) {
+ Requires.NotNull(collection, "collection");
+ collection.Add(new AttributeValues(typeUri, values));
+ }
+
+ /// <summary>
+ /// Serializes a set of attribute values to a dictionary of fields to send in the message.
+ /// </summary>
+ /// <param name="fields">The dictionary to fill with serialized attributes.</param>
+ /// <param name="attributes">The attributes.</param>
+ internal static void SerializeAttributes(IDictionary<string, string> fields, IEnumerable<AttributeValues> attributes) {
+ Requires.NotNull(fields, "fields");
+ Requires.NotNull(attributes, "attributes");
+
+ AliasManager aliasManager = new AliasManager();
+ foreach (var att in attributes) {
+ string alias = aliasManager.GetAlias(att.TypeUri);
+ fields.Add("type." + alias, att.TypeUri);
+ if (att.Values == null) {
+ continue;
+ }
+ if (att.Values.Count != 1) {
+ fields.Add("count." + alias, att.Values.Count.ToString(CultureInfo.InvariantCulture));
+ for (int i = 0; i < att.Values.Count; i++) {
+ fields.Add(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i + 1), att.Values[i]);
+ }
+ } else {
+ fields.Add("value." + alias, att.Values[0]);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Deserializes attribute values from an incoming set of message data.
+ /// </summary>
+ /// <param name="fields">The data coming in with the message.</param>
+ /// <returns>The attribute values found in the message.</returns>
+ internal static IEnumerable<AttributeValues> DeserializeAttributes(IDictionary<string, string> fields) {
+ AliasManager aliasManager = ParseAliases(fields);
+ foreach (string alias in aliasManager.Aliases) {
+ AttributeValues att = new AttributeValues(aliasManager.ResolveAlias(alias));
+ int count = 1;
+ bool countSent = false;
+ string countString;
+ if (fields.TryGetValue("count." + alias, out countString)) {
+ if (!int.TryParse(countString, out count) || count < 0) {
+ Logger.OpenId.ErrorFormat("Failed to parse count.{0} value to a non-negative integer.", alias);
+ continue;
+ }
+ countSent = true;
+ }
+ if (countSent) {
+ for (int i = 1; i <= count; i++) {
+ string value;
+ if (fields.TryGetValue(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i), out value)) {
+ att.Values.Add(value);
+ } else {
+ Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri);
+ continue;
+ }
+ }
+ } else {
+ string value;
+ if (fields.TryGetValue("value." + alias, out value)) {
+ att.Values.Add(value);
+ } else {
+ Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri);
+ continue;
+ }
+ }
+ yield return att;
+ }
+ }
+
+ /// <summary>
+ /// Reads through the attributes included in the response to discover
+ /// the alias-TypeURI relationships.
+ /// </summary>
+ /// <param name="fields">The data included in the extension message.</param>
+ /// <returns>The alias manager that provides lookup between aliases and type URIs.</returns>
+ private static AliasManager ParseAliases(IDictionary<string, string> fields) {
+ Requires.NotNull(fields, "fields");
+
+ AliasManager aliasManager = new AliasManager();
+ const string TypePrefix = "type.";
+ foreach (var pair in fields) {
+ if (!pair.Key.StartsWith(TypePrefix, StringComparison.Ordinal)) {
+ continue;
+ }
+ string alias = pair.Key.Substring(TypePrefix.Length);
+ if (alias.IndexOfAny(FetchRequest.IllegalAliasCharacters) >= 0) {
+ Logger.OpenId.ErrorFormat("Illegal characters in alias name '{0}'.", alias);
+ continue;
+ }
+ aliasManager.SetAlias(alias, pair.Value);
+ }
+ return aliasManager;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs
new file mode 100644
index 0000000..cbcbff6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs
@@ -0,0 +1,156 @@
+//-----------------------------------------------------------------------
+// <copyright file="AttributeRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An individual attribute to be requested of the OpenID Provider using
+ /// the Attribute Exchange extension.
+ /// </summary>
+ [Serializable]
+ [DebuggerDisplay("{TypeUri} (required: {IsRequired}) ({Count})")]
+ public class AttributeRequest {
+ /// <summary>
+ /// Backing field for the <see cref="Count"/> property.
+ /// </summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private int count = 1;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class
+ /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1.
+ /// </summary>
+ public AttributeRequest() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class
+ /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1.
+ /// </summary>
+ /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param>
+ public AttributeRequest(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+ this.TypeUri = typeUri;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class
+ /// with <see cref="Count"/> = 1.
+ /// </summary>
+ /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param>
+ /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param>
+ public AttributeRequest(string typeUri, bool isRequired)
+ : this(typeUri) {
+ this.IsRequired = isRequired;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class.
+ /// </summary>
+ /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param>
+ /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param>
+ /// <param name="count">The maximum number of values for this attribute the Relying Party is prepared to receive.</param>
+ public AttributeRequest(string typeUri, bool isRequired, int count)
+ : this(typeUri, isRequired) {
+ this.Count = count;
+ }
+
+ /// <summary>
+ /// Gets or sets the URI uniquely identifying the attribute being requested.
+ /// </summary>
+ public string TypeUri { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the relying party considers this a required field.
+ /// Note that even if set to true, the Provider may not provide the value.
+ /// </summary>
+ public bool IsRequired { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum number of values for this attribute the
+ /// Relying Party wishes to receive from the OpenID Provider.
+ /// A value of int.MaxValue is considered infinity.
+ /// </summary>
+ public int Count {
+ get {
+ return this.count;
+ }
+
+ set {
+ Requires.InRange(value > 0, "value");
+ this.count = value;
+ }
+ }
+
+ /// <summary>
+ /// Used by a Provider to create a response to a request for an attribute's value(s)
+ /// using a given array of strings.
+ /// </summary>
+ /// <param name="values">The values for the requested attribute.</param>
+ /// <returns>
+ /// The newly created <see cref="AttributeValues"/> object that should be added to
+ /// the <see cref="FetchResponse"/> object.
+ /// </returns>
+ public AttributeValues Respond(params string[] values) {
+ Requires.NotNull(values, "values");
+ Requires.True(values.Length <= this.Count, "values");
+ return new AttributeValues(this.TypeUri, values);
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </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) {
+ AttributeRequest other = obj as AttributeRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.TypeUri != other.TypeUri) {
+ return false;
+ }
+
+ if (this.Count != other.Count) {
+ return false;
+ }
+
+ if (this.IsRequired != other.IsRequired) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <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() {
+ int hashCode = this.IsRequired ? 1 : 0;
+ unchecked {
+ hashCode += this.Count;
+ if (this.TypeUri != null) {
+ hashCode += this.TypeUri.GetHashCode();
+ }
+ }
+
+ return hashCode;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs
new file mode 100644
index 0000000..37ebe38
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs
@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------
+// <copyright file="AttributeValues.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An individual attribute's value(s) as supplied by an OpenID Provider
+ /// in response to a prior request by an OpenID Relying Party as part of
+ /// a fetch request, or by a relying party as part of a store request.
+ /// </summary>
+ [Serializable]
+ [DebuggerDisplay("{TypeUri}")]
+ public class AttributeValues {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeValues"/> class.
+ /// </summary>
+ /// <param name="typeUri">The TypeURI that uniquely identifies the attribute.</param>
+ /// <param name="values">The values for the attribute.</param>
+ public AttributeValues(string typeUri, params string[] values) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+
+ this.TypeUri = typeUri;
+ this.Values = (IList<string>)values ?? EmptyList<string>.Instance;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeValues"/> class.
+ /// </summary>
+ /// <remarks>
+ /// This is internal because web sites should be using the
+ /// <see cref="AttributeRequest.Respond"/> method to instantiate.
+ /// </remarks>
+ internal AttributeValues() {
+ this.Values = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeValues"/> class.
+ /// </summary>
+ /// <param name="typeUri">The TypeURI of the attribute whose values are being provided.</param>
+ internal AttributeValues(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+
+ this.TypeUri = typeUri;
+ this.Values = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Gets the URI uniquely identifying the attribute whose value is being supplied.
+ /// </summary>
+ public string TypeUri { get; internal set; }
+
+ /// <summary>
+ /// Gets the values supplied by the Provider.
+ /// </summary>
+ public IList<string> Values { get; private set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </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) {
+ AttributeValues other = obj as AttributeValues;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.TypeUri != other.TypeUri) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalent<string>(this.Values, other.Values)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <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() {
+ int hashCode = 0;
+ unchecked {
+ if (this.TypeUri != null) {
+ hashCode += this.TypeUri.GetHashCode();
+ }
+
+ foreach (string value in this.Values) {
+ hashCode += value.GetHashCode();
+ }
+ }
+
+ return hashCode;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs
new file mode 100644
index 0000000..167d0d2
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ /// <summary>
+ /// Attribute Exchange constants
+ /// </summary>
+ internal static class Constants {
+ /// <summary>
+ /// The TypeURI by which the AX extension is recognized in
+ /// OpenID messages and in XRDS documents.
+ /// </summary>
+ internal const string TypeUri = "http://openid.net/srv/ax/1.0";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs
new file mode 100644
index 0000000..124a18c
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs
@@ -0,0 +1,285 @@
+//-----------------------------------------------------------------------
+// <copyright file="FetchRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Fetch message, request leg.
+ /// </summary>
+ [Serializable]
+ public sealed class FetchRequest : ExtensionBase, 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.TypeUri && isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && mode == Mode) {
+ return new FetchRequest();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// Characters that may not appear in an attribute alias list.
+ /// </summary>
+ internal static readonly char[] IllegalAliasListCharacters = new[] { '.', '\n' };
+
+ /// <summary>
+ /// Characters that may not appear in an attribute Type URI alias.
+ /// </summary>
+ internal static readonly char[] IllegalAliasCharacters = new[] { '.', ',', ':' };
+
+ /// <summary>
+ /// The value for the 'mode' parameter.
+ /// </summary>
+ [MessagePart("mode", IsRequired = true)]
+ private const string Mode = "fetch_request";
+
+ /// <summary>
+ /// The collection of requested attributes.
+ /// </summary>
+ private readonly KeyedCollection<string, AttributeRequest> attributes = new KeyedCollectionDelegate<string, AttributeRequest>(ar => ar.TypeUri);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FetchRequest"/> class.
+ /// </summary>
+ public FetchRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets a collection of the attributes whose values are
+ /// requested by the Relying Party.
+ /// </summary>
+ /// <value>A collection where the keys are the attribute type URIs, and the value
+ /// is all the attribute request details.</value>
+ public KeyedCollection<string, AttributeRequest> Attributes {
+ get {
+ Contract.Ensures(Contract.Result<KeyedCollection<string, AttributeRequest>>() != null);
+ return this.attributes;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL that the OpenID Provider may re-post the fetch response
+ /// message to at some time after the initial response has been sent, using an
+ /// OpenID Authentication Positive Assertion to inform the relying party of updates
+ /// to the requested fields.
+ /// </summary>
+ [MessagePart("update_url", IsRequired = false)]
+ public Uri UpdateUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a list of aliases for optional attributes.
+ /// </summary>
+ /// <value>A comma-delimited list of aliases.</value>
+ [MessagePart("if_available", IsRequired = false)]
+ private string OptionalAliases { get; set; }
+
+ /// <summary>
+ /// Gets or sets a list of aliases for required attributes.
+ /// </summary>
+ /// <value>A comma-delimited list of aliases.</value>
+ [MessagePart("required", IsRequired = false)]
+ private string RequiredAliases { get; set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </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) {
+ FetchRequest other = obj as FetchRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (this.UpdateUrl != other.UpdateUrl) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalentUnordered(this.Attributes.ToList(), other.Attributes.ToList())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <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() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+
+ if (this.UpdateUrl != null) {
+ hashCode += this.UpdateUrl.GetHashCode();
+ }
+
+ foreach (AttributeRequest att in this.Attributes) {
+ hashCode += att.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
+
+ #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() {
+ var fields = ((IMessage)this).ExtraData;
+ fields.Clear();
+
+ List<string> requiredAliases = new List<string>(), optionalAliases = new List<string>();
+ AliasManager aliasManager = new AliasManager();
+ foreach (var att in this.attributes) {
+ string alias = aliasManager.GetAlias(att.TypeUri);
+
+ // define the alias<->typeUri mapping
+ fields.Add("type." + alias, att.TypeUri);
+
+ // set how many values the relying party wants max
+ fields.Add("count." + alias, att.Count.ToString(CultureInfo.InvariantCulture));
+
+ if (att.IsRequired) {
+ requiredAliases.Add(alias);
+ } else {
+ optionalAliases.Add(alias);
+ }
+ }
+
+ // Set optional/required lists
+ this.OptionalAliases = optionalAliases.Count > 0 ? string.Join(",", optionalAliases.ToArray()) : null;
+ this.RequiredAliases = requiredAliases.Count > 0 ? string.Join(",", requiredAliases.ToArray()) : null;
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var extraData = ((IMessage)this).ExtraData;
+ var requiredAliases = ParseAliasList(this.RequiredAliases);
+ var optionalAliases = ParseAliasList(this.OptionalAliases);
+
+ // if an alias shows up in both lists, an exception will result implicitly.
+ var allAliases = new List<string>(requiredAliases.Count + optionalAliases.Count);
+ allAliases.AddRange(requiredAliases);
+ allAliases.AddRange(optionalAliases);
+ if (allAliases.Count == 0) {
+ Logger.OpenId.Error("Attribute Exchange extension did not provide any aliases in the if_available or required lists.");
+ return;
+ }
+
+ AliasManager aliasManager = new AliasManager();
+ foreach (var alias in allAliases) {
+ string attributeTypeUri;
+ if (extraData.TryGetValue("type." + alias, out attributeTypeUri)) {
+ aliasManager.SetAlias(alias, attributeTypeUri);
+ AttributeRequest att = new AttributeRequest {
+ TypeUri = attributeTypeUri,
+ IsRequired = requiredAliases.Contains(alias),
+ };
+ string countString;
+ if (extraData.TryGetValue("count." + alias, out countString)) {
+ if (countString == "unlimited") {
+ att.Count = int.MaxValue;
+ } else {
+ int count;
+ if (int.TryParse(countString, out count) && count > 0) {
+ att.Count = count;
+ } else {
+ Logger.OpenId.Error("count." + alias + " could not be parsed into a positive integer.");
+ }
+ }
+ } else {
+ att.Count = 1;
+ }
+ this.Attributes.Add(att);
+ } else {
+ Logger.OpenId.Error("Type URI definition of alias " + alias + " is missing.");
+ }
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.UpdateUrl != null && !this.UpdateUrl.IsAbsoluteUri) {
+ this.UpdateUrl = null;
+ Logger.OpenId.ErrorFormat("The AX fetch request update_url parameter was not absolute ('{0}'). Ignoring value.", this.UpdateUrl);
+ }
+
+ if (this.OptionalAliases != null) {
+ if (this.OptionalAliases.IndexOfAny(IllegalAliasListCharacters) >= 0) {
+ Logger.OpenId.Error("Illegal characters found in Attribute Exchange if_available alias list. Ignoring value.");
+ this.OptionalAliases = null;
+ }
+ }
+
+ if (this.RequiredAliases != null) {
+ if (this.RequiredAliases.IndexOfAny(IllegalAliasListCharacters) >= 0) {
+ Logger.OpenId.Error("Illegal characters found in Attribute Exchange required alias list. Ignoring value.");
+ this.RequiredAliases = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Splits a list of aliases by their commas.
+ /// </summary>
+ /// <param name="aliasList">The comma-delimited list of aliases. May be null or empty.</param>
+ /// <returns>The list of aliases. Never null, but may be empty.</returns>
+ private static IList<string> ParseAliasList(string aliasList) {
+ if (string.IsNullOrEmpty(aliasList)) {
+ return EmptyList<string>.Instance;
+ }
+
+ return aliasList.Split(',');
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs
new file mode 100644
index 0000000..14b1caa
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------
+// <copyright file="FetchResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Fetch message, response leg.
+ /// </summary>
+ [Serializable]
+ public sealed class FetchResponse : ExtensionBase, 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.TypeUri && !isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && mode == Mode) {
+ return new FetchResponse();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The value of the 'mode' parameter.
+ /// </summary>
+ [MessagePart("mode", IsRequired = true)]
+ private const string Mode = "fetch_response";
+
+ /// <summary>
+ /// The collection of provided attributes. This field will never be null.
+ /// </summary>
+ private readonly KeyedCollection<string, AttributeValues> attributesProvided = new KeyedCollectionDelegate<string, AttributeValues>(av => av.TypeUri);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FetchResponse"/> class.
+ /// </summary>
+ public FetchResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets a sequence of the attributes whose values are provided by the OpenID Provider.
+ /// </summary>
+ public KeyedCollection<string, AttributeValues> Attributes {
+ get {
+ Contract.Ensures(Contract.Result<KeyedCollection<string, AttributeValues>>() != null);
+ return this.attributesProvided;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the OpenID Provider intends to
+ /// honor the request for updates.
+ /// </summary>
+ public bool UpdateUrlSupported {
+ get { return this.UpdateUrl != null; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL the OpenID Provider will post updates to.
+ /// Must be set if the Provider supports and will use this feature.
+ /// </summary>
+ [MessagePart("update_url", IsRequired = false)]
+ public Uri UpdateUrl { get; set; }
+
+ /// <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>
+ /// Gets the first attribute value provided for a given attribute Type URI.
+ /// </summary>
+ /// <param name="typeUri">
+ /// The type URI of the attribute.
+ /// Usually a constant from <see cref="WellKnownAttributes"/>.</param>
+ /// <returns>
+ /// The first value provided for the attribute, or <c>null</c> if the attribute is missing or no values were provided.
+ /// </returns>
+ /// <remarks>
+ /// This is meant as a helper method for the common case of just wanting one attribute value.
+ /// For greater flexibility or to retrieve more than just the first value for an attribute,
+ /// use the <see cref="Attributes"/> collection directly.
+ /// </remarks>
+ public string GetAttributeValue(string typeUri) {
+ if (this.Attributes.Contains(typeUri)) {
+ return this.Attributes[typeUri].Values.FirstOrDefault();
+ } else {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </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) {
+ FetchResponse other = obj as FetchResponse;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (this.UpdateUrl != other.UpdateUrl) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalentUnordered(this.Attributes.ToList(), other.Attributes.ToList())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <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() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+
+ if (this.UpdateUrl != null) {
+ hashCode += this.UpdateUrl.GetHashCode();
+ }
+
+ foreach (AttributeValues value in this.Attributes) {
+ hashCode += value.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
+
+ #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() {
+ var extraData = ((IMessage)this).ExtraData;
+ AXUtilities.SerializeAttributes(extraData, this.attributesProvided);
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var extraData = ((IMessage)this).ExtraData;
+ foreach (var att in AXUtilities.DeserializeAttributes(extraData)) {
+ this.Attributes.Add(att);
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.UpdateUrl != null && !this.UpdateUrl.IsAbsoluteUri) {
+ this.UpdateUrl = null;
+ Logger.OpenId.ErrorFormat("The AX fetch response update_url parameter was not absolute ('{0}'). Ignoring value.", this.UpdateUrl);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs
new file mode 100644
index 0000000..641b17a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs
@@ -0,0 +1,127 @@
+//-----------------------------------------------------------------------
+// <copyright file="StoreRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Store message, request leg.
+ /// </summary>
+ [Serializable]
+ public sealed class StoreRequest : ExtensionBase, 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.TypeUri && isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && mode == Mode) {
+ return new StoreRequest();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The value of the 'mode' parameter.
+ /// </summary>
+ [MessagePart("mode", IsRequired = true)]
+ private const string Mode = "store_request";
+
+ /// <summary>
+ /// The collection of provided attribute values. This field will never be null.
+ /// </summary>
+ private readonly KeyedCollection<string, AttributeValues> attributesProvided = new KeyedCollectionDelegate<string, AttributeValues>(av => av.TypeUri);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StoreRequest"/> class.
+ /// </summary>
+ public StoreRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets the collection of all the attributes that are included in the store request.
+ /// </summary>
+ public KeyedCollection<string, AttributeValues> Attributes {
+ get { return this.attributesProvided; }
+ }
+
+ #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() {
+ var fields = ((IMessage)this).ExtraData;
+ fields.Clear();
+
+ AXUtilities.SerializeAttributes(fields, this.attributesProvided);
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var fields = ((IMessage)this).ExtraData;
+ foreach (var att in AXUtilities.DeserializeAttributes(fields)) {
+ this.Attributes.Add(att);
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </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) {
+ var other = obj as StoreRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalentUnordered(this.Attributes.ToList(), other.Attributes.ToList())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <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() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+ foreach (AttributeValues att in this.Attributes) {
+ hashCode += att.GetHashCode();
+ }
+ return hashCode;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs
new file mode 100644
index 0000000..ba7f091
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs
@@ -0,0 +1,164 @@
+//-----------------------------------------------------------------------
+// <copyright file="StoreResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Store message, response leg.
+ /// </summary>
+ [Serializable]
+ public sealed class StoreResponse : 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.TypeUri && !isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && (mode == SuccessMode || mode == FailureMode)) {
+ return new StoreResponse();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The value of the mode parameter used to express a successful store operation.
+ /// </summary>
+ private const string SuccessMode = "store_response_success";
+
+ /// <summary>
+ /// The value of the mode parameter used to express a store operation failure.
+ /// </summary>
+ private const string FailureMode = "store_response_failure";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StoreResponse"/> class
+ /// to represent a successful store operation.
+ /// </summary>
+ public StoreResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ this.Succeeded = true;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StoreResponse"/> class
+ /// to represent a failed store operation.
+ /// </summary>
+ /// <param name="failureReason">The reason for failure.</param>
+ public StoreResponse(string failureReason)
+ : this() {
+ this.Succeeded = false;
+ this.FailureReason = failureReason;
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the storage request succeeded.
+ /// </summary>
+ /// <value>Defaults to <c>true</c>.</value>
+ public bool Succeeded {
+ get { return this.Mode == SuccessMode; }
+ set { this.Mode = value ? SuccessMode : FailureMode; }
+ }
+
+ /// <summary>
+ /// Gets or sets the reason for the failure, if applicable.
+ /// </summary>
+ [MessagePart("error", IsRequired = false)]
+ public string FailureReason { get; set; }
+
+ /// <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>
+ /// Gets or sets the mode argument.
+ /// </summary>
+ /// <value>One of 'store_response_success' or 'store_response_failure'.</value>
+ [MessagePart("mode", IsRequired = true)]
+ private string Mode { get; set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </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) {
+ var other = obj as StoreResponse;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (this.Succeeded != other.Succeeded) {
+ return false;
+ }
+
+ if (this.FailureReason != other.FailureReason) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <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() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+ hashCode += this.Succeeded ? 0 : 1;
+ if (this.FailureReason != null) {
+ hashCode += this.FailureReason.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ ErrorUtilities.VerifyProtocol(
+ this.Mode == SuccessMode || this.Mode == FailureMode,
+ MessagingStrings.UnexpectedMessagePartValue,
+ "mode",
+ this.Mode);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs
new file mode 100644
index 0000000..5aa89c6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs
@@ -0,0 +1,323 @@
+//-----------------------------------------------------------------------
+// <copyright file="WellKnownAttributes.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Attribute types defined at http://www.axschema.org/types/.
+ /// </summary>
+ /// <remarks>
+ /// If you don't see what you need here, check that URL to see if any have been added.
+ /// You can use new ones directly without adding them to this class, and can even make
+ /// up your own if you expect the other end to understand what you make up.
+ /// </remarks>
+ public static class WellKnownAttributes {
+ /// <summary>
+ /// Inherent attributes about a personality such as gender and bio.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Person {
+ /// <summary>Gender, either "M" or "F"</summary>
+ /// <example>"M", "F"</example>
+ public const string Gender = "http://axschema.org/person/gender";
+
+ /// <summary>Biography (text)</summary>
+ /// <example>"I am the very model of a modern Major General."</example>
+ public const string Biography = "http://axschema.org/media/biography";
+ }
+
+ /// <summary>
+ /// Preferences such as language and timezone.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Preferences {
+ /// <summary>Preferred language, as per RFC4646</summary>
+ /// <example>"en-US"</example>
+ public const string Language = "http://axschema.org/pref/language";
+
+ /// <summary>Home time zone information (as specified in <a href="http://en.wikipedia.org/wiki/List_of_tz_zones_by_name">zoneinfo</a>)</summary>
+ /// <example>"America/Pacific"</example>
+ public const string TimeZone = "http://axschema.org/pref/timezone";
+ }
+
+ /// <summary>
+ /// The names a person goes by.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Name {
+ /// <summary>Subject's alias or "screen" name</summary>
+ /// <example>"Johnny5"</example>
+ public const string Alias = "http://axschema.org/namePerson/friendly";
+
+ /// <summary>Full name of subject</summary>
+ /// <example>"John Doe"</example>
+ public const string FullName = "http://axschema.org/namePerson";
+
+ /// <summary>Honorific prefix for the subject's name</summary>
+ /// <example>"Mr.", "Mrs.", "Dr."</example>
+ public const string Prefix = "http://axschema.org/namePerson/prefix";
+
+ /// <summary>First or given name of subject</summary>
+ /// <example>"John"</example>
+ public const string First = "http://axschema.org/namePerson/first";
+
+ /// <summary>Last name or surname of subject</summary>
+ /// <example>"Smith"</example>
+ public const string Last = "http://axschema.org/namePerson/last";
+
+ /// <summary>Middle name(s) of subject</summary>
+ /// <example>"Robert"</example>
+ public const string Middle = "http://axschema.org/namePerson/middle";
+
+ /// <summary>Suffix of subject's name</summary>
+ /// <example>"III", "Jr."</example>
+ public const string Suffix = "http://axschema.org/namePerson/suffix";
+ }
+
+ /// <summary>
+ /// Business affiliation.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Company {
+ /// <summary>Company name (employer)</summary>
+ /// <example>"Springfield Power"</example>
+ public const string CompanyName = "http://axschema.org/company/name";
+
+ /// <summary>Employee title</summary>
+ /// <example>"Engineer"</example>
+ public const string JobTitle = "http://axschema.org/company/title";
+ }
+
+ /// <summary>
+ /// Information about a person's birthdate.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class BirthDate {
+ /// <summary>Date of birth.</summary>
+ /// <example>"1979-01-01"</example>
+ public const string WholeBirthDate = "http://axschema.org/birthDate";
+
+ /// <summary>Year of birth (four digits)</summary>
+ /// <example>"1979"</example>
+ public const string Year = "http://axschema.org/birthDate/birthYear";
+
+ /// <summary>Month of birth (1-12)</summary>
+ /// <example>"05"</example>
+ public const string Month = "http://axschema.org/birthDate/birthMonth";
+
+ /// <summary>Day of birth</summary>
+ /// <example>"31"</example>
+ public const string DayOfMonth = "http://axschema.org/birthDate/birthday";
+ }
+
+ /// <summary>
+ /// Various ways to contact a person.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Contact {
+ /// <summary>Internet SMTP email address as per RFC2822</summary>
+ /// <example>"jsmith@isp.example.com"</example>
+ public const string Email = "http://axschema.org/contact/email";
+
+ /// <summary>
+ /// Various types of phone numbers.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Phone {
+ /// <summary>Main phone number (preferred)</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Preferred = "http://axschema.org/contact/phone/default";
+
+ /// <summary>Home phone number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Home = "http://axschema.org/contact/phone/home";
+
+ /// <summary>Business phone number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Work = "http://axschema.org/contact/phone/business";
+
+ /// <summary>Cellular (or mobile) phone number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Mobile = "http://axschema.org/contact/phone/cell";
+
+ /// <summary>Fax number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Fax = "http://axschema.org/contact/phone/fax";
+ }
+
+ /// <summary>
+ /// The many fields that make up an address.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class HomeAddress {
+ /// <summary>Home postal address: street number, name and apartment number</summary>
+ /// <example>"#42 135 East 1st Street"</example>
+ public const string StreetAddressLine1 = "http://axschema.org/contact/postalAddress/home";
+
+ /// <summary>"#42 135 East 1st Street"</summary>
+ /// <example>"Box 67"</example>
+ public const string StreetAddressLine2 = "http://axschema.org/contact/postalAddressAdditional/home";
+
+ /// <summary>Home city name</summary>
+ /// <example>"Vancouver"</example>
+ public const string City = "http://axschema.org/contact/city/home";
+
+ /// <summary>Home state or province name</summary>
+ /// <example>"BC"</example>
+ public const string State = "http://axschema.org/contact/state/home";
+
+ /// <summary>Home country code in ISO.3166.1988 (alpha 2) format</summary>
+ /// <example>"CA"</example>
+ public const string Country = "http://axschema.org/contact/country/home";
+
+ /// <summary>Home postal code; region specific format</summary>
+ /// <example>"V5A 4B2"</example>
+ public const string PostalCode = "http://axschema.org/contact/postalCode/home";
+ }
+
+ /// <summary>
+ /// The many fields that make up an address.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class WorkAddress {
+ /// <summary>Business postal address: street number, name and apartment number</summary>
+ /// <example>"#42 135 East 1st Street"</example>
+ public const string StreetAddressLine1 = "http://axschema.org/contact/postalAddress/business";
+
+ /// <summary>"#42 135 East 1st Street"</summary>
+ /// <example>"Box 67"</example>
+ public const string StreetAddressLine2 = "http://axschema.org/contact/postalAddressAdditional/business";
+
+ /// <summary>Business city name</summary>
+ /// <example>"Vancouver"</example>
+ public const string City = "http://axschema.org/contact/city/business";
+
+ /// <summary>Business state or province name</summary>
+ /// <example>"BC"</example>
+ public const string State = "http://axschema.org/contact/state/business";
+
+ /// <summary>Business country code in ISO.3166.1988 (alpha 2) format</summary>
+ /// <example>"CA"</example>
+ public const string Country = "http://axschema.org/contact/country/business";
+
+ /// <summary>Business postal code; region specific format</summary>
+ /// <example>"V5A 4B2"</example>
+ public const string PostalCode = "http://axschema.org/contact/postalCode/business";
+ }
+
+ /// <summary>
+ /// Various handles for instant message clients.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class IM {
+ /// <summary>AOL instant messaging service handle</summary>
+ /// <example>"jsmith421234"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "AOL", Justification = "By design")]
+ public const string AOL = "http://axschema.org/contact/IM/AIM";
+
+ /// <summary>ICQ instant messaging service handle</summary>
+ /// <example>"1234567"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ICQ", Justification = "By design")]
+ public const string ICQ = "http://axschema.org/contact/IM/ICQ";
+
+ /// <summary>MSN instant messaging service handle</summary>
+ /// <example>"jsmith42@hotmail.com"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "MSN", Justification = "By design")]
+ public const string MSN = "http://axschema.org/contact/IM/MSN";
+
+ /// <summary>Yahoo! instant messaging service handle</summary>
+ /// <example>"jsmith421234"</example>
+ public const string Yahoo = "http://axschema.org/contact/IM/Yahoo";
+
+ /// <summary>Jabber instant messaging service handle</summary>
+ /// <example>"jsmith@jabber.example.com"</example>
+ public const string Jabber = "http://axschema.org/contact/IM/Jabber";
+
+ /// <summary>Skype instant messaging service handle</summary>
+ /// <example>"jsmith42"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Skype", Justification = "By design")]
+ public const string Skype = "http://axschema.org/contact/IM/Skype";
+ }
+
+ /// <summary>
+ /// Various web addresses connected with this personality.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "By design"), SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Web {
+ /// <summary>Web site URL</summary>
+ /// <example>"http://example.com/~jsmith/"</example>
+ public const string Homepage = "http://axschema.org/contact/web/default";
+
+ /// <summary>Blog home page URL</summary>
+ /// <example>"http://example.com/jsmith_blog/"</example>
+ public const string Blog = "http://axschema.org/contact/web/blog";
+
+ /// <summary>LinkedIn URL</summary>
+ /// <example>"http://www.linkedin.com/pub/1/234/56"</example>
+ public const string LinkedIn = "http://axschema.org/contact/web/Linkedin";
+
+ /// <summary>Amazon URL</summary>
+ /// <example>"http://www.amazon.com/gp/pdp/profile/A24DLKJ825"</example>
+ public const string Amazon = "http://axschema.org/contact/web/Amazon";
+
+ /// <summary>Flickr URL</summary>
+ /// <example>"http://flickr.com/photos/jsmith42/"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Flickr", Justification = "By design")]
+ public const string Flickr = "http://axschema.org/contact/web/Flickr";
+
+ /// <summary>del.icio.us URL</summary>
+ /// <example>"http://del.icio.us/jsmith42"</example>
+ public const string Delicious = "http://axschema.org/contact/web/Delicious";
+ }
+ }
+
+ /// <summary>
+ /// Audio and images of this personality.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "By design"), SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Media {
+ /// <summary>Spoken name (web URL)</summary>
+ /// <example>"http://example.com/~jsmith/john_smith.wav"</example>
+ public const string SpokenName = "http://axschema.org/media/spokenname";
+
+ /// <summary>Audio greeting (web URL)</summary>
+ /// <example>"http://example.com/~jsmith/i_greet_you.wav"</example>
+ public const string AudioGreeting = "http://axschema.org/media/greeting/audio";
+
+ /// <summary>Video greeting (web URL)</summary>
+ /// <example>"http://example.com/~jsmith/i_greet_you.mov"</example>
+ public const string VideoGreeting = "http://axschema.org/media/greeting/video";
+
+ /// <summary>
+ /// Images of this personality.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Images {
+ /// <summary>Image (web URL); unspecified dimension</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Default = "http://axschema.org/media/image/default";
+
+ /// <summary>Image (web URL) with equal width and height</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Aspect11 = "http://axschema.org/media/image/aspect11";
+
+ /// <summary>Image (web URL) 4:3 aspect ratio - landscape</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Aspect43 = "http://axschema.org/media/image/aspect43";
+
+ /// <summary>Image (web URL) 4:3 aspect ratio - landscape</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Aspect34 = "http://axschema.org/media/image/aspect34";
+
+ /// <summary>Image (web URL); favicon format as per FAVICON-W3C. The format for the image must be 16x16 pixels or 32x32 pixels, using either 8-bit or 24-bit colors. The format of the image must be one of PNG (a W3C standard), GIF, or ICO.</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Fav", Justification = "By design")]
+ public const string FavIcon = "http://axschema.org/media/image/favicon";
+ }
+ }
+ }
+}