using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Globalization; namespace DotNetOpenId.Extensions.ProviderAuthenticationPolicy { /// /// The PAPE response part of an OpenID Authentication response message. /// public sealed class PolicyResponse : IExtensionResponse { // This array of formats is not yet a complete list. static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" }; /// /// Instantiates a . /// public PolicyResponse() { ActualPolicies = new List(1); AssuranceLevels = new Dictionary(1); } /// /// One or more authentication policy URIs that the OP conformed to when authenticating the End User. /// /// /// 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". /// public IList ActualPolicies { get; private set; } DateTime? authenticationTimeUtc; /// /// Optional. The most recent timestamp when the End User has actively authenticated to the OP in a manner fitting the asserted policies. /// /// /// 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. /// 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; } } } /// /// 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. /// /// /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels. /// 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); } } } /// /// Gets a dictionary where keys are the authentication level type URIs and /// the values are the per authentication level defined custom value. /// /// /// A very common key is /// and values for this key are available in . /// public IDictionary AssuranceLevels { get; private set; } /// /// Tests equality between two instances. /// 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; } /// /// Gets a hash code for this object. /// public override int GetHashCode() { return ActualPolicies.GetHashCode(); } #region IExtensionResponse Members IDictionary IExtensionResponse.Serialize(DotNetOpenId.Provider.IRequest authenticationRequest) { var fields = new Dictionary(); 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 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 IExtension.AdditionalSupportedTypeUris { get { return new string[0]; } } #endregion static internal string SerializePolicies(IList policies) { if (policies.Count == 0) { return AuthenticationPolicies.None; } else { return PolicyRequest.ConcatenateListOfElements(policies); } } } }