summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
blob: 880a25e795dd35bf3cf75a194056350c56fdcaa7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
//-----------------------------------------------------------------------
// <copyright file="PolicyResponse.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. 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.pape.max_auth_age" parameter 
		/// then the OP MUST include "openid.pape.auth_time" in its response. 
		/// If "openid.pape.max_auth_age" was not requested, the OP MAY choose to include 
		/// "openid.pape.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);
			}
		}
	}
}