summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2008-04-17 21:01:43 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2008-04-17 21:01:43 -0700
commit0fa78ed519bd570d01bea13f6c19d12fa5ab369f (patch)
tree7cb77a1929020f35d280664488e4f70c9fbc4acd /src
parent07b8b586c6873b88df126b4bd9947d87afe8f929 (diff)
downloadDotNetOpenAuth-0fa78ed519bd570d01bea13f6c19d12fa5ab369f.zip
DotNetOpenAuth-0fa78ed519bd570d01bea13f6c19d12fa5ab369f.tar.gz
DotNetOpenAuth-0fa78ed519bd570d01bea13f6c19d12fa5ab369f.tar.bz2
Attribute Exchange extension is now feature complete.
Also added some unit and round-tripping tests.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenId.Test/DotNetOpenId.Test.csproj2
-rw-r--r--src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchRequestTests.cs73
-rw-r--r--src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchResponseTests.cs52
-rw-r--r--src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs30
-rw-r--r--src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs8
-rw-r--r--src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs50
-rw-r--r--src/DotNetOpenId/DotNetOpenId.csproj4
-rw-r--r--src/DotNetOpenId/Extensions/AliasManager.cs65
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchangeFetchRequest.cs109
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchangeFetchResponse.cs113
-rw-r--r--src/DotNetOpenId/Extensions/AttributeRequest.cs45
-rw-r--r--src/DotNetOpenId/Extensions/AttributeResponse.cs23
-rw-r--r--src/DotNetOpenId/Extensions/DemandLevel.cs21
-rw-r--r--src/DotNetOpenId/Strings.Designer.cs18
-rw-r--r--src/DotNetOpenId/Strings.resx6
15 files changed, 603 insertions, 16 deletions
diff --git a/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj b/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj
index bc71e0e..cc7054c 100644
--- a/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj
+++ b/src/DotNetOpenId.Test/DotNetOpenId.Test.csproj
@@ -44,6 +44,8 @@
<Compile Include="AssociationsTest.cs" />
<Compile Include="AssociationTestSuite.cs" />
<Compile Include="ExtensionsArgumentsManagerTests.cs" />
+ <Compile Include="Extensions\AttributeExchangeFetchRequestTests.cs" />
+ <Compile Include="Extensions\AttributeExchangeFetchResponseTests.cs" />
<Compile Include="Extensions\AttributeExchangeTests.cs" />
<Compile Include="Extensions\ExtensionTestBase.cs" />
<Compile Include="Extensions\SimpleRegistrationTests.cs" />
diff --git a/src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchRequestTests.cs b/src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchRequestTests.cs
new file mode 100644
index 0000000..3095b02
--- /dev/null
+++ b/src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchRequestTests.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.Extensions;
+
+namespace DotNetOpenId.Test.Extensions {
+ [TestFixture]
+ public class AttributeExchangeFetchRequestTests {
+ [Test, ExpectedException(typeof(ArgumentNullException))]
+ public void AddAttributeRequestNull() {
+ new AttributeExchangeFetchRequest().AddAttribute(null);
+ }
+
+ [Test]
+ public void AddAttributeRequest() {
+ var req = new AttributeExchangeFetchRequest();
+ req.AddAttribute(new AttributeRequest() { TypeUri = "http://someUri" });
+ }
+
+ [Test]
+ public void AddAttributeRequestStrangeUri() {
+ var req = new AttributeExchangeFetchRequest();
+ req.AddAttribute(new AttributeRequest() { TypeUri = "=someUri*who*knows*but*this*is*legal" });
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void AddAttributeRequestAgain() {
+ var req = new AttributeExchangeFetchRequest();
+ req.AddAttribute(new AttributeRequest() { TypeUri = "http://UriTwice" });
+ req.AddAttribute(new AttributeRequest() { TypeUri = "http://UriTwice" });
+ }
+
+ [Test]
+ public void RespondSimpleValue() {
+ var req = new AttributeRequest();
+ req.TypeUri = "http://someType";
+ var resp = req.Respond("value");
+ Assert.AreEqual(req.TypeUri, resp.TypeUri);
+ Assert.AreEqual(1, resp.Values.Length);
+ Assert.AreEqual("value", resp.Values[0]);
+ }
+
+ [Test]
+ public void RespondTwoValues() {
+ var req = new AttributeRequest();
+ req.TypeUri = "http://someType";
+ req.Count = 2;
+ var resp = req.Respond("value1", "value2");
+ Assert.AreEqual(req.TypeUri, resp.TypeUri);
+ Assert.AreEqual(2, resp.Values.Length);
+ Assert.AreEqual("value1", resp.Values[0]);
+ Assert.AreEqual("value2", resp.Values[1]);
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void RespondTooManyValues() {
+ var req = new AttributeRequest();
+ req.TypeUri = "http://someType";
+ req.Count = 1;
+ req.Respond("value1", "value2");
+ }
+
+ [Test, ExpectedException(typeof(ArgumentNullException))]
+ public void RespondNull() {
+ var req = new AttributeRequest();
+ req.TypeUri = "http://someType";
+ req.Count = 1;
+ req.Respond(null);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchResponseTests.cs b/src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchResponseTests.cs
new file mode 100644
index 0000000..14533ce
--- /dev/null
+++ b/src/DotNetOpenId.Test/Extensions/AttributeExchangeFetchResponseTests.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenId.Extensions;
+
+namespace DotNetOpenId.Test.Extensions {
+ [TestFixture]
+ public class AttributeExchangeFetchResponseTests {
+ [Test]
+ public void AddAttribute() {
+ var response = new AttributeExchangeFetchResponse();
+ response.AddAttribute(new AttributeResponse {
+ TypeUri = "http://someattribute",
+ Values = new[] { "Value1" },
+ });
+ }
+
+ [Test]
+ public void AddTwoAttributes() {
+ var response = new AttributeExchangeFetchResponse();
+ response.AddAttribute(new AttributeResponse {
+ TypeUri = "http://someattribute",
+ Values = new[] { "Value1" },
+ });
+ response.AddAttribute(new AttributeResponse {
+ TypeUri = "http://someOtherAttribute",
+ Values = new[] { "Value2" },
+ });
+ }
+
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void AddAttributeTwice() {
+ var response = new AttributeExchangeFetchResponse();
+ response.AddAttribute(new AttributeResponse {
+ TypeUri = "http://someattribute",
+ Values = new[] { "Value1" },
+ });
+ response.AddAttribute(new AttributeResponse {
+ TypeUri = "http://someattribute",
+ Values = new[] { "Value1" },
+ });
+ }
+
+ [Test, ExpectedException(typeof(ArgumentNullException))]
+ public void AddAttributeNull() {
+ var response = new AttributeExchangeFetchResponse();
+ response.AddAttribute(null);
+ }
+ }
+}
diff --git a/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs b/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs
index c216fd1..ab28784 100644
--- a/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs
+++ b/src/DotNetOpenId.Test/Extensions/AttributeExchangeTests.cs
@@ -9,6 +9,9 @@ using DotNetOpenId.Extensions;
namespace DotNetOpenId.Test.Extensions {
[TestFixture]
public class AttributeExchangeTests : ExtensionTestBase {
+ const string nicknameTypeUri = "http://axschema.org/namePerson/friendly";
+ const string emailTypeUri = "http://axschema.org/contact/email";
+
[Test]
public void None() {
var fetchResponse = ParameterizedTest<AttributeExchangeFetchResponse>(
@@ -22,9 +25,36 @@ namespace DotNetOpenId.Test.Extensions {
[Test]
public void Fetch() {
var request = new AttributeExchangeFetchRequest();
+ request.AddAttribute(new AttributeRequest { TypeUri = nicknameTypeUri });
+ request.AddAttribute(new AttributeRequest { TypeUri = emailTypeUri, Count = int.MaxValue });
+ var response = ParameterizedTest<AttributeExchangeFetchResponse>(
+ TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
+ Assert.IsNotNull(response);
+ var att = response.GetAttribute(nicknameTypeUri);
+ Assert.IsNotNull(att);
+ Assert.AreEqual(nicknameTypeUri, att.TypeUri);
+ Assert.AreEqual(1, att.Values.Length);
+ Assert.AreEqual("Andrew", att.Values[0]);
+ att = response.GetAttribute(emailTypeUri);
+ Assert.IsNotNull(att);
+ Assert.AreEqual(emailTypeUri, att.TypeUri);
+ Assert.AreEqual(2, att.Values.Length);
+ Assert.AreEqual("a@a.com", att.Values[0]);
+ Assert.AreEqual("b@b.com", att.Values[1]);
+ }
+
+ [Test]
+ public void FetchLimitEmails() {
+ var request = new AttributeExchangeFetchRequest();
+ request.AddAttribute(new AttributeRequest { TypeUri = emailTypeUri, Count = 1 });
var response = ParameterizedTest<AttributeExchangeFetchResponse>(
TestSupport.GetIdentityUrl(TestSupport.Scenarios.ExtensionFullCooperation, Version), request);
Assert.IsNotNull(response);
+ var att = response.GetAttribute(emailTypeUri);
+ Assert.IsNotNull(att);
+ Assert.AreEqual(emailTypeUri, att.TypeUri);
+ Assert.AreEqual(1, att.Values.Length);
+ Assert.AreEqual("a@a.com", att.Values[0]);
}
[Test]
diff --git a/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs b/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs
index adb93bb..3f14253 100644
--- a/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs
+++ b/src/DotNetOpenId.Test/Extensions/ExtensionTestBase.cs
@@ -11,12 +11,12 @@ using System.Diagnostics;
namespace DotNetOpenId.Test.Extensions {
public class ExtensionTestBase {
- protected IRelyingPartyApplicationStore Store;
+ protected IRelyingPartyApplicationStore AppStore;
protected const ProtocolVersion Version = ProtocolVersion.V20;
[SetUp]
public virtual void Setup() {
- Store = new ApplicationMemoryStore();
+ AppStore = new ApplicationMemoryStore();
}
protected T ParameterizedTest<T>(Identifier identityUrl, IExtensionRequest extensionArgs)
@@ -24,7 +24,7 @@ namespace DotNetOpenId.Test.Extensions {
Debug.Assert(identityUrl != null);
var returnTo = TestSupport.GetFullUrl(TestSupport.ConsumerPage);
var realm = new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri);
- var consumer = new OpenIdRelyingParty(Store, null);
+ var consumer = new OpenIdRelyingParty(AppStore, null);
var request = consumer.CreateRequest(identityUrl, realm, returnTo);
if (extensionArgs != null)
extensionArgs.AddToRequest(request);
@@ -46,7 +46,7 @@ namespace DotNetOpenId.Test.Extensions {
}
throw;
}
- consumer = new OpenIdRelyingParty(Store, redirectUrl);
+ consumer = new OpenIdRelyingParty(AppStore, redirectUrl);
Assert.AreEqual(AuthenticationStatus.Authenticated, consumer.Response.Status);
Assert.AreEqual(identityUrl, consumer.Response.ClaimedIdentifier);
T r = new T();
diff --git a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs
index 10cb735..40f21a9 100644
--- a/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs
+++ b/src/DotNetOpenId.TestWeb/ProviderEndpoint.aspx.cs
@@ -12,9 +12,9 @@ using System.Collections.Specialized;
using DotNetOpenId.Extensions;
public partial class ProviderEndpoint : System.Web.UI.Page {
- protected void Page_Load(object sender, EventArgs e) {
+ const string nicknameTypeUri = "http://axschema.org/namePerson/friendly";
+ const string emailTypeUri = "http://axschema.org/contact/email";
- }
void respondToExtensions(DotNetOpenId.Provider.IRequest request, TestSupport.Scenarios scenario) {
var sregRequest = SimpleRegistrationRequestFields.ReadFromRequest(request);
var sregResponse = new SimpleRegistrationFieldValues();
@@ -24,19 +24,47 @@ public partial class ProviderEndpoint : System.Web.UI.Page {
var aeStoreResponse = new AttributeExchangeStoreResponse();
switch (scenario) {
case TestSupport.Scenarios.ExtensionFullCooperation:
- if (sregRequest.FullName != SimpleRegistrationRequest.NoRequest)
- sregResponse.FullName = "Andrew Arnott";
- if (sregRequest.Email != SimpleRegistrationRequest.NoRequest)
- sregResponse.Email = "andrewarnott@gmail.com";
+ if (sregRequest != null) {
+ if (sregRequest.FullName != SimpleRegistrationRequest.NoRequest)
+ sregResponse.FullName = "Andrew Arnott";
+ if (sregRequest.Email != SimpleRegistrationRequest.NoRequest)
+ sregResponse.Email = "andrewarnott@gmail.com";
+ }
+ if (aeFetchRequest != null) {
+ var att = aeFetchRequest.GetAttribute(nicknameTypeUri);
+ if (att != null)
+ aeFetchResponse.AddAttribute(att.Respond("Andrew"));
+ att = aeFetchRequest.GetAttribute(emailTypeUri);
+ if (att != null) {
+ string[] emails = new[] { "a@a.com", "b@b.com" };
+ string[] subset = new string[Math.Min(emails.Length, att.Count)];
+ Array.Copy(emails, subset, subset.Length);
+ aeFetchResponse.AddAttribute(att.Respond(subset));
+ }
+ }
break;
case TestSupport.Scenarios.ExtensionPartialCooperation:
- if (sregRequest.FullName == SimpleRegistrationRequest.Require)
- sregResponse.FullName = "Andrew Arnott";
- if (sregRequest.Email == SimpleRegistrationRequest.Require)
- sregResponse.Email = "andrewarnott@gmail.com";
+ if (sregRequest != null) {
+ if (sregRequest.FullName == SimpleRegistrationRequest.Require)
+ sregResponse.FullName = "Andrew Arnott";
+ if (sregRequest.Email == SimpleRegistrationRequest.Require)
+ sregResponse.Email = "andrewarnott@gmail.com";
+ }
+ if (aeFetchRequest != null) {
+ var att = aeFetchRequest.GetAttribute(nicknameTypeUri);
+ if (att != null && att.IsRequired)
+ aeFetchResponse.AddAttribute(att.Respond("Andrew"));
+ att = aeFetchRequest.GetAttribute(emailTypeUri);
+ if (att != null && att.IsRequired) {
+ string[] emails = new[] { "a@a.com", "b@b.com" };
+ string[] subset = new string[Math.Min(emails.Length, att.Count)];
+ Array.Copy(emails, subset, subset.Length);
+ aeFetchResponse.AddAttribute(att.Respond(subset));
+ }
+ }
break;
}
- sregResponse.AddToResponse(request);
+ if (sregRequest != null) sregResponse.AddToResponse(request);
if (aeFetchRequest != null) aeFetchResponse.AddToResponse(request);
if (aeStoreRequest != null) aeStoreResponse.AddToResponse(request);
}
diff --git a/src/DotNetOpenId/DotNetOpenId.csproj b/src/DotNetOpenId/DotNetOpenId.csproj
index 696dee7..6b70277 100644
--- a/src/DotNetOpenId/DotNetOpenId.csproj
+++ b/src/DotNetOpenId/DotNetOpenId.csproj
@@ -52,11 +52,15 @@
<Compile Include="AssociationMemoryStore.cs" />
<Compile Include="Associations.cs" />
<Compile Include="ExtensionArgumentsManager.cs" />
+ <Compile Include="Extensions\AliasManager.cs" />
<Compile Include="Extensions\AttributeExchangeFetchResponse.cs" />
+ <Compile Include="Extensions\AttributeRequest.cs" />
+ <Compile Include="Extensions\AttributeResponse.cs" />
<Compile Include="Extensions\Constants.cs" />
<Compile Include="Extensions\AttributeExchangeFetchRequest.cs" />
<Compile Include="Extensions\AttributeExchangeStoreRequest.cs" />
<Compile Include="Extensions\AttributeExchangeStoreResponse.cs" />
+ <Compile Include="Extensions\DemandLevel.cs" />
<Compile Include="Extensions\IExtension.cs" />
<Compile Include="HmacSha256Association.cs" />
<Compile Include="Identifier.cs" />
diff --git a/src/DotNetOpenId/Extensions/AliasManager.cs b/src/DotNetOpenId/Extensions/AliasManager.cs
new file mode 100644
index 0000000..70aca3c
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/AliasManager.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace DotNetOpenId.Extensions {
+ class AliasManager {
+ readonly string aliasFormat = "alias{0}";
+ /// <summary>
+ /// Tracks extension Type URIs and aliases assigned to them.
+ /// </summary>
+ Dictionary<string, string> typeUriToAliasMap = new Dictionary<string, string>();
+ /// <summary>
+ /// Tracks extension aliases and Type URIs assigned to them.
+ /// </summary>
+ Dictionary<string, string> aliasToTypeUriMap = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Gets an alias assigned for a given Type URI. A new alias is assigned if necessary.
+ /// </summary>
+ public string GetAlias(string typeUri) {
+ if (string.IsNullOrEmpty(typeUri)) throw new ArgumentNullException("typeUri");
+ string alias;
+ if (typeUriToAliasMap.TryGetValue(typeUri, out alias))
+ return alias;
+ else
+ return assignNewAlias(typeUri);
+ }
+
+ /// <summary>
+ /// Sets an alias and the value that will be returned by <see cref="ResolveAlias"/>.
+ /// </summary>
+ public void SetAlias(string alias, string typeUri) {
+ if (string.IsNullOrEmpty(alias)) throw new ArgumentNullException("alias");
+ if (string.IsNullOrEmpty(typeUri)) throw new ArgumentNullException("typeUri");
+ aliasToTypeUriMap.Add(alias, typeUri);
+ typeUriToAliasMap.Add(typeUri, alias);
+ }
+
+ /// <summary>
+ /// Gets the Type Uri encoded by a given alias.
+ /// </summary>
+ public string ResolveAlias(string alias) {
+ if (string.IsNullOrEmpty(alias)) throw new ArgumentNullException("alias");
+ string typeUri;
+ if (!aliasToTypeUriMap.TryGetValue(alias, out typeUri))
+ throw new ArgumentOutOfRangeException("alias");
+ return typeUri;
+ }
+
+ public IEnumerable<string> Aliases {
+ get { return aliasToTypeUriMap.Keys; }
+ }
+
+ string assignNewAlias(string typeUri) {
+ Debug.Assert(!string.IsNullOrEmpty(typeUri));
+ Debug.Assert(!typeUriToAliasMap.ContainsKey(typeUri));
+ string alias = string.Format(CultureInfo.InvariantCulture, aliasFormat, typeUriToAliasMap.Count + 1);
+ typeUriToAliasMap.Add(typeUri, alias);
+ aliasToTypeUriMap.Add(alias, typeUri);
+ return alias;
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/AttributeExchangeFetchRequest.cs b/src/DotNetOpenId/Extensions/AttributeExchangeFetchRequest.cs
index 245f19a..07d68c6 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchangeFetchRequest.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchangeFetchRequest.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Text;
using System.Globalization;
+using System.Diagnostics;
namespace DotNetOpenId.Extensions {
/// <summary>
@@ -10,6 +11,32 @@ namespace DotNetOpenId.Extensions {
public class AttributeExchangeFetchRequest : IExtensionRequest {
readonly string Mode = "fetch_request";
+ List<AttributeRequest> attributesRequested = new List<AttributeRequest>();
+ public void AddAttribute(AttributeRequest attribute) {
+ if (attribute == null) throw new ArgumentNullException("attribute");
+ if (containsAttribute(attribute.TypeUri)) throw new ArgumentException(
+ string.Format(CultureInfo.CurrentCulture,
+ Strings.AttributeAlreadyAdded, attribute.TypeUri), "attribute");
+ attributesRequested.Add(attribute);
+ }
+ public AttributeRequest GetAttribute(string typeUri) {
+ foreach (var attribute in attributesRequested)
+ if (string.Equals(attribute.TypeUri, typeUri, StringComparison.Ordinal))
+ return attribute;
+ return null;
+ }
+ bool containsAttribute(string typeUri) {
+ return GetAttribute(typeUri) != null;
+ }
+
+ /// <summary>
+ /// If set, the OpenID Provider may re-post the fetch response message to the
+ /// specified URL 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>
+ public Uri UpdateUrl { get; set; }
+
/// <summary>
/// Reads an incoming authentication request (from a relying party)
/// for Attribute Exchange properties and returns an instance of this
@@ -27,6 +54,29 @@ namespace DotNetOpenId.Extensions {
var fields = new Dictionary<string, string> {
{ "mode", Mode },
};
+ if (UpdateUrl != null)
+ fields.Add("update_url", UpdateUrl.AbsoluteUri);
+
+ List<string> requiredAliases = new List<string>(), optionalAliases = new List<string>();
+ AliasManager aliasManager = new AliasManager();
+ foreach (var att in attributesRequested) {
+ 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());
+ if (att.IsRequired)
+ requiredAliases.Add(alias);
+ else
+ optionalAliases.Add(alias);
+ }
+
+ // Set optional/required lists
+ if (optionalAliases.Count > 0)
+ fields.Add("if_available", string.Join(",", optionalAliases.ToArray()));
+ if (requiredAliases.Count > 0)
+ fields.Add("required", string.Join(",", requiredAliases.ToArray()));
+
authenticationRequest.AddExtensionArguments(Constants.ae.ns, fields);
}
@@ -37,9 +87,68 @@ namespace DotNetOpenId.Extensions {
fields.TryGetValue("mode", out mode);
if (mode != Mode) return false;
+ string updateUrl;
+ fields.TryGetValue("update_url", out updateUrl);
+ Uri updateUri;
+ if (Uri.TryCreate(updateUrl, UriKind.Absolute, out updateUri))
+ UpdateUrl = updateUri;
+
+ string requiredAliasString, optionalAliasString;
+ fields.TryGetValue("if_available", out optionalAliasString);
+ fields.TryGetValue("required", out requiredAliasString);
+ var requiredAliases = parseAliasList(requiredAliasString);
+ var optionalAliases = parseAliasList(optionalAliasString);
+ // 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);
+ AliasManager aliasManager = new AliasManager();
+ foreach (var alias in allAliases) {
+ string typeUri;
+ if (fields.TryGetValue("type." + alias, out typeUri)) {
+ aliasManager.SetAlias(alias, typeUri);
+ AttributeRequest att = new AttributeRequest {
+ TypeUri = typeUri,
+ IsRequired = requiredAliases.Contains(alias),
+ };
+ string countString;
+ if (fields.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 {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("count." + alias + " could not be parsed into a positive integer.");
+ }
+ }
+ } else {
+ att.Count = 1;
+ }
+ AddAttribute(att);
+ } else {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("Type URI definition of alias " + alias + " is missing.");
+ }
+ }
+
return true;
}
+ List<string> parseAliasList(string aliasList) {
+ List<string> result = new List<string>();
+ if (string.IsNullOrEmpty(aliasList)) return result;
+ if (aliasList.Contains(".") || aliasList.Contains("\n")) {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("Illegal characters found in Attribute Exchange alias list.");
+ return result;
+ }
+ result.AddRange(aliasList.Split(','));
+ return result;
+ }
+
#endregion
}
}
diff --git a/src/DotNetOpenId/Extensions/AttributeExchangeFetchResponse.cs b/src/DotNetOpenId/Extensions/AttributeExchangeFetchResponse.cs
index db72875..49ecdfd 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchangeFetchResponse.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchangeFetchResponse.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using DotNetOpenId.RelyingParty;
using System.Globalization;
+using System.Diagnostics;
namespace DotNetOpenId.Extensions {
/// <summary>
@@ -10,7 +11,35 @@ namespace DotNetOpenId.Extensions {
/// </summary>
public class AttributeExchangeFetchResponse : IExtensionResponse {
readonly string Mode = "fetch_response";
-
+
+ List<AttributeResponse> attributesProvided = new List<AttributeResponse>();
+ public void AddAttribute(AttributeResponse attribute) {
+ if (attribute == null) throw new ArgumentNullException("attribute");
+ if (containsAttribute(attribute.TypeUri)) throw new ArgumentException(
+ string.Format(CultureInfo.CurrentCulture, Strings.AttributeAlreadyAdded, attribute.TypeUri));
+ attributesProvided.Add(attribute);
+ }
+ public AttributeResponse GetAttribute(string typeUri) {
+ foreach (var att in attributesProvided) {
+ if (att.TypeUri == typeUri)
+ return att;
+ }
+ return null;
+ }
+ bool containsAttribute(string typeUri) {
+ return GetAttribute(typeUri) != null;
+ }
+
+ /// <summary>
+ /// Whether the OpenID Provider intends to honor the request for updates.
+ /// </summary>
+ public bool UpdateUrlSupported { get { return UpdateUrl != null; } }
+ /// <summary>
+ /// The URL the OpenID Provider will post updates to. Must be set if the Provider
+ /// supports and will use this feature.
+ /// </summary>
+ public Uri UpdateUrl { get; set; }
+
/// <summary>
/// Reads a Provider's response for Attribute Exchange values and returns
/// an instance of this struct with the values.
@@ -27,6 +56,25 @@ namespace DotNetOpenId.Extensions {
var fields = new Dictionary<string, string> {
{ "mode", Mode },
};
+
+ if (UpdateUrlSupported)
+ fields.Add("update_url", UpdateUrl.AbsoluteUri);
+
+ AliasManager aliasManager = new AliasManager();
+ foreach (var att in attributesProvided) {
+ string alias = aliasManager.GetAlias(att.TypeUri);
+ if (att.Values.Length == 0) continue;
+ fields.Add("type." + alias, att.TypeUri);
+ if (att.Values.Length > 1) {
+ fields.Add("count." + alias, att.Values.Length.ToString());
+ for (int i = 0; i < att.Values.Length; i++) {
+ fields.Add(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i + 1), att.Values[i]);
+ }
+ } else {
+ fields.Add("value." + alias, att.Values[0]);
+ }
+ }
+
authenticationRequest.AddExtensionArguments(Constants.ae.ns, fields);
}
@@ -37,9 +85,72 @@ namespace DotNetOpenId.Extensions {
fields.TryGetValue("mode", out mode);
if (mode != Mode) return false;
+ string updateUrl;
+ fields.TryGetValue("update_url", out updateUrl);
+ Uri updateUri;
+ if (Uri.TryCreate(updateUrl, UriKind.Absolute, out updateUri))
+ UpdateUrl = updateUri;
+
+ AliasManager aliasManager = parseAliases(fields);
+ foreach (string alias in aliasManager.Aliases) {
+ AttributeResponse att = new AttributeResponse() {
+ TypeUri = 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) {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("Failed to parse count.{0} value to a positive integer.");
+ continue;
+ }
+ countSent = true;
+ }
+ att.Values = new string[count];
+ if (countSent) {
+ for (int i = 0; i < att.Values.Length; i++) {
+ string value;
+ if (fields.TryGetValue(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i + 1), out value)) {
+ att.Values[i] = value;
+ } else {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("Missing value for attribute '{0}'.", att.TypeUri);
+ continue;
+ }
+ }
+ } else {
+ string value;
+ if (fields.TryGetValue("value." + alias, out value))
+ att.Values[0] = value;
+ else {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("Missing value for attribute '{0}'.", att.TypeUri);
+ continue;
+ }
+ }
+ AddAttribute(att);
+ }
+
return true;
}
+ AliasManager parseAliases(IDictionary<string, string> fields) {
+ Debug.Assert(fields != null);
+ AliasManager aliasManager = new AliasManager();
+ foreach (var pair in fields) {
+ if (!pair.Key.StartsWith("type.", StringComparison.Ordinal)) continue;
+ string alias = pair.Key.Substring(5);
+ if (alias.IndexOfAny(new[] { '.', ',', ':' }) >= 0) {
+ if (TraceUtil.Switch.TraceError)
+ Trace.TraceError("Illegal characters in alias name '{0}'.", alias);
+ continue;
+ }
+ aliasManager.SetAlias(alias, pair.Value);
+ }
+ return aliasManager;
+ }
+
#endregion
}
}
diff --git a/src/DotNetOpenId/Extensions/AttributeRequest.cs b/src/DotNetOpenId/Extensions/AttributeRequest.cs
new file mode 100644
index 0000000..4c8e947
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/AttributeRequest.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Globalization;
+
+namespace DotNetOpenId.Extensions {
+ /// <summary>
+ /// An individual attribute to be requested of the OpenID Provider using
+ /// the Attribute Exchange extension.
+ /// </summary>
+ public class AttributeRequest {
+ /// <summary>
+ /// The URI uniquely identifying the attribute being requested.
+ /// </summary>
+ public string TypeUri;
+ /// <summary>
+ /// 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;
+ int count = 1;
+ /// <summary>
+ /// 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 count; }
+ set {
+ if (value <= 0) throw new ArgumentOutOfRangeException("value");
+ count = value;
+ }
+ }
+
+ public AttributeResponse Respond(params string[] values) {
+ if (values == null) throw new ArgumentNullException("values");
+ if (values.Length > Count) throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
+ Strings.AttributeTooManyValues, Count, TypeUri, values.Length));
+ return new AttributeResponse {
+ TypeUri = this.TypeUri,
+ Values = values,
+ };
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/AttributeResponse.cs b/src/DotNetOpenId/Extensions/AttributeResponse.cs
new file mode 100644
index 0000000..cff7874
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/AttributeResponse.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions {
+ /// <summary>
+ /// An individual attribute's value(s) as supplied by an OpenID Provider
+ /// in response to a prior request by an OpenID Relying Party.
+ /// </summary>
+ public class AttributeResponse {
+ internal AttributeResponse() { }
+
+ /// <summary>
+ /// 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 string[] Values;
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/DemandLevel.cs b/src/DotNetOpenId/Extensions/DemandLevel.cs
new file mode 100644
index 0000000..05ec8be
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/DemandLevel.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions {
+ /// <summary>
+ /// Indicates a relying party's level of desire for a particular value
+ /// to be provided by the OpenID Provider.
+ /// </summary>
+ public enum DemandLevel {
+ /// <summary>
+ /// The relying party considers this information as optional.
+ /// </summary>
+ Optional,
+ /// <summary>
+ /// The relying party considers this information as required.
+ /// Note however, that the Provider still has the option to not supply this value.
+ /// </summary>
+ Required,
+ }
+}
diff --git a/src/DotNetOpenId/Strings.Designer.cs b/src/DotNetOpenId/Strings.Designer.cs
index 549033e..cf6f6af 100644
--- a/src/DotNetOpenId/Strings.Designer.cs
+++ b/src/DotNetOpenId/Strings.Designer.cs
@@ -61,6 +61,24 @@ namespace DotNetOpenId {
}
/// <summary>
+ /// Looks up a localized string similar to An attribute with type URI &apos;{0}&apos; has already been added..
+ /// </summary>
+ internal static string AttributeAlreadyAdded {
+ get {
+ return ResourceManager.GetString("AttributeAlreadyAdded", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Only {0} values for attribute &apos;{1}&apos; were requested, but {2} were supplied..
+ /// </summary>
+ internal static string AttributeTooManyValues {
+ get {
+ return ResourceManager.GetString("AttributeTooManyValues", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted..
/// </summary>
internal static string BadAssociationPrivateData {
diff --git a/src/DotNetOpenId/Strings.resx b/src/DotNetOpenId/Strings.resx
index 9c11ef2..9e76b85 100644
--- a/src/DotNetOpenId/Strings.resx
+++ b/src/DotNetOpenId/Strings.resx
@@ -117,6 +117,12 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="AttributeAlreadyAdded" xml:space="preserve">
+ <value>An attribute with type URI '{0}' has already been added.</value>
+ </data>
+ <data name="AttributeTooManyValues" xml:space="preserve">
+ <value>Only {0} values for attribute '{1}' were requested, but {2} were supplied.</value>
+ </data>
<data name="BadAssociationPrivateData" xml:space="preserve">
<value>The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted.</value>
</data>