//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- 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; /// /// The PAPE response part of an OpenID Authentication response message. /// [Serializable] public sealed class PolicyResponse : ExtensionBase, IMessageWithEvents { /// /// The factory method that may be used in deserialization of this message. /// internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { if (typeUri == Constants.TypeUri && !isProviderRole) { return new PolicyResponse(); } return null; }; /// /// 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. /// private const string AuthLevelAliasPrefix = "auth_level."; /// /// One or more authentication policy URIs that the OP conformed to when authenticating the End User. /// /// Space separated list of authentication policy URIs. /// /// 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". /// [MessagePart("auth_policies", IsRequired = true)] private string actualPoliciesString; /// /// Backing field for the property. /// private DateTime? authenticationTimeUtc; /// /// Initializes a new instance of the class. /// public PolicyResponse() : base(new Version(1, 0), Constants.TypeUri, null) { this.ActualPolicies = new List(1); this.AssuranceLevels = new Dictionary(1); } /// /// Gets a list of authentication policy URIs that the /// OP conformed to when authenticating the End User. /// public IList ActualPolicies { get; private set; } /// /// Gets or sets 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. /// [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; } } } /// /// 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. /// /// /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level /// example classifications of authentication methods within the defined /// levels. /// [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); } } } /// /// 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; } /// /// Gets a value indicating whether this extension is signed by the Provider. /// /// /// true if this instance is signed by the Provider; otherwise, false. /// public bool IsSignedByProvider { get { return this.IsSignedByRemoteParty; } } #region IMessageWithEvents Members /// /// Called when the message is about to be transmitted, /// before it passes through the channel binding elements. /// 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); } } } /// /// Called when the message has been received, /// after it passes through the channel binding elements. /// 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 /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// /// true if the specified is equal to the current ; otherwise, false. /// /// /// The parameter is null. /// 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; } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// 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; } } /// /// Serializes the applied policies for transmission from the Provider /// to the Relying Party. /// /// The applied policies. /// A space-delimited list of applied policies. private static string SerializePolicies(IList policies) { if (policies.Count == 0) { return AuthenticationPolicies.None; } else { return PapeUtilities.ConcatenateListOfElements(policies); } } } }