//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { using System; using System.Collections.ObjectModel; using System.Linq; using DotNetOpenAuth.Logging; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; /// /// The Attribute Exchange Fetch message, response leg. /// [Serializable] public sealed class FetchResponse : ExtensionBase, IMessageWithEvents { /// /// The factory method that may be used in deserialization of this message. /// internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { if (typeUri == Constants.TypeUri && !isProviderRole) { string mode; if (data.TryGetValue("mode", out mode) && mode == Mode) { return new FetchResponse(); } } return null; }; /// /// The value of the 'mode' parameter. /// [MessagePart("mode", IsRequired = true)] private const string Mode = "fetch_response"; /// /// The collection of provided attributes. This field will never be null. /// private readonly KeyedCollection attributesProvided = new KeyedCollectionDelegate(av => av.TypeUri); /// /// Initializes a new instance of the class. /// public FetchResponse() : base(new Version(1, 0), Constants.TypeUri, null) { } /// /// Gets a sequence of the attributes whose values are provided by the OpenID Provider. /// public KeyedCollection Attributes { get { return this.attributesProvided; } } /// /// Gets a value indicating whether the OpenID Provider intends to /// honor the request for updates. /// public bool UpdateUrlSupported { get { return this.UpdateUrl != null; } } /// /// Gets or sets the URL the OpenID Provider will post updates to. /// Must be set if the Provider supports and will use this feature. /// [MessagePart("update_url", IsRequired = false)] public Uri UpdateUrl { get; set; } /// /// Gets a value indicating whether this extension is signed by the Provider. /// /// /// true if this instance is signed by the Provider; otherwise, false. /// public bool IsSignedByProvider { get { return this.IsSignedByRemoteParty; } } /// /// Gets the first attribute value provided for a given attribute Type URI. /// /// /// The type URI of the attribute. /// Usually a constant from . /// /// The first value provided for the attribute, or null if the attribute is missing or no values were provided. /// /// /// 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 collection directly. /// public string GetAttributeValue(string typeUri) { if (this.Attributes.Contains(typeUri)) { return this.Attributes[typeUri].Values.FirstOrDefault(); } else { return null; } } /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// /// true if the specified is equal to the current ; otherwise, false. /// /// /// The parameter is null. /// public override bool Equals(object obj) { 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; } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// 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 /// /// Called when the message is about to be transmitted, /// before it passes through the channel binding elements. /// void IMessageWithEvents.OnSending() { var extraData = ((IMessage)this).ExtraData; AXUtilities.SerializeAttributes(extraData, this.attributesProvided); } /// /// Called when the message has been received, /// after it passes through the channel binding elements. /// void IMessageWithEvents.OnReceiving() { var extraData = ((IMessage)this).ExtraData; foreach (var att in AXUtilities.DeserializeAttributes(extraData)) { this.Attributes.Add(att); } } #endregion /// /// Checks the message state for conformity to the protocol specification /// and throws an exception if the message is invalid. /// /// /// 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. /// Note that this property should not check signatures or perform any state checks /// outside this scope of this particular message. /// /// Thrown if the message is invalid. 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); } } } }