summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs70
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs77
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs69
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs60
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs65
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs222
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs282
7 files changed, 845 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
new file mode 100644
index 0000000..99c7a2e
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------
+// <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>
+ /// 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", Justification = "By design")]
+ 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", Justification = "By design")]
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor", Justification = "By design")]
+ 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", Justification = "By design")]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Justification = "By design")]
+ public const string PhysicalMultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical";
+
+ /// <summary>
+ /// Indicates that the Provider MUST use a pair-wise pseudonym for the user that is persistent
+ /// and unique across the requesting realm as the openid.claimed_id and openid.identity (see Section 4.2).
+ /// </summary>
+ public const string PrivatePersonalIdentifier = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier";
+
+ /// <summary>
+ /// Indicates that the OP MUST only respond with a positive assertion if the requirements demonstrated
+ /// by the OP to obtain certification by a Federally adopted Trust Framework Provider have been met.
+ /// </summary>
+ /// <remarks>
+ /// Notwithstanding the RP may request this authentication policy, the RP MUST still
+ /// verify that this policy appears in the positive assertion response rather than assume the OP
+ /// recognized and complied with the request.
+ /// </remarks>
+ public const string USGovernmentTrustLevel1 = "http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf";
+
+ /// <summary>
+ /// Indicates that the OP MUST not include any OpenID Attribute Exchange or Simple Registration
+ /// information regarding the user in the assertion.
+ /// </summary>
+ public const string NoPersonallyIdentifiableInformation = "http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf";
+
+ /// <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";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
new file mode 100644
index 0000000..93e76d5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------
+// <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 CompatibilityAlias = "pape";
+
+ /// <summary>
+ /// The string to prepend on an Auth Level Type alias definition.
+ /// </summary>
+ internal const string AuthLevelNamespaceDeclarationPrefix = "auth_level.ns.";
+
+ /// <summary>
+ /// Well-known assurance level Type URIs.
+ /// </summary>
+ internal static class AssuranceLevels {
+ /// <summary>
+ /// A mapping between the PAPE TypeURI and the alias to use if
+ /// possible for backward compatibility reasons.
+ /// </summary>
+ internal static readonly IDictionary<string, string> PreferredTypeUriToAliasMap = new Dictionary<string, string> {
+ { NistTypeUri, "nist" },
+ };
+
+ /// <summary>
+ /// The Type URI of the NIST assurance level.
+ /// </summary>
+ 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/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs
new file mode 100644
index 0000000..9dc0574
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------
+// <copyright file="DateTimeEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// An encoder/decoder design for DateTimes that must conform to the PAPE spec.
+ /// </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.ToUniversalTimeSafe().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.OpenId.ErrorFormat("Invalid format for message part: {0}", value);
+ return null;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
new file mode 100644
index 0000000..3031aad
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------
+// <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.Diagnostics.CodeAnalysis;
+ 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>
+ /// <para>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 DotNetOpenAuth documentation.
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf</para>
+ /// <para>
+ /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels.
+ /// </para>
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Justification = "By design")]
+ 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/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs
new file mode 100644
index 0000000..d8ffb63
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------
+// <copyright file="PapeUtilities.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.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Utility methods for use by the PAPE extension.
+ /// </summary>
+ internal static class PapeUtilities {
+ /// <summary>
+ /// Looks at the incoming fields and figures out what the aliases and name spaces for auth level types are.
+ /// </summary>
+ /// <param name="fields">The incoming message data in which to discover TypeURIs and aliases.</param>
+ /// <returns>The <see cref="AliasManager"/> initialized with the given data.</returns>
+ 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.AssuranceLevels.PreferredTypeUriToAliasMap);
+
+ return aliasManager;
+ }
+
+ /// <summary>
+ /// Concatenates a sequence of strings using a space as a separator.
+ /// </summary>
+ /// <param name="values">The elements to concatenate together..</param>
+ /// <returns>The concatenated string of elements.</returns>
+ /// <exception cref="FormatException">Thrown if any element in the sequence includes a space.</exception>
+ internal static string ConcatenateListOfElements(IEnumerable<string> values) {
+ Requires.NotNull(values, "values");
+
+ StringBuilder valuesList = new StringBuilder();
+ foreach (string value in values.Distinct()) {
+ 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/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
new file mode 100644
index 0000000..84589dc
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
@@ -0,0 +1,222 @@
+//-----------------------------------------------------------------------
+// <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 DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The PAPE request part of an OpenID Authentication request message.
+ /// </summary>
+ [Serializable]
+ public sealed class PolicyRequest : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && isProviderRole) {
+ return new PolicyRequest();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The transport field for the RP's preferred authentication policies.
+ /// </summary>
+ /// <remarks>
+ /// This field is written to/read from during custom serialization.
+ /// </remarks>
+ [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) {
+ this.PreferredPolicies = new List<string>(1);
+ this.PreferredAuthLevelTypes = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum acceptable time since the End User has
+ /// actively authenticated to the OP in a manner fitting the requested
+ /// policies, beyond which the Provider 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>
+ /// Gets the list of 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>
+ /// Gets the namespaces of the custom Assurance Level the
+ /// Relying Party requests, in the order of its preference.
+ /// </summary>
+ public IList<string> PreferredAuthLevelTypes { get; private set; }
+
+ #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 (this.PreferredAuthLevelTypes.Count > 0) {
+ AliasManager authLevelAliases = new AliasManager();
+ authLevelAliases.AssignAliases(this.PreferredAuthLevelTypes, Constants.AssuranceLevels.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(this.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;
+
+ this.PreferredPolicies.Clear();
+ string[] preferredPolicies = this.preferredPoliciesString.Split(' ');
+ foreach (string policy in preferredPolicies) {
+ if (policy.Length > 0) {
+ this.PreferredPolicies.Add(policy);
+ }
+ }
+
+ this.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;
+ }
+ this.PreferredAuthLevelTypes.Add(authLevelAliases.ResolveAlias(authLevelAlias));
+ }
+ }
+ }
+
+ #endregion
+
+ /// <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 (this.MaximumAuthenticationAge != other.MaximumAuthenticationAge) {
+ return false;
+ }
+
+ if (this.PreferredPolicies.Count != other.PreferredPolicies.Count) {
+ return false;
+ }
+
+ foreach (string policy in this.PreferredPolicies) {
+ if (!other.PreferredPolicies.Contains(policy)) {
+ return false;
+ }
+ }
+
+ if (this.PreferredAuthLevelTypes.Count != other.PreferredAuthLevelTypes.Count) {
+ return false;
+ }
+
+ foreach (string authLevel in this.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() {
+ // This is a poor hash function, but an site that cares will likely have a bunch
+ // of look-alike instances anyway, so a good hash function would still bunch
+ // all the instances into the same hash code.
+ if (this.MaximumAuthenticationAge.HasValue) {
+ return this.MaximumAuthenticationAge.Value.GetHashCode();
+ } else {
+ return 1;
+ }
+ }
+
+ /// <summary>
+ /// Serializes the policies as a single string per the PAPE spec..
+ /// </summary>
+ /// <param name="policies">The policies to include in the list.</param>
+ /// <returns>The concatenated string of the given policies.</returns>
+ private static string SerializePolicies(IEnumerable<string> policies) {
+ return PapeUtilities.ConcatenateListOfElements(policies);
+ }
+
+ /// <summary>
+ /// Serializes the auth levels to a list of aliases.
+ /// </summary>
+ /// <param name="preferredAuthLevelTypes">The preferred auth level types.</param>
+ /// <param name="aliases">The alias manager.</param>
+ /// <returns>A space-delimited list of aliases.</returns>
+ 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);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
new file mode 100644
index 0000000..1fddc22
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
@@ -0,0 +1,282 @@
+//-----------------------------------------------------------------------
+// <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.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The PAPE response part of an OpenID Authentication response message.
+ /// </summary>
+ [Serializable]
+ public sealed class PolicyResponse : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && !isProviderRole) {
+ return new PolicyResponse();
+ }
+
+ return null;
+ };
+
+ /// <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;
+
+ /// <summary>
+ /// Backing field for the <see cref="AuthenticationTimeUtc"/> property.
+ /// </summary>
+ private DateTime? authenticationTimeUtc;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PolicyResponse"/> class.
+ /// </summary>
+ public PolicyResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ this.ActualPolicies = new List<string>(1);
+ this.AssuranceLevels = new Dictionary<string, string>(1);
+ }
+
+ /// <summary>
+ /// Gets a list of authentication policy URIs that the
+ /// OP conformed to when authenticating the End User.
+ /// </summary>
+ public IList<string> ActualPolicies { get; private set; }
+
+ /// <summary>
+ /// Gets or sets 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 this.authenticationTimeUtc;
+ }
+
+ set {
+ Requires.True(!value.HasValue || value.Value.Kind != DateTimeKind.Unspecified, "value", OpenIdStrings.UnspecifiedDateTimeKindNotAllowed);
+
+ // Make sure that whatever is set here, it becomes UTC time.
+ if (value.HasValue) {
+ // Convert to UTC and cut to the second, since the protocol only allows for
+ // that level of precision.
+ this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTimeSafe());
+ } else {
+ this.authenticationTimeUtc = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets 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>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Justification = "Acronym")]
+ public NistAssuranceLevel? NistAssuranceLevel {
+ get {
+ string levelString;
+ if (this.AssuranceLevels.TryGetValue(Constants.AssuranceLevels.NistTypeUri, out levelString)) {
+ return (NistAssuranceLevel)Enum.Parse(typeof(NistAssuranceLevel), levelString);
+ } else {
+ return null;
+ }
+ }
+
+ set {
+ if (value != null) {
+ this.AssuranceLevels[Constants.AssuranceLevels.NistTypeUri] = ((int)value).ToString(CultureInfo.InvariantCulture);
+ } else {
+ this.AssuranceLevels.Remove(Constants.AssuranceLevels.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.AssuranceLevels.NistTypeUri"/>
+ /// and values for this key are available in <see cref="NistAssuranceLevel"/>.
+ /// </remarks>
+ public IDictionary<string, string> AssuranceLevels { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this extension is signed by the Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByProvider {
+ get { return this.IsSignedByRemoteParty; }
+ }
+
+ #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 (this.AssuranceLevels.Count > 0) {
+ AliasManager aliases = new AliasManager();
+ aliases.AssignAliases(this.AssuranceLevels.Keys, Constants.AssuranceLevels.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 this.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 = this.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
+
+ /// <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 (this.AuthenticationTimeUtc != other.AuthenticationTimeUtc) {
+ return false;
+ }
+
+ if (this.AssuranceLevels.Count != other.AssuranceLevels.Count) {
+ return false;
+ }
+
+ foreach (var pair in this.AssuranceLevels) {
+ if (!other.AssuranceLevels.Contains(pair)) {
+ return false;
+ }
+ }
+
+ if (this.ActualPolicies.Count != other.ActualPolicies.Count) {
+ return false;
+ }
+
+ foreach (string policy in this.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() {
+ // This is a poor hash function, but an site that cares will likely have a bunch
+ // of look-alike instances anyway, so a good hash function would still bunch
+ // all the instances into the same hash code.
+ if (this.AuthenticationTimeUtc.HasValue) {
+ return this.AuthenticationTimeUtc.Value.GetHashCode();
+ } else {
+ return 1;
+ }
+ }
+
+ /// <summary>
+ /// Serializes the applied policies for transmission from the Provider
+ /// to the Relying Party.
+ /// </summary>
+ /// <param name="policies">The applied policies.</param>
+ /// <returns>A space-delimited list of applied policies.</returns>
+ private static string SerializePolicies(IList<string> policies) {
+ if (policies.Count == 0) {
+ return AuthenticationPolicies.None;
+ } else {
+ return PapeUtilities.ConcatenateListOfElements(policies);
+ }
+ }
+ }
+}