1
//-----------------------------------------------------------------------
2 // <copyright file="FetchResponse.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.OpenId
.Extensions
.AttributeExchange
{
9 using System
.Collections
.Generic
;
10 using System
.Diagnostics
;
11 using System
.Globalization
;
13 using DotNetOpenAuth
.Messaging
;
14 using DotNetOpenAuth
.OpenId
.Messages
;
17 /// The Attribute Exchange Fetch message, response leg.
19 public sealed class FetchResponse
: ExtensionBase
, IMessageWithEvents
{
21 /// The factory method that may be used in deserialization of this message.
23 internal static readonly OpenIdExtensionFactory
.CreateDelegate Factory
= (typeUri
, data
, baseMessage
) => {
24 if (typeUri
== Constants
.TypeUri
&& baseMessage
is IndirectSignedResponse
) {
26 if (data
.TryGetValue("mode", out mode
) && mode
== Mode
) {
27 return new FetchResponse();
34 [MessagePart("mode", IsRequired
= true)]
35 private const string Mode
= "fetch_response";
38 /// The list of provided attributes. This field will never be null.
40 private readonly List
<AttributeValues
> attributesProvided
= new List
<AttributeValues
>();
43 /// Initializes a new instance of the <see cref="FetchResponse"/> class.
45 public FetchResponse()
46 : base(new Version(1, 0), Constants
.TypeUri
, null) {
50 /// Gets a sequence of the attributes whose values are provided by the OpenID Provider.
52 public IEnumerable
<AttributeValues
> Attributes
{
53 get { return this.attributesProvided; }
57 /// Gets a value indicating whether the OpenID Provider intends to
58 /// honor the request for updates.
60 public bool UpdateUrlSupported
{
61 get { return this.UpdateUrl != null; }
65 /// Gets or sets the URL the OpenID Provider will post updates to.
66 /// Must be set if the Provider supports and will use this feature.
68 [MessagePart("update_url", IsRequired
= false)]
69 public Uri UpdateUrl { get; set; }
72 /// Used by the Provider to add attributes to the response for the relying party.
74 public void AddAttribute(AttributeValues attribute
) {
75 ErrorUtilities
.VerifyArgumentNotNull(attribute
, "attribute");
76 ErrorUtilities
.VerifyArgumentNamed(!this.ContainsAttribute(attribute
.TypeUri
), "attribute", OpenIdStrings
.AttributeAlreadyAdded
, attribute
.TypeUri
);
77 this.attributesProvided
.Add(attribute
);
81 /// Used by the Relying Party to get the value(s) returned by the OpenID Provider
82 /// for a given attribute, or null if that attribute was not provided.
84 public AttributeValues
GetAttribute(string attributeTypeUri
) {
85 return this.attributesProvided
.SingleOrDefault(attribute
=> string.Equals(attribute
.TypeUri
, attributeTypeUri
, StringComparison
.Ordinal
));
89 /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
91 /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
93 /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
95 /// <exception cref="T:System.NullReferenceException">
96 /// The <paramref name="obj"/> parameter is null.
98 public override bool Equals(object obj
) {
99 FetchResponse other
= obj
as FetchResponse
;
104 if (this.UpdateUrl
!= other
.UpdateUrl
) {
108 if (!MessagingUtilities
.AreEquivalentUnordered(this.Attributes
.ToList(), other
.Attributes
.ToList())) {
115 #region IMessageWithEvents Members
118 /// Called when the message is about to be transmitted,
119 /// before it passes through the channel binding elements.
121 void IMessageWithEvents
.OnSending() {
122 var extraData
= ((IMessage
)this).ExtraData
;
123 SerializeAttributes(extraData
, this.attributesProvided
);
127 /// Called when the message has been received,
128 /// after it passes through the channel binding elements.
130 void IMessageWithEvents
.OnReceiving() {
131 var extraData
= ((IMessage
)this).ExtraData
;
132 foreach (var att
in DeserializeAttributes(extraData
)) {
133 this.AddAttribute(att
);
139 internal static void SerializeAttributes(IDictionary
<string, string> fields
, IEnumerable
<AttributeValues
> attributes
) {
140 ErrorUtilities
.VerifyArgumentNotNull(fields
, "fields");
141 ErrorUtilities
.VerifyArgumentNotNull(attributes
, "attributes");
143 AliasManager aliasManager
= new AliasManager();
144 foreach (var att
in attributes
) {
145 string alias = aliasManager
.GetAlias(att
.TypeUri
);
146 fields
.Add("type." + alias, att
.TypeUri
);
147 if (att
.Values
== null) {
150 if (att
.Values
.Count
!= 1) {
151 fields
.Add("count." + alias, att
.Values
.Count
.ToString(CultureInfo
.InvariantCulture
));
152 for (int i
= 0; i
< att
.Values
.Count
; i
++) {
153 fields
.Add(string.Format(CultureInfo
.InvariantCulture
, "value.{0}.{1}", alias, i
+ 1), att
.Values
[i
]);
156 fields
.Add("value." + alias, att
.Values
[0]);
161 internal static IEnumerable
<AttributeValues
> DeserializeAttributes(IDictionary
<string, string> fields
) {
162 AliasManager aliasManager
= ParseAliases(fields
);
163 foreach (string alias in aliasManager
.Aliases
) {
164 AttributeValues att
= new AttributeValues(aliasManager
.ResolveAlias(alias));
166 bool countSent
= false;
168 if (fields
.TryGetValue("count." + alias, out countString
)) {
169 if (!int.TryParse(countString
, out count
) || count
<= 0) {
170 Logger
.ErrorFormat("Failed to parse count.{0} value to a positive integer.", alias);
176 for (int i
= 1; i
<= count
; i
++) {
178 if (fields
.TryGetValue(string.Format(CultureInfo
.InvariantCulture
, "value.{0}.{1}", alias, i
), out value)) {
179 att
.Values
.Add(value);
181 Logger
.ErrorFormat("Missing value for attribute '{0}'.", att
.TypeUri
);
187 if (fields
.TryGetValue("value." + alias, out value)) {
188 att
.Values
.Add(value);
190 Logger
.ErrorFormat("Missing value for attribute '{0}'.", att
.TypeUri
);
199 /// Checks the message state for conformity to the protocol specification
200 /// and throws an exception if the message is invalid.
203 /// <para>Some messages have required fields, or combinations of fields that must relate to each other
204 /// in specialized ways. After deserializing a message, this method checks the state of the
205 /// message to see if it conforms to the protocol.</para>
206 /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
207 /// outside this scope of this particular message.</para>
209 /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
210 protected override void EnsureValidMessage() {
211 base.EnsureValidMessage();
213 if (this.UpdateUrl
!= null && !this.UpdateUrl
.IsAbsoluteUri
) {
214 this.UpdateUrl
= null;
215 Logger
.ErrorFormat("The AX fetch response update_url parameter was not absolute ('{0}'). Ignoring value.", this.UpdateUrl
);
219 private static AliasManager
ParseAliases(IDictionary
<string, string> fields
) {
220 ErrorUtilities
.VerifyArgumentNotNull(fields
, "fields");
222 AliasManager aliasManager
= new AliasManager();
223 foreach (var pair
in fields
) {
224 if (!pair
.Key
.StartsWith("type.", StringComparison
.Ordinal
)) {
227 string alias = pair
.Key
.Substring(5);
228 if (alias.IndexOfAny(new[] { '.', ',', ':' }
) >= 0) {
229 Logger
.ErrorFormat("Illegal characters in alias name '{0}'.", alias);
232 aliasManager
.SetAlias(alias, pair
.Value
);
237 private bool ContainsAttribute(string typeUri
) {
238 return this.GetAttribute(typeUri
) != null;