diff options
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange')
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"; + } + } + } +} |