summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs285
1 files changed, 285 insertions, 0 deletions
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(',');
+ }
+ }
+}