summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-01-17 21:46:29 -0800
committerAndrew <andrewarnott@gmail.com>2009-01-17 21:46:29 -0800
commit15e9c340c28dcec55f5356a95f0d08ac40b2d7c0 (patch)
treeb72817bcc2c2c41fd986c6e256e10295e006682d
parent07efff0df7929c836dfe38a0076cc7ee6ed316ff (diff)
downloadDotNetOpenAuth-15e9c340c28dcec55f5356a95f0d08ac40b2d7c0.zip
DotNetOpenAuth-15e9c340c28dcec55f5356a95f0d08ac40b2d7c0.tar.gz
DotNetOpenAuth-15e9c340c28dcec55f5356a95f0d08ac40b2d7c0.tar.bz2
Initial half-port of PAPE extension.
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj3
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PapeTests.cs39
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequestTests.cs199
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponseTests.cs263
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj8
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs47
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs68
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs72
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs54
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs56
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs180
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs228
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/TimespanSecondsEncoder.cs51
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs18
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx6
15 files changed, 1292 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
index 3005f7e..4494558 100644
--- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
+++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
@@ -118,6 +118,9 @@
<Compile Include="OpenId\Extensions\AttributeExchange\AttributeValuesTests.cs" />
<Compile Include="OpenId\Extensions\AttributeExchange\StoreRequestTests.cs" />
<Compile Include="OpenId\Extensions\AttributeExchange\StoreResponseTests.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PapeTests.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyRequestTests.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyResponseTests.cs" />
<Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsResponseTests.cs" />
<Compile Include="OpenId\Extensions\ExtensionTestUtilities.cs" />
<Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequestTests.cs" />
diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PapeTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PapeTests.cs
new file mode 100644
index 0000000..d151ed5
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PapeTests.cs
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------
+// <copyright file="PapeTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Test.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+
+ [TestClass]
+ public class PapeTests : ExtensionTestBase {
+ [TestMethod]
+ public void None() {
+ var response = ParameterizedTest<PolicyResponse>(
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, null);
+ Assert.IsNull(response);
+ }
+
+ [TestMethod]
+ public void Full() {
+ var request = new PolicyRequest();
+ request.MaximumAuthenticationAge = TimeSpan.FromMinutes(10);
+ request.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ var response = ParameterizedTest<PolicyResponse>(
+ TestSupport.Scenarios.ExtensionFullCooperation, Version, request);
+ Assert.IsNotNull(response);
+ Assert.IsNotNull(response.AuthenticationTimeUtc);
+ Assert.IsTrue(response.AuthenticationTimeUtc.Value > DateTime.UtcNow - request.MaximumAuthenticationAge);
+ Assert.IsTrue(response.AssuranceLevels.ContainsKey(Constants.AuthenticationLevels.NistTypeUri));
+ Assert.AreEqual("1", response.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri]);
+ Assert.AreEqual(NistAssuranceLevel.Level1, response.NistAssuranceLevel);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequestTests.cs
new file mode 100644
index 0000000..2d85b4e
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequestTests.cs
@@ -0,0 +1,199 @@
+//-----------------------------------------------------------------------
+// <copyright file="PolicyRequestTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Test.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Globalization;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+
+ [TestClass]
+ public class PolicyRequestTests {
+ [TestMethod]
+ public void Ctor() {
+ PolicyRequest req = new PolicyRequest();
+ Assert.IsNull(req.MaximumAuthenticationAge);
+ Assert.IsNotNull(req.PreferredPolicies);
+ Assert.AreEqual(0, req.PreferredPolicies.Count);
+ }
+
+ [TestMethod]
+ public void MaximumAuthenticationAgeTest() {
+ PolicyRequest req = new PolicyRequest();
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ Assert.IsNotNull(req.MaximumAuthenticationAge);
+ Assert.AreEqual(TimeSpan.FromHours(1), req.MaximumAuthenticationAge);
+ req.MaximumAuthenticationAge = null;
+ Assert.IsNull(req.MaximumAuthenticationAge);
+ }
+
+ [TestMethod]
+ public void AddPolicies() {
+ PolicyRequest resp = new PolicyRequest();
+ resp.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(2, resp.PreferredPolicies.Count);
+ Assert.AreEqual(AuthenticationPolicies.MultiFactor, resp.PreferredPolicies[0]);
+ Assert.AreEqual(AuthenticationPolicies.PhishingResistant, resp.PreferredPolicies[1]);
+ }
+
+ [TestMethod]
+ public void AddPolicyMultipleTimes() {
+ // Although this isn't really the desired behavior (we'd prefer to see an
+ // exception thrown), since we're using a List<string> internally we can't
+ // expect anything better (for now). But if this is ever fixed, by all means
+ // change this test to expect an exception or something else.
+ PolicyRequest resp = new PolicyRequest();
+ resp.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(2, resp.PreferredPolicies.Count);
+ }
+
+ [TestMethod]
+ public void AddAuthLevelTypes() {
+ PolicyRequest req = new PolicyRequest();
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ Assert.AreEqual(1, req.PreferredAuthLevelTypes.Count);
+ Assert.IsTrue(req.PreferredAuthLevelTypes.Contains(Constants.AuthenticationLevels.NistTypeUri));
+ }
+
+ [TestMethod]
+ public void EqualsTest() {
+ PolicyRequest req = new PolicyRequest();
+ PolicyRequest req2 = new PolicyRequest();
+ Assert.AreEqual(req, req2);
+ Assert.AreNotEqual(req, null);
+ Assert.AreNotEqual(null, req);
+
+ // Test PreferredPolicies list comparison
+ req.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredPolicies.Clear();
+ req2.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(req, req2);
+
+ // Test PreferredPolicies list comparison when that list is not in the same order.
+ req.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredPolicies.Insert(0, AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(req, req2);
+
+ // Test MaximumAuthenticationAge comparison.
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ Assert.AreNotEqual(req, req2);
+ req2.MaximumAuthenticationAge = req.MaximumAuthenticationAge;
+ Assert.AreEqual(req, req2);
+
+ // Test PreferredAuthLevelTypes comparison.
+ req.PreferredAuthLevelTypes.Add("authlevel1");
+ Assert.AreNotEqual(req, req2);
+ req2.PreferredAuthLevelTypes.Add("authlevel2");
+ Assert.AreNotEqual(req, req2);
+ req.PreferredAuthLevelTypes.Add("authlevel2");
+ req2.PreferredAuthLevelTypes.Add("authlevel1");
+ Assert.AreEqual(req, req2);
+ }
+
+ [TestMethod]
+ public void DeserializeNull() {
+ PolicyRequest req = new PolicyRequest();
+ Assert.IsFalse(((IExtensionRequest)req).Deserialize(null, null, Constants.TypeUri));
+ }
+
+ [TestMethod]
+ public void DeserializeEmpty() {
+ PolicyRequest req = new PolicyRequest();
+ Assert.IsFalse(((IExtensionRequest)req).Deserialize(new Dictionary<string, string>(), null, Constants.TypeUri));
+ }
+
+ [TestMethod]
+ public void SerializeRoundTrip() {
+ // This test relies on the PolicyRequest.Equals method. If this and that test
+ // are failing, work on EqualsTest first.
+
+ // Most basic test
+ PolicyRequest req = new PolicyRequest(), req2 = new PolicyRequest();
+ var fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(req, req2);
+
+ // Test with all fields set
+ req2 = new PolicyRequest();
+ req.PreferredPolicies.Add(AuthenticationPolicies.MultiFactor);
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(req, req2);
+
+ // Test with an extra policy and auth level
+ req2 = new PolicyRequest();
+ req.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ req.PreferredAuthLevelTypes.Add("customAuthLevel");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(req, req2);
+
+ // Test with a policy added twice. We should see it intelligently leave one of
+ // the doubled policies out.
+ req2 = new PolicyRequest();
+ req.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.IsTrue(((IExtensionRequest)req2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreNotEqual(req, req2);
+ // Now go ahead and add the doubled one so we can do our equality test.
+ req2.PreferredPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ req2.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ Assert.AreEqual(req, req2);
+ }
+
+ [TestMethod]
+ public void Serialize() {
+ PolicyRequest req = new PolicyRequest();
+ var fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("preferred_auth_policies"));
+ Assert.IsEmpty(fields["preferred_auth_policies"]);
+
+ req.MaximumAuthenticationAge = TimeSpan.FromHours(1);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(2, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("max_auth_age"));
+ Assert.AreEqual(TimeSpan.FromHours(1).TotalSeconds.ToString(CultureInfo.InvariantCulture), fields["max_auth_age"]);
+
+ req.PreferredPolicies.Add("http://pol1/");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual("http://pol1/", fields["preferred_auth_policies"]);
+
+ req.PreferredPolicies.Add("http://pol2/");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual("http://pol1/ http://pol2/", fields["preferred_auth_policies"]);
+
+ req.PreferredAuthLevelTypes.Add("http://authtype1/");
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(4, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.alias1"));
+ Assert.AreEqual("http://authtype1/", fields["auth_level.ns.alias1"]);
+ Assert.IsTrue(fields.ContainsKey("preferred_auth_level_types"));
+ Assert.AreEqual("alias1", fields["preferred_auth_level_types"]);
+
+ req.PreferredAuthLevelTypes.Add(Constants.AuthenticationLevels.NistTypeUri);
+ fields = ((IExtensionRequest)req).Serialize(null);
+ Assert.AreEqual(5, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.alias2"));
+ Assert.AreEqual("http://authtype1/", fields["auth_level.ns.alias2"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.nist"));
+ Assert.AreEqual(Constants.AuthenticationLevels.NistTypeUri, fields["auth_level.ns.nist"]);
+ Assert.AreEqual("alias2 nist", fields["preferred_auth_level_types"]);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponseTests.cs
new file mode 100644
index 0000000..1e0ad76
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponseTests.cs
@@ -0,0 +1,263 @@
+//-----------------------------------------------------------------------
+// <copyright file="PolicyResponseTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Test.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+
+ [TestClass]
+ public class PolicyResponseTests {
+ DateTime someLocalTime = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Local);
+ DateTime someUtcTime = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Utc);
+ DateTime someUnspecifiedTime = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Unspecified);
+ [TestMethod]
+ public void Ctor() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.IsNotNull(resp.ActualPolicies);
+ Assert.AreEqual(0, resp.ActualPolicies.Count);
+ Assert.IsNull(resp.AuthenticationTimeUtc);
+ Assert.IsNull(resp.NistAssuranceLevel);
+ }
+
+ [TestMethod]
+ public void AddPolicies() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(2, resp.ActualPolicies.Count);
+ Assert.AreEqual(AuthenticationPolicies.MultiFactor, resp.ActualPolicies[0]);
+ Assert.AreEqual(AuthenticationPolicies.PhishingResistant, resp.ActualPolicies[1]);
+ }
+
+ [TestMethod]
+ public void AddPolicyMultipleTimes() {
+ // Although this isn't really the desired behavior (we'd prefer to see an
+ // exception thrown), since we're using a List<string> internally we can't
+ // expect anything better (for now). But if this is ever fixed, by all means
+ // change this test to expect an exception or something else.
+ PolicyResponse resp = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(2, resp.ActualPolicies.Count);
+ }
+
+ [TestMethod]
+ public void AuthenticationTimeUtcConvertsToUtc() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = someLocalTime;
+ Assert.IsNotNull(resp.AuthenticationTimeUtc);
+ Assert.AreEqual(DateTimeKind.Utc, resp.AuthenticationTimeUtc.Value.Kind);
+ Assert.AreEqual(someLocalTime.ToUniversalTime(), resp.AuthenticationTimeUtc.Value);
+ }
+
+ [TestMethod]
+ public void AuthenticationTimeUtcSetUtc() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = someUtcTime;
+ Assert.AreEqual(someUtcTime, resp.AuthenticationTimeUtc);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void AuthenticationTimeUtcSetUnspecified() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = someUnspecifiedTime;
+ }
+
+ [TestMethod]
+ public void AuthenticationTimeUtcSetNull() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.AuthenticationTimeUtc = null;
+ Assert.IsNull(resp.AuthenticationTimeUtc);
+ resp.AuthenticationTimeUtc = someUtcTime;
+ Assert.IsNotNull(resp.AuthenticationTimeUtc);
+ resp.AuthenticationTimeUtc = null;
+ Assert.IsNull(resp.AuthenticationTimeUtc);
+ }
+
+ [TestMethod]
+ public void NistAssuranceLevelSetVarious() {
+ PolicyResponse resp = new PolicyResponse();
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level1;
+ Assert.AreEqual(NistAssuranceLevel.Level1, resp.NistAssuranceLevel);
+ resp.NistAssuranceLevel = null;
+ Assert.IsNull(resp.NistAssuranceLevel);
+ resp.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1;
+ Assert.AreEqual(NistAssuranceLevel.InsufficientForLevel1, resp.NistAssuranceLevel);
+ }
+
+ [TestMethod]
+ public void AssuranceLevels() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.AreEqual(0, resp.AssuranceLevels.Count);
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ Assert.AreEqual(1, resp.AssuranceLevels.Count);
+ Assert.AreEqual("2", resp.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri]);
+ resp.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = "3";
+ Assert.AreEqual(NistAssuranceLevel.Level3, resp.NistAssuranceLevel);
+ resp.AssuranceLevels.Clear();
+ Assert.IsNull(resp.NistAssuranceLevel);
+ }
+
+ [TestMethod]
+ public void EqualsTest() {
+ PolicyResponse resp = new PolicyResponse();
+ PolicyResponse resp2 = new PolicyResponse();
+ Assert.AreEqual(resp, resp2);
+ Assert.AreNotEqual(resp, null);
+ Assert.AreNotEqual(null, resp);
+
+ // Test ActualPolicies list comparison
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.ActualPolicies.Clear();
+ resp2.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(resp, resp2);
+
+ // Test ActualPolicies list comparison when that list is not in the same order.
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.ActualPolicies.Insert(0, AuthenticationPolicies.MultiFactor);
+ Assert.AreEqual(resp, resp2);
+
+ // Test AuthenticationTimeUtc comparison.
+ resp.AuthenticationTimeUtc = DateTime.Now;
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AuthenticationTimeUtc = resp.AuthenticationTimeUtc;
+ Assert.AreEqual(resp, resp2);
+ resp2.AuthenticationTimeUtc += TimeSpan.FromSeconds(1);
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AuthenticationTimeUtc = resp.AuthenticationTimeUtc;
+ Assert.AreEqual(resp, resp2);
+
+ // Test NistAssuranceLevel comparison.
+ resp.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1;
+ Assert.AreNotEqual(resp, resp2);
+ resp2.NistAssuranceLevel = NistAssuranceLevel.InsufficientForLevel1;
+ Assert.AreEqual(resp, resp2);
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ Assert.AreNotEqual(resp, resp2);
+ resp2.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ Assert.AreEqual(resp, resp2);
+
+ // Test AssuranceLevels comparison.
+ resp.AssuranceLevels.Add("custom", "b");
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AssuranceLevels.Add("custom", "2");
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AssuranceLevels["custom"] = "b";
+ Assert.AreEqual(resp, resp2);
+ resp.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = "1";
+ Assert.AreNotEqual(resp, resp2);
+ resp2.AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = "1";
+ Assert.AreEqual(resp, resp2);
+ }
+
+ [TestMethod]
+ public void DeserializeNull() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.IsFalse(((IExtensionResponse)resp).Deserialize(null, null, Constants.TypeUri));
+ }
+
+ [TestMethod]
+ public void DeserializeEmpty() {
+ PolicyResponse resp = new PolicyResponse();
+ Assert.IsFalse(((IExtensionResponse)resp).Deserialize(new Dictionary<string, string>(), null, Constants.TypeUri));
+ }
+
+ [TestMethod]
+ public void SerializeRoundTrip() {
+ // This test relies on the PolicyResponse.Equals method. If this and that test
+ // are failing, work on EqualsTest first.
+
+ // Most basic test
+ PolicyResponse resp = new PolicyResponse(), resp2 = new PolicyResponse();
+ var fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(resp, resp2);
+
+ // Test with all fields set
+ resp2 = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.MultiFactor);
+ resp.AuthenticationTimeUtc = someUtcTime;
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level2;
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(resp, resp2);
+
+ // Test with an extra policy
+ resp2 = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ resp.AssuranceLevels.Add("customlevel", "ABC");
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreEqual(resp, resp2);
+
+ // Test with a policy added twice. We should see it intelligently leave one of
+ // the doubled policies out.
+ resp2 = new PolicyResponse();
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.IsTrue(((IExtensionResponse)resp2).Deserialize(fields, null, Constants.TypeUri));
+ Assert.AreNotEqual(resp, resp2);
+ // Now go ahead and add the doubled one so we can do our equality test.
+ resp2.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ Assert.AreEqual(resp, resp2);
+ }
+
+ [TestMethod]
+ public void Serialize() {
+ PolicyResponse resp = new PolicyResponse(), resp2 = new PolicyResponse();
+ var fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_policies"));
+ Assert.AreEqual(AuthenticationPolicies.None, fields["auth_policies"]);
+
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhishingResistant);
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.AreEqual(AuthenticationPolicies.PhishingResistant, fields["auth_policies"]);
+
+ resp.ActualPolicies.Add(AuthenticationPolicies.PhysicalMultiFactor);
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(1, fields.Count);
+ Assert.AreEqual(
+ AuthenticationPolicies.PhishingResistant + " " + AuthenticationPolicies.PhysicalMultiFactor,
+ fields["auth_policies"]);
+
+ resp.AuthenticationTimeUtc = DateTime.UtcNow;
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(2, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_time"));
+
+ resp.NistAssuranceLevel = NistAssuranceLevel.Level3;
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(4, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.nist"));
+ Assert.AreEqual(Constants.AuthenticationLevels.NistTypeUri, fields["auth_level.ns.nist"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.nist"));
+ Assert.AreEqual("3", fields["auth_level.nist"]);
+
+ resp.AssuranceLevels.Add("custom", "CU");
+ fields = ((IExtensionResponse)resp).Serialize(null);
+ Assert.AreEqual(6, fields.Count);
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.alias2"));
+ Assert.AreEqual("custom", fields["auth_level.ns.alias2"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.alias2"));
+ Assert.AreEqual("CU", fields["auth_level.alias2"]);
+ // and make sure the NIST is still there.
+ Assert.IsTrue(fields.ContainsKey("auth_level.ns.nist"));
+ Assert.AreEqual(Constants.AuthenticationLevels.NistTypeUri, fields["auth_level.ns.nist"]);
+ Assert.IsTrue(fields.ContainsKey("auth_level.nist"));
+ Assert.AreEqual("3", fields["auth_level.nist"]);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 736d273..8ca0811 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -196,6 +196,14 @@
<Compile Include="OpenId\Extensions\ExtensionBase.cs" />
<Compile Include="OpenId\Extensions\ExtensionArgumentsManager.cs" />
<Compile Include="OpenId\Extensions\OpenIdExtensionFactory.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\Constants.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\DateTimeEncoder.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\NistAssuranceLevel.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PapeUtilities.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyRequest.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyResponse.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\TimespanSecondsEncoder.cs" />
<Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequest.cs" />
<Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsResponse.cs" />
<Compile Include="OpenId\Extensions\SimpleRegistration\Constants.cs" />
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
new file mode 100644
index 0000000..ebc5083
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthenticationPolicies.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Well-known authentication policies defined in the PAPE extension spec or by a recognized
+ /// standards body.
+ /// </summary>
+ /// <remarks>
+ /// This is a class of constants rather than a flags enum because policies may be
+ /// freely defined and used by anyone, just by using a new Uri.
+ /// </remarks>
+ public static class AuthenticationPolicies {
+ /// <summary>
+ /// Used in a PAPE response to indicate that no PAPE authentication policies could be satisfied.
+ /// </summary>
+ /// <remarks>
+ /// Used internally by the PAPE extension, so that users don't have to know about it.
+ /// </remarks>
+ internal const string None = "http://schemas.openid.net/pape/policies/2007/06/none";
+
+ /// <summary>
+ /// An authentication mechanism where the End User does not provide a shared secret to a party potentially under the control of the Relying Party. (Note that the potentially malicious Relying Party controls where the User-Agent is redirected to and thus may not send it to the End User's actual OpenID Provider).
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Phishing")]
+ public const string PhishingResistant = "http://schemas.openid.net/pape/policies/2007/06/phishing-resistant";
+
+ /// <summary>
+ /// An authentication mechanism where the End User authenticates to the OpenID Provider by providing over one authentication factor. Common authentication factors are something you know, something you have, and something you are. An example would be authentication using a password and a software token or digital certificate.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor")]
+ public const string MultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor";
+
+ /// <summary>
+ /// An authentication mechanism where the End User authenticates to the OpenID Provider by providing over one authentication factor where at least one of the factors is a physical factor such as a hardware device or biometric. Common authentication factors are something you know, something you have, and something you are. This policy also implies the Multi-Factor Authentication policy (http://schemas.openid.net/pape/policies/2007/06/multi-factor) and both policies MAY BE specified in conjunction without conflict. An example would be authentication using a password and a hardware token.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor")]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
+ public const string PhysicalMultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical";
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
new file mode 100644
index 0000000..25bbbfd
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// OpenID Provider Authentication Policy extension constants.
+ /// </summary>
+ internal static class Constants {
+ /// <summary>
+ /// The namespace used by this extension in messages.
+ /// </summary>
+ internal const string TypeUri = "http://specs.openid.net/extensions/pape/1.0";
+
+ /// <summary>
+ /// The namespace alias to use for OpenID 1.x interop, where aliases are not defined in the message.
+ /// </summary>
+ internal const string pape_compatibility_alias = "pape";
+
+ /// <summary>
+ /// The string to prepend on an Auth Level Type alias definition.
+ /// </summary>
+ internal const string AuthLevelNamespaceDeclarationPrefix = "auth_level.ns.";
+
+ internal static class AuthenticationLevels {
+ internal static readonly IDictionary<string, string> PreferredTypeUriToAliasMap = new Dictionary<string, string> {
+ { NistTypeUri, nist_compatibility_alias },
+ };
+
+ internal const string nist_compatibility_alias = "nist";
+ internal const string NistTypeUri = "http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf";
+ }
+
+ /// <summary>
+ /// Parameters to be included with PAPE requests.
+ /// </summary>
+ internal static class RequestParameters {
+ /// <summary>
+ /// Optional. If the End User has not actively authenticated to the OP within the number of seconds specified in a manner fitting the requested policies, the OP SHOULD authenticate the End User for this request.
+ /// </summary>
+ /// <value>Integer value greater than or equal to zero in seconds.</value>
+ /// <remarks>
+ /// The OP should realize that not adhering to the request for re-authentication most likely means that the End User will not be allowed access to the services provided by the RP. If this parameter is absent in the request, the OP should authenticate the user at its own discretion.
+ /// </remarks>
+ internal const string MaxAuthAge = "max_auth_age";
+
+ /// <summary>
+ /// Zero or more authentication policy URIs that the OP SHOULD conform to when authenticating the user. If multiple policies are requested, the OP SHOULD satisfy as many as it can.
+ /// </summary>
+ /// <value>Space separated list of authentication policy URIs.</value>
+ /// <remarks>
+ /// If no policies are requested, the RP may be interested in other information such as the authentication age.
+ /// </remarks>
+ internal const string PreferredAuthPolicies = "preferred_auth_policies";
+
+ /// <summary>
+ /// The space separated list of the name spaces of the custom Assurance Level that RP requests, in the order of its preference.
+ /// </summary>
+ internal const string PreferredAuthLevelTypes = "preferred_auth_level_types";
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs
new file mode 100644
index 0000000..2fe498e
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------
+// <copyright file="DateTimeEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using System.Globalization;
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <remarks>
+ /// The timestamp MUST be formatted as specified in section 5.6 of [RFC3339] (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), with the following restrictions:
+ /// * All times must be in the UTC timezone, indicated with a "Z".
+ /// * No fractional seconds are allowed
+ /// For example: 2005-05-15T17:11:51Z
+ /// </remarks>
+ internal class DateTimeEncoder : IMessagePartEncoder {
+ /// <summary>
+ /// An array of the date/time formats allowed by the PAPE extension.
+ /// </summary>
+ /// <remarks>
+ /// TODO: This array of formats is not yet a complete list.
+ /// </remarks>
+ private static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" };
+
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ DateTime? dateTime = value as DateTime?;
+ if (dateTime.HasValue) {
+ return dateTime.Value.ToUniversalTime().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ DateTime dateTime;
+ if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dateTime) && dateTime.Kind == DateTimeKind.Utc) { // may be unspecified per our option above
+ return dateTime;
+ } else {
+ Logger.ErrorFormat("Invalid format for auth_time parameter: {0}", value);
+ return null;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
new file mode 100644
index 0000000..07f2b22
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------
+// <copyright file="NistAssuranceLevel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// Descriptions for NIST-defined levels of assurance that a credential
+ /// has not been compromised and therefore the extent to which an
+ /// authentication assertion can be trusted.
+ /// </summary>
+ /// <remarks>
+ /// One using this enum should review the following publication for details
+ /// before asserting or interpreting what these levels signify, notwithstanding
+ /// the brief summaries attached to each level in DotNetOpenId documentation.
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ ///
+ /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels.
+ /// </remarks>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist")]
+ public enum NistAssuranceLevel {
+ /// <summary>
+ /// Not an assurance level defined by NIST, but rather SHOULD be used to
+ /// signify that the OP recognizes the parameter and the End User
+ /// authentication did not meet the requirements of Level 1.
+ /// </summary>
+ InsufficientForLevel1 = 0,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level1 = 1,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level2 = 2,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level3 = 3,
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level4 = 4,
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs
new file mode 100644
index 0000000..812c0b5
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs
@@ -0,0 +1,56 @@
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using System.Globalization;
+
+ internal static class PapeUtilities {
+ static internal IEnumerable<T> GetUniqueItems<T>(IList<T> list) {
+ List<T> itemsSeen = new List<T>(list.Count);
+ foreach (T item in list) {
+ if (itemsSeen.Contains(item)) continue;
+ itemsSeen.Add(item);
+ yield return item;
+ }
+ }
+
+ /// <summary>
+ /// Looks at the incoming fields and figures out what the aliases and name spaces for auth level types are.
+ /// </summary>
+ internal static AliasManager FindIncomingAliases(IDictionary<string, string> fields) {
+ AliasManager aliasManager = new AliasManager();
+
+ foreach (var pair in fields) {
+ if (!pair.Key.StartsWith(Constants.AuthLevelNamespaceDeclarationPrefix, StringComparison.Ordinal)) {
+ continue;
+ }
+
+ string alias = pair.Key.Substring(Constants.AuthLevelNamespaceDeclarationPrefix.Length);
+ aliasManager.SetAlias(alias, pair.Value);
+ }
+
+ aliasManager.SetPreferredAliasesWhereNotSet(Constants.AuthenticationLevels.PreferredTypeUriToAliasMap);
+
+ return aliasManager;
+ }
+
+ internal static string ConcatenateListOfElements(IList<string> values) {
+ ErrorUtilities.VerifyArgumentNotNull(values, "values");
+
+ StringBuilder valuesList = new StringBuilder();
+ foreach (string value in GetUniqueItems(values)) {
+ if (value.Contains(" ")) {
+ throw new FormatException(string.Format(CultureInfo.CurrentCulture,
+ OpenIdStrings.InvalidUri, value));
+ }
+ valuesList.Append(value);
+ valuesList.Append(" ");
+ }
+ if (valuesList.Length > 0)
+ valuesList.Length -= 1; // remove trailing space
+ return valuesList.ToString();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
new file mode 100644
index 0000000..ffaec90
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
@@ -0,0 +1,180 @@
+//-----------------------------------------------------------------------
+// <copyright file="PolicyRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Globalization;
+ using System.Diagnostics;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The PAPE request part of an OpenID Authentication request message.
+ /// </summary>
+ public sealed class PolicyRequest : ExtensionBase, IMessageWithEvents {
+ [MessagePart("preferred_auth_policies", IsRequired = true)]
+ private string preferredPoliciesString;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PolicyRequest"/> class.
+ /// </summary>
+ public PolicyRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ PreferredPolicies = new List<string>(1);
+ PreferredAuthLevelTypes = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Optional. If the End User has not actively authenticated to the OP within
+ /// the number of seconds specified in a manner fitting the requested policies,
+ /// the OP SHOULD authenticate the End User for this request.
+ /// </summary>
+ /// <remarks>
+ /// The OP should realize that not adhering to the request for re-authentication
+ /// most likely means that the End User will not be allowed access to the
+ /// services provided by the RP. If this parameter is absent in the request,
+ /// the OP should authenticate the user at its own discretion.
+ /// </remarks>
+ [MessagePart("max_auth_age", IsRequired = false, Encoder = typeof(TimespanSecondsEncoder))]
+ public TimeSpan? MaximumAuthenticationAge { get; set; }
+
+ /// <summary>
+ /// Zero or more authentication policy URIs that the OP SHOULD conform to when authenticating the user. If multiple policies are requested, the OP SHOULD satisfy as many as it can.
+ /// </summary>
+ /// <value>List of authentication policy URIs obtainable from the <see cref="AuthenticationPolicies"/> class or from a custom list.</value>
+ /// <remarks>
+ /// If no policies are requested, the RP may be interested in other information such as the authentication age.
+ /// </remarks>
+ public IList<string> PreferredPolicies { get; private set; }
+
+ /// <summary>
+ /// Zero or more name spaces of the custom Assurance Level the RP requests, in the order of its preference.
+ /// </summary>
+ public IList<string> PreferredAuthLevelTypes { 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) {
+ PolicyRequest other = obj as PolicyRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (MaximumAuthenticationAge != other.MaximumAuthenticationAge) {
+ return false;
+ }
+
+ if (PreferredPolicies.Count != other.PreferredPolicies.Count) {
+ return false;
+ }
+
+ foreach (string policy in PreferredPolicies) {
+ if (!other.PreferredPolicies.Contains(policy)) {
+ return false;
+ }
+ }
+
+ if (PreferredAuthLevelTypes.Count != other.PreferredAuthLevelTypes.Count) {
+ return false;
+ }
+
+ foreach (string authLevel in PreferredAuthLevelTypes) {
+ if (!other.PreferredAuthLevelTypes.Contains(authLevel)) {
+ 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() {
+ // TODO: fix this to match Equals
+ return PreferredPolicies.GetHashCode();
+ }
+
+ private static string SerializePolicies(IList<string> policies) {
+ return PapeUtilities.ConcatenateListOfElements(policies);
+ }
+
+ private static string SerializeAuthLevels(IList<string> preferredAuthLevelTypes, AliasManager aliases) {
+ var aliasList = new List<string>();
+ foreach (string typeUri in preferredAuthLevelTypes) {
+ aliasList.Add(aliases.GetAlias(typeUri));
+ }
+
+ return PapeUtilities.ConcatenateListOfElements(aliasList);
+ }
+
+ #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;
+ extraData.Clear();
+
+ this.preferredPoliciesString = SerializePolicies(this.PreferredPolicies);
+
+ if (PreferredAuthLevelTypes.Count > 0) {
+ AliasManager authLevelAliases = new AliasManager();
+ authLevelAliases.AssignAliases(PreferredAuthLevelTypes, Constants.AuthenticationLevels.PreferredTypeUriToAliasMap);
+
+ // Add a definition for each Auth Level Type alias.
+ foreach (string alias in authLevelAliases.Aliases) {
+ extraData.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, authLevelAliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list a preferred order.
+ extraData.Add(Constants.RequestParameters.PreferredAuthLevelTypes, SerializeAuthLevels(PreferredAuthLevelTypes, authLevelAliases));
+ }
+ }
+
+ /// <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;
+
+ PreferredPolicies.Clear();
+ string[] preferredPolicies = extraData[Constants.RequestParameters.PreferredAuthPolicies].Split(' ');
+ foreach (string policy in preferredPolicies) {
+ if (policy.Length > 0)
+ PreferredPolicies.Add(policy);
+ }
+
+ PreferredAuthLevelTypes.Clear();
+ AliasManager authLevelAliases = PapeUtilities.FindIncomingAliases(extraData);
+ string preferredAuthLevelAliases;
+ if (extraData.TryGetValue(Constants.RequestParameters.PreferredAuthLevelTypes, out preferredAuthLevelAliases)) {
+ foreach (string authLevelAlias in preferredAuthLevelAliases.Split(' ')) {
+ if (authLevelAlias.Length == 0) continue;
+ PreferredAuthLevelTypes.Add(authLevelAliases.ResolveAlias(authLevelAlias));
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
new file mode 100644
index 0000000..71514a8
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
@@ -0,0 +1,228 @@
+//-----------------------------------------------------------------------
+// <copyright file="PolicyResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Diagnostics;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The PAPE response part of an OpenID Authentication response message.
+ /// </summary>
+ public sealed class PolicyResponse : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The first part of a parameter name that gives the custom string value for
+ /// the assurance level. The second part of the parameter name is the alias for
+ /// that assurance level.
+ /// </summary>
+ private const string AuthLevelAliasPrefix = "auth_level.";
+
+ /// <summary>
+ /// One or more authentication policy URIs that the OP conformed to when authenticating the End User.
+ /// </summary>
+ /// <value>Space separated list of authentication policy URIs.</value>
+ /// <remarks>
+ /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none".
+ /// </remarks>
+ [MessagePart("auth_policies", IsRequired = true)]
+ private string actualPoliciesString;
+
+ private DateTime? authenticationTimeUtc;
+
+ /// <summary>
+ /// Instantiates a <see cref="PolicyResponse"/>.
+ /// </summary>
+ public PolicyResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ ActualPolicies = new List<string>(1);
+ AssuranceLevels = new Dictionary<string, string>(1);
+ }
+
+ /// <summary>
+ /// One or more authentication policy URIs that the OP conformed to when authenticating the End User.
+ /// </summary>
+ /// <remarks>
+ /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none".
+ /// </remarks>
+ public IList<string> ActualPolicies { get; private set; }
+
+ /// <summary>
+ /// Optional. The most recent timestamp when the End User has actively authenticated to the OP in a manner fitting the asserted policies.
+ /// </summary>
+ /// <remarks>
+ /// If the RP's request included the "openid.max_auth_age" parameter then the OP MUST include "openid.auth_time" in its response. If "openid.max_auth_age" was not requested, the OP MAY choose to include "openid.auth_time" in its response.
+ /// </remarks>
+ [MessagePart("auth_time", Encoder=typeof(DateTimeEncoder))]
+ public DateTime? AuthenticationTimeUtc {
+ get { return authenticationTimeUtc; }
+ set {
+ // Make sure that whatever is set here, it becomes UTC time.
+ if (value.HasValue) {
+ if (value.Value.Kind == DateTimeKind.Unspecified)
+ throw new ArgumentException(OpenIdStrings.UnspecifiedDateTimeKindNotAllowed, "value");
+ authenticationTimeUtc = value.Value.ToUniversalTime();
+ } else {
+ authenticationTimeUtc = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Optional. The Assurance Level as defined by the National Institute of Standards and Technology (NIST) in Special Publication 800-63 (Burr, W., Dodson, D., and W. Polk, Ed., “Electronic Authentication Guideline,” April 2006.) [NIST_SP800‑63] corresponding to the authentication method and policies employed by the OP when authenticating the End User.
+ /// </summary>
+ /// <remarks>
+ /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels.
+ /// </remarks>
+ public NistAssuranceLevel? NistAssuranceLevel {
+ get {
+ string levelString;
+ if (AssuranceLevels.TryGetValue(Constants.AuthenticationLevels.NistTypeUri, out levelString)) {
+ return (NistAssuranceLevel)Enum.Parse(typeof(NistAssuranceLevel), levelString);
+ } else {
+ return null;
+ }
+ }
+ set {
+ if (value != null) {
+ AssuranceLevels[Constants.AuthenticationLevels.NistTypeUri] = ((int)value).ToString(CultureInfo.InvariantCulture);
+ } else {
+ AssuranceLevels.Remove(Constants.AuthenticationLevels.NistTypeUri);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a dictionary where keys are the authentication level type URIs and
+ /// the values are the per authentication level defined custom value.
+ /// </summary>
+ /// <remarks>
+ /// A very common key is <see cref="Constants.AuthenticationLevels.NistTypeUri"/>
+ /// and values for this key are available in <see cref="NistAssuranceLevel"/>.
+ /// </remarks>
+ public IDictionary<string, string> AssuranceLevels { 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) {
+ PolicyResponse other = obj as PolicyResponse;
+ if (other == null) {
+ return false;
+ }
+
+ if (AuthenticationTimeUtc != other.AuthenticationTimeUtc) {
+ return false;
+ }
+
+ if (AssuranceLevels.Count != other.AssuranceLevels.Count) {
+ return false;
+ }
+
+ foreach (var pair in AssuranceLevels) {
+ if (!other.AssuranceLevels.Contains(pair)) {
+ return false;
+ }
+ }
+
+ if (ActualPolicies.Count != other.ActualPolicies.Count) {
+ return false;
+ }
+
+ foreach (string policy in ActualPolicies) {
+ if (!other.ActualPolicies.Contains(policy)) {
+ 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() {
+ // TODO: fix this to match Equals
+ return ActualPolicies.GetHashCode();
+ }
+
+ private static string SerializePolicies(IList<string> policies) {
+ if (policies.Count == 0) {
+ return AuthenticationPolicies.None;
+ } else {
+ return PapeUtilities.ConcatenateListOfElements(policies);
+ }
+ }
+
+ #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;
+ extraData.Clear();
+
+ this.actualPoliciesString = SerializePolicies(this.ActualPolicies);
+
+ if (AssuranceLevels.Count > 0) {
+ AliasManager aliases = new AliasManager();
+ aliases.AssignAliases(AssuranceLevels.Keys, Constants.AuthenticationLevels.PreferredTypeUriToAliasMap);
+
+ // Add a definition for each Auth Level Type alias.
+ foreach (string alias in aliases.Aliases) {
+ extraData.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, aliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list the individual values.
+ foreach (var pair in AssuranceLevels) {
+ extraData.Add(AuthLevelAliasPrefix + aliases.GetAlias(pair.Key), pair.Value);
+ }
+ }
+ }
+
+ /// <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;
+
+ this.ActualPolicies.Clear();
+ string[] actualPolicies = actualPoliciesString.Split(' ');
+ foreach (string policy in actualPolicies) {
+ if (policy.Length > 0 && policy != AuthenticationPolicies.None) {
+ this.ActualPolicies.Add(policy);
+ }
+ }
+
+ this.AssuranceLevels.Clear();
+ AliasManager authLevelAliases = PapeUtilities.FindIncomingAliases(extraData);
+ foreach (string authLevelAlias in authLevelAliases.Aliases) {
+ string authValue;
+ if (extraData.TryGetValue(AuthLevelAliasPrefix + authLevelAlias, out authValue)) {
+ string authLevelType = authLevelAliases.ResolveAlias(authLevelAlias);
+ this.AssuranceLevels[authLevelType] = authValue;
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/TimespanSecondsEncoder.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/TimespanSecondsEncoder.cs
new file mode 100644
index 0000000..d0d73e1
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/TimespanSecondsEncoder.cs
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------
+// <copyright file="TimespanSecondsEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using System.Globalization;
+
+ /// <summary>
+ /// Encodes and decodes the <see cref="TimeSpan"/> as an integer of total seconds.
+ /// </summary>
+ internal class TimespanSecondsEncoder : IMessagePartEncoder {
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ TimeSpan? timeSpan = value as TimeSpan?;
+ if (timeSpan.HasValue) {
+ return timeSpan.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
+ } else {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ return TimeSpan.FromSeconds(double.Parse(value, CultureInfo.InvariantCulture));
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index e5975b8..7a7bb3c 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -259,6 +259,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is not a valid URI..
+ /// </summary>
+ internal static string InvalidUri1 {
+ get {
+ return ResourceManager.GetString("InvalidUri1", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Not a recognized XRI format: &apos;{0}&apos;..
/// </summary>
internal static string InvalidXri {
@@ -443,6 +452,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to Providing a DateTime whose Kind is Unspecified is not allowed..
+ /// </summary>
+ internal static string UnspecifiedDateTimeKindNotAllowed {
+ get {
+ return ResourceManager.GetString("UnspecifiedDateTimeKindNotAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The openid.user_setup_url parameter is required when sending negative assertion messages in response to immediate mode requests..
/// </summary>
internal static string UserSetupUrlRequiredInImmediateNegativeResponse {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index 8afd674..9a76dab 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -253,4 +253,10 @@ Discovered endpoint info:
<data name="AttributeTooManyValues" xml:space="preserve">
<value>Only {0} values for attribute '{1}' were requested, but {2} were supplied.</value>
</data>
+ <data name="InvalidUri1" xml:space="preserve">
+ <value>The value '{0}' is not a valid URI.</value>
+ </data>
+ <data name="UnspecifiedDateTimeKindNotAllowed" xml:space="preserve">
+ <value>Providing a DateTime whose Kind is Unspecified is not allowed.</value>
+ </data>
</root> \ No newline at end of file