summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenId/Extensions
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenId/Extensions')
-rw-r--r--src/DotNetOpenId/Extensions/AliasManager.cs60
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs13
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs5
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs5
-rw-r--r--src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs5
-rw-r--r--src/DotNetOpenId/Extensions/IExtension.cs36
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs40
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs90
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs48
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs195
-rw-r--r--src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs194
-rw-r--r--src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs21
-rw-r--r--src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs23
-rw-r--r--src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs3
14 files changed, 720 insertions, 18 deletions
diff --git a/src/DotNetOpenId/Extensions/AliasManager.cs b/src/DotNetOpenId/Extensions/AliasManager.cs
index 52308d5..f4bbb04 100644
--- a/src/DotNetOpenId/Extensions/AliasManager.cs
+++ b/src/DotNetOpenId/Extensions/AliasManager.cs
@@ -37,6 +37,61 @@ namespace DotNetOpenId.Extensions {
aliasToTypeUriMap.Add(alias, typeUri);
typeUriToAliasMap.Add(typeUri, alias);
}
+ /// <summary>
+ /// Takes a sequence of type URIs and assigns aliases for all of them.
+ /// </summary>
+ /// <param name="typeUris">The type URIs to create aliases for.</param>
+ /// <param name="preferredTypeUriToAliases">An optional dictionary of URI/alias pairs that suggest preferred aliases to use if available for certain type URIs.</param>
+ public void AssignAliases(IEnumerable<string> typeUris, IDictionary<string, string> preferredTypeUriToAliases) {
+ // First go through the actually used type URIs and see which ones have matching preferred aliases.
+ if (preferredTypeUriToAliases != null) {
+ foreach (string typeUri in typeUris) {
+ if (typeUriToAliasMap.ContainsKey(typeUri)) {
+ // this Type URI is already mapped to an alias.
+ continue;
+ }
+
+ string preferredAlias;
+ if (preferredTypeUriToAliases.TryGetValue(typeUri, out preferredAlias) && !IsAliasUsed(preferredAlias)) {
+ SetAlias(preferredAlias, typeUri);
+ }
+ }
+ }
+
+ // Now go through the whole list again and assign whatever is left now that the preferred ones
+ // have gotten their picks where available.
+ foreach (string typeUri in typeUris) {
+ if (typeUriToAliasMap.ContainsKey(typeUri)) {
+ // this Type URI is already mapped to an alias.
+ continue;
+ }
+
+ assignNewAlias(typeUri);
+ }
+ }
+ /// <summary>
+ /// Sets up aliases for any Type URIs in a dictionary that do not yet have aliases defined,
+ /// and where the given preferred alias is still available.
+ /// </summary>
+ /// <param name="preferredTypeUriToAliases">A dictionary of type URI keys and alias values.</param>
+ public void SetPreferredAliasesWhereNotSet(IDictionary<string, string> preferredTypeUriToAliases) {
+ if (preferredTypeUriToAliases == null) throw new ArgumentNullException("preferredTypeUriToAliases");
+
+ foreach (var pair in preferredTypeUriToAliases) {
+ if (typeUriToAliasMap.ContainsKey(pair.Key)) {
+ // type URI is already mapped
+ continue;
+ }
+
+ if (aliasToTypeUriMap.ContainsKey(pair.Value)) {
+ // alias is already mapped
+ continue;
+ }
+
+ // The type URI and alias are as yet unset, so go ahead and assign them.
+ SetAlias(pair.Value, pair.Key);
+ }
+ }
/// <summary>
/// Gets the Type Uri encoded by a given alias.
@@ -57,6 +112,11 @@ namespace DotNetOpenId.Extensions {
public IEnumerable<string> Aliases {
get { return aliasToTypeUriMap.Keys; }
}
+ /// <summary>
+ /// Returns a value indicating whether an alias has already been assigned to a type URI.
+ /// </summary>
+ /// <param name="alias">The alias in question.</param>
+ /// <returns>True if the alias has already been assigned. False otherwise.</returns>
public bool IsAliasUsed(string alias) {
if (string.IsNullOrEmpty(alias)) throw new ArgumentNullException("alias");
return aliasToTypeUriMap.ContainsKey(alias);
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs b/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs
index 6e09914..1b64868 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/FetchRequest.cs
@@ -53,6 +53,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionRequest Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionRequest.Serialize(RelyingParty.IAuthenticationRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -84,7 +87,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
return fields;
}
- bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request) {
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request, string typeUri) {
if (fields == null) return false;
string mode;
fields.TryGetValue("mode", out mode);
@@ -111,11 +114,11 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
}
AliasManager aliasManager = new AliasManager();
foreach (var alias in allAliases) {
- string typeUri;
- if (fields.TryGetValue("type." + alias, out typeUri)) {
- aliasManager.SetAlias(alias, typeUri);
+ string attributeTypeUri;
+ if (fields.TryGetValue("type." + alias, out attributeTypeUri)) {
+ aliasManager.SetAlias(alias, attributeTypeUri);
AttributeRequest att = new AttributeRequest {
- TypeUri = typeUri,
+ TypeUri = attributeTypeUri,
IsRequired = requiredAliases.Contains(alias),
};
string countString;
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs
index ddd3087..3878d2c 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/FetchResponse.cs
@@ -55,6 +55,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionResponse Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionResponse.Serialize(Provider.IRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -87,7 +90,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
}
}
- bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response) {
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response, string typeUri) {
if (fields == null) return false;
string mode;
fields.TryGetValue("mode", out mode);
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs b/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs
index 3df74a0..6b5ce2b 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/StoreRequest.cs
@@ -53,6 +53,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionRequest Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionRequest.Serialize(RelyingParty.IAuthenticationRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -64,7 +67,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
return fields;
}
- bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request) {
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request, string typeUri) {
if (fields == null) return false;
string mode;
fields.TryGetValue("mode", out mode);
diff --git a/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs
index a4e9cf1..acc0d6b 100644
--- a/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs
+++ b/src/DotNetOpenId/Extensions/AttributeExchange/StoreResponse.cs
@@ -24,6 +24,9 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
#region IExtensionResponse Members
string IExtension.TypeUri { get { return Constants.TypeUri; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
IDictionary<string, string> IExtensionResponse.Serialize(Provider.IRequest authenticationRequest) {
var fields = new Dictionary<string, string> {
@@ -35,7 +38,7 @@ namespace DotNetOpenId.Extensions.AttributeExchange {
return fields;
}
- bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response) {
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, IAuthenticationResponse response, string typeUri) {
if (fields == null) return false;
string mode;
if (!fields.TryGetValue("mode", out mode)) return false;
diff --git a/src/DotNetOpenId/Extensions/IExtension.cs b/src/DotNetOpenId/Extensions/IExtension.cs
index 2450a8a..e43565a 100644
--- a/src/DotNetOpenId/Extensions/IExtension.cs
+++ b/src/DotNetOpenId/Extensions/IExtension.cs
@@ -11,6 +11,22 @@ namespace DotNetOpenId.Extensions {
/// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
/// </summary>
string TypeUri { get; }
+ /// <summary>
+ /// Additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ IEnumerable<string> AdditionalSupportedTypeUris { get; }
}
/// <summary>
@@ -26,8 +42,14 @@ namespace DotNetOpenId.Extensions {
/// <summary>
/// Reads the extension information on an authentication request to the provider.
/// </summary>
- /// <returns>True if the extension found any of its parameters in the request, false otherwise.</returns>
- bool Deserialize(IDictionary<string, string> fields, Provider.IRequest request);
+ /// <param name="fields">The fields belonging to the extension.</param>
+ /// <param name="request">The incoming OpenID request carrying the extension.</param>
+ /// <param name="typeUri">The actual extension TypeUri that was recognized in the message.</param>
+ /// <returns>
+ /// True if the extension found a valid set of recognized parameters in the request,
+ /// false otherwise.
+ /// </returns>
+ bool Deserialize(IDictionary<string, string> fields, Provider.IRequest request, string typeUri);
}
/// <summary>
@@ -43,7 +65,13 @@ namespace DotNetOpenId.Extensions {
/// <summary>
/// Reads a Provider's response for extension values.
/// </summary>
- /// <returns>True if the extension found any of its parameters in the response.</returns>
- bool Deserialize(IDictionary<string, string> fields, RelyingParty.IAuthenticationResponse response);
+ /// <param name="fields">The fields belonging to the extension.</param>
+ /// <param name="response">The incoming OpenID response carrying the extension.</param>
+ /// <param name="typeUri">The actual extension TypeUri that was recognized in the message.</param>
+ /// <returns>
+ /// True if the extension found a valid set of recognized parameters in the response,
+ /// false otherwise.
+ /// </returns>
+ bool Deserialize(IDictionary<string, string> fields, RelyingParty.IAuthenticationResponse response, string typeUri);
}
}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
new file mode 100644
index 0000000..f505a9b
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <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>
+ [System.Diagnostics.CodeAnalysis.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>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")]
+ [System.Diagnostics.CodeAnalysis.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>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor")]
+ [System.Diagnostics.CodeAnalysis.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/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
new file mode 100644
index 0000000..395ea36
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// OpenID Provider Authentication Policy extension constants.
+ /// </summary>
+ 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";
+ }
+ /// <summary>
+ /// Parameters to be included with PAPE responses.
+ /// </summary>
+ internal static class ResponseParameters {
+ /// <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>
+ internal const string AuthPolicies = "auth_policies";
+ /// <summary>
+ /// Optional. The most recent timestamp when the End User has actively authenticated to the OP in a manner fitting the asserted policies.
+ /// </summary>
+ /// <value>
+ /// 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
+ /// </value>
+ /// <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>
+ internal const string AuthTime = "auth_time";
+ /// <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>
+ internal const string AuthLevelAliasPrefix = "auth_level.";
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
new file mode 100644
index 0000000..6358294
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <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/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
new file mode 100644
index 0000000..eb78e1f
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Globalization;
+using System.Diagnostics;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// The PAPE request part of an OpenID Authentication request message.
+ /// </summary>
+ public sealed class PolicyRequest : IExtensionRequest {
+ /// <summary>
+ /// Instantiates a new <see cref="PolicyRequest"/>.
+ /// </summary>
+ public PolicyRequest() {
+ 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>
+ 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>
+ /// Tests equality between two <see cref="PolicyRequest"/> instances.
+ /// </summary>
+ 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>
+ /// Gets a hash code for this object.
+ /// </summary>
+ public override int GetHashCode() {
+ return PreferredPolicies.GetHashCode();
+ }
+
+ #region IExtensionRequest Members
+
+ IDictionary<string, string> IExtensionRequest.Serialize(DotNetOpenId.RelyingParty.IAuthenticationRequest authenticationRequest) {
+ var fields = new Dictionary<string, string>();
+
+ if (MaximumAuthenticationAge.HasValue) {
+ fields.Add(Constants.RequestParameters.MaxAuthAge,
+ MaximumAuthenticationAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture));
+ }
+
+ // Even if empty, this parameter is required as part of the request message.
+ fields.Add(Constants.RequestParameters.PreferredAuthPolicies, SerializePolicies(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) {
+ fields.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, authLevelAliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list a preferred order.
+ fields.Add(Constants.RequestParameters.PreferredAuthLevelTypes, SerializeAuthLevels(PreferredAuthLevelTypes, authLevelAliases));
+ }
+
+ return fields;
+ }
+
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> fields, DotNetOpenId.Provider.IRequest request, string typeUri) {
+ if (fields == null) return false;
+ if (!fields.ContainsKey(Constants.RequestParameters.PreferredAuthPolicies)) return false;
+
+ string maxAuthAge;
+ MaximumAuthenticationAge = fields.TryGetValue(Constants.RequestParameters.MaxAuthAge, out maxAuthAge) ?
+ TimeSpan.FromSeconds(double.Parse(maxAuthAge, CultureInfo.InvariantCulture)) : (TimeSpan?)null;
+
+ PreferredPolicies.Clear();
+ string[] preferredPolicies = fields[Constants.RequestParameters.PreferredAuthPolicies].Split(' ');
+ foreach (string policy in preferredPolicies) {
+ if (policy.Length > 0)
+ PreferredPolicies.Add(policy);
+ }
+
+ PreferredAuthLevelTypes.Clear();
+ AliasManager authLevelAliases = FindIncomingAliases(fields);
+ string preferredAuthLevelAliases;
+ if (fields.TryGetValue(Constants.RequestParameters.PreferredAuthLevelTypes, out preferredAuthLevelAliases)) {
+ foreach (string authLevelAlias in preferredAuthLevelAliases.Split(' ')) {
+ if (authLevelAlias.Length == 0) continue;
+ PreferredAuthLevelTypes.Add(authLevelAliases.ResolveAlias(authLevelAlias));
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region IExtension Members
+
+ string IExtension.TypeUri {
+ get { return Constants.TypeUri; }
+ }
+
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
+
+ #endregion
+
+ static internal string SerializePolicies(IList<string> policies) {
+ return 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 ConcatenateListOfElements(aliasList);
+ }
+
+ /// <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) {
+ Debug.Assert(values != null);
+ StringBuilder valuesList = new StringBuilder();
+ foreach (string value in GetUniqueItems(values)) {
+ if (value.Contains(" ")) {
+ throw new FormatException(string.Format(CultureInfo.CurrentCulture,
+ Strings.InvalidUri, value));
+ }
+ valuesList.Append(value);
+ valuesList.Append(" ");
+ }
+ if (valuesList.Length > 0)
+ valuesList.Length -= 1; // remove trailing space
+ return valuesList.ToString();
+ }
+
+ 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;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
new file mode 100644
index 0000000..0f5922f
--- /dev/null
+++ b/src/DotNetOpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy {
+ /// <summary>
+ /// The PAPE response part of an OpenID Authentication response message.
+ /// </summary>
+ public sealed class PolicyResponse : IExtensionResponse {
+ // This array of formats is not yet a complete list.
+ static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" };
+
+ /// <summary>
+ /// Instantiates a <see cref="PolicyResponse"/>.
+ /// </summary>
+ public PolicyResponse() {
+ 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; }
+ DateTime? authenticationTimeUtc;
+ /// <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>
+ 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(Strings.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>
+ /// Tests equality between two <see cref="PolicyResponse"/> instances.
+ /// </summary>
+ 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>
+ /// Gets a hash code for this object.
+ /// </summary>
+ public override int GetHashCode() {
+ return ActualPolicies.GetHashCode();
+ }
+
+ #region IExtensionResponse Members
+
+ IDictionary<string, string> IExtensionResponse.Serialize(DotNetOpenId.Provider.IRequest authenticationRequest) {
+ var fields = new Dictionary<string, string>();
+
+ fields.Add(Constants.ResponseParameters.AuthPolicies, SerializePolicies(ActualPolicies));
+ if (AuthenticationTimeUtc.HasValue) {
+ fields.Add(Constants.ResponseParameters.AuthTime, AuthenticationTimeUtc.Value.ToUniversalTime().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture));
+ }
+
+ 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) {
+ fields.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) {
+ fields.Add(Constants.ResponseParameters.AuthLevelAliasPrefix + aliases.GetAlias(pair.Key), pair.Value);
+ }
+ }
+
+ return fields;
+ }
+
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> fields, DotNetOpenId.RelyingParty.IAuthenticationResponse response, string typeUri) {
+ if (fields == null) return false;
+ if (!fields.ContainsKey(Constants.ResponseParameters.AuthPolicies)) return false;
+
+ ActualPolicies.Clear();
+ string[] actualPolicies = fields[Constants.ResponseParameters.AuthPolicies].Split(' ');
+ foreach (string policy in actualPolicies) {
+ if (policy.Length > 0 && policy != AuthenticationPolicies.None)
+ ActualPolicies.Add(policy);
+ }
+
+ AuthenticationTimeUtc = null;
+ string authTime;
+ if (fields.TryGetValue(Constants.ResponseParameters.AuthTime, out authTime)) {
+ DateTime authDateTime;
+ if (DateTime.TryParse(authTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out authDateTime) &&
+ authDateTime.Kind == DateTimeKind.Utc) { // may be unspecified per our option above
+ AuthenticationTimeUtc = authDateTime;
+ } else {
+ Logger.ErrorFormat("Invalid format for {0} parameter: {1}",
+ Constants.ResponseParameters.AuthTime, authTime);
+ }
+ }
+
+ AssuranceLevels.Clear();
+ AliasManager authLevelAliases = PolicyRequest.FindIncomingAliases(fields);
+ foreach (string authLevelAlias in authLevelAliases.Aliases) {
+ string authValue;
+ if (fields.TryGetValue(Constants.ResponseParameters.AuthLevelAliasPrefix + authLevelAlias, out authValue)) {
+ string authLevelType = authLevelAliases.ResolveAlias(authLevelAlias);
+ AssuranceLevels[authLevelType] = authValue;
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region IExtension Members
+
+ string IExtension.TypeUri {
+ get { return Constants.TypeUri; }
+ }
+
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
+
+ #endregion
+
+ static internal string SerializePolicies(IList<string> policies) {
+ if (policies.Count == 0) {
+ return AuthenticationPolicies.None;
+ } else {
+ return PolicyRequest.ConcatenateListOfElements(policies);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
index e3cca83..e5d282e 100644
--- a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
+++ b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
@@ -128,9 +128,18 @@ namespace DotNetOpenId.Extensions.SimpleRegistration {
#region IExtensionRequest Members
string IExtension.TypeUri { get { return Constants.sreg_ns; } }
+ static readonly string[] additionalTypeUris = new string[] {
+ Constants.sreg_ns10,
+ Constants.sreg_ns11other,
+ };
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return additionalTypeUris; }
+ }
- bool IExtensionRequest.Deserialize(IDictionary<string, string> args, IRequest request) {
+ bool IExtensionRequest.Deserialize(IDictionary<string, string> args, IRequest request, string typeUri) {
if (args == null) return false;
+ Debug.Assert(!string.IsNullOrEmpty(typeUri));
+ typeUriDeserializedFrom = typeUri;
string policyUrl;
if (args.TryGetValue(Constants.policy_url, out policyUrl)
@@ -168,10 +177,18 @@ namespace DotNetOpenId.Extensions.SimpleRegistration {
}
#endregion
+ string typeUriDeserializedFrom;
+ /// <summary>
+ /// Prepares a Simple Registration response extension that is compatible with the
+ /// version of Simple Registration used in the request message.
+ /// </summary>
+ public ClaimsResponse CreateResponse() {
+ return new ClaimsResponse(typeUriDeserializedFrom);
+ }
+
/// <summary>
/// Renders the requested information as a string.
/// </summary>
- /// <returns></returns>
public override string ToString() {
return string.Format(CultureInfo.CurrentCulture, @"Nickname = '{0}'
Email = '{1}'
diff --git a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
index 88f0b35..0203bce 100644
--- a/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
+++ b/src/DotNetOpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
@@ -24,6 +24,20 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals"), Serializable()]
public sealed class ClaimsResponse : IExtensionResponse
{
+ string typeUriToUse;
+
+ /// <summary>
+ /// Creates an instance of the <see cref="ClaimsResponse"/> class.
+ /// </summary>
+ [Obsolete("Use ClaimsRequest.CreateResponse() instead.")]
+ public ClaimsResponse() : this(Constants.sreg_ns) {
+ }
+
+ internal ClaimsResponse(string typeUriToUse) {
+ if (string.IsNullOrEmpty(typeUriToUse)) throw new ArgumentNullException("typeUriToUse");
+ this.typeUriToUse = typeUriToUse;
+ }
+
/// <summary>
/// The nickname the user goes by.
/// </summary>
@@ -39,7 +53,7 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
{
get
{
- if (Email == null) return null;
+ if (string.IsNullOrEmpty(Email)) return null;
if (string.IsNullOrEmpty(FullName))
return new MailAddress(Email);
else
@@ -104,7 +118,10 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
public string TimeZone { get; set; }
#region IExtensionResponse Members
- string IExtension.TypeUri { get { return Constants.sreg_ns; } }
+ string IExtension.TypeUri { get { return typeUriToUse; } }
+ IEnumerable<string> IExtension.AdditionalSupportedTypeUris {
+ get { return new string[0]; }
+ }
/// <summary>
/// Adds the values of this struct to an authentication response being prepared
@@ -148,7 +165,7 @@ namespace DotNetOpenId.Extensions.SimpleRegistration
return fields;
}
- bool IExtensionResponse.Deserialize(IDictionary<string, string> sreg, IAuthenticationResponse response) {
+ bool IExtensionResponse.Deserialize(IDictionary<string, string> sreg, IAuthenticationResponse response, string typeUri) {
if (sreg == null) return false;
string nickname, email, fullName, dob, genderString, postalCode, country, language, timeZone;
BirthDate = null;
diff --git a/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs b/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs
index fc4066e..8aa9591 100644
--- a/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs
+++ b/src/DotNetOpenId/Extensions/SimpleRegistration/Constants.cs
@@ -7,8 +7,9 @@ namespace DotNetOpenId.Extensions.SimpleRegistration {
/// Simple Registration constants
/// </summary>
internal static class Constants {
- internal const string TypeUri = "http://openid.net/sreg/1.0";
internal const string sreg_ns = "http://openid.net/extensions/sreg/1.1";
+ internal const string sreg_ns10 = "http://openid.net/sreg/1.0";
+ internal const string sreg_ns11other = "http://openid.net/sreg/1.1";
internal const string sreg_compatibility_alias = "sreg";
internal const string policy_url = "policy_url";
internal const string optional = "optional";