summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/AuthenticationRequest.cs
blob: 80d8aeb2ef5ccf587d923d611c055203ad6c1f79 (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
//-----------------------------------------------------------------------
// <copyright file="AuthenticationRequest.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.OpenId.Provider {
	using System;
	using System.Threading;
	using System.Threading.Tasks;
	using DotNetOpenAuth.Messaging;
	using DotNetOpenAuth.OpenId.Messages;
	using Validation;

	/// <summary>
	/// Implements the <see cref="IAuthenticationRequest"/> interface
	/// so that OpenID Provider sites can easily respond to authentication
	/// requests.
	/// </summary>
	[Serializable]
	internal class AuthenticationRequest : HostProcessedRequest, IAuthenticationRequest {
		/// <summary>
		/// The positive assertion to send, if the host site chooses to send it.
		/// </summary>
		private readonly PositiveAssertionResponse positiveResponse;

		/// <summary>
		/// Initializes a new instance of the <see cref="AuthenticationRequest"/> class.
		/// </summary>
		/// <param name="provider">The provider that received the request.</param>
		/// <param name="request">The incoming authentication request message.</param>
		internal AuthenticationRequest(OpenIdProvider provider, CheckIdRequest request)
			: base(provider, request) {
			Requires.NotNull(provider, "provider");

			this.positiveResponse = new PositiveAssertionResponse(request);

			if (this.ClaimedIdentifier == Protocol.ClaimedIdentifierForOPIdentifier &&
				Protocol.ClaimedIdentifierForOPIdentifier != null) {
				// Force the hosting OP to deal with identifier_select by nulling out the two identifiers.
				this.IsDirectedIdentity = true;
				this.positiveResponse.ClaimedIdentifier = null;
				this.positiveResponse.LocalIdentifier = null;
			}

			// URL delegation is only detectable from 2.0 RPs, since openid.claimed_id isn't included from 1.0 RPs.
			// If the openid.claimed_id is present, and if it's different than the openid.identity argument, then
			// the RP has discovered a claimed identifier that has delegated authentication to this Provider.
			this.IsDelegatedIdentifier = this.ClaimedIdentifier != null && this.ClaimedIdentifier != this.LocalIdentifier;

			Reporting.RecordEventOccurrence("AuthenticationRequest.IsDelegatedIdentifier", this.IsDelegatedIdentifier.ToString());
		}

		#region HostProcessedRequest members

		/// <summary>
		/// Gets or sets the provider endpoint.
		/// </summary>
		/// <value>
		/// The default value is the URL that the request came in on from the relying party.
		/// </value>
		public override Uri ProviderEndpoint {
			get { return this.positiveResponse.ProviderEndpoint; }
			set { this.positiveResponse.ProviderEndpoint = value; }
		}

		#endregion

		/// <summary>
		/// Gets a value indicating whether the response is ready to be created and sent.
		/// </summary>
		public override bool IsResponseReady {
			get {
				// The null checks on the identifiers is to make sure that an identifier_select
				// has been resolved to actual identifiers.
				return this.IsAuthenticated.HasValue &&
					(!this.IsAuthenticated.Value || !this.IsDirectedIdentity || (this.LocalIdentifier != null && this.ClaimedIdentifier != null));
			}
		}

		#region IAuthenticationRequest Properties

		/// <summary>
		/// Gets a value indicating whether the Provider should help the user
		/// select a Claimed Identifier to send back to the relying party.
		/// </summary>
		public bool IsDirectedIdentity { get; private set; }

		/// <summary>
		/// Gets a value indicating whether the requesting Relying Party is using a delegated URL.
		/// </summary>
		/// <remarks>
		/// When delegated identifiers are used, the <see cref="ClaimedIdentifier"/> should not
		/// be changed at the Provider during authentication.
		/// Delegation is only detectable on requests originating from OpenID 2.0 relying parties.
		/// A relying party implementing only OpenID 1.x may use delegation and this property will
		/// return false anyway.
		/// </remarks>
		public bool IsDelegatedIdentifier { get; private set; }

		/// <summary>
		/// Gets or sets the Local Identifier to this OpenID Provider of the user attempting
		/// to authenticate.  Check <see cref="IsDirectedIdentity"/> to see if
		/// this value is valid.
		/// </summary>
		/// <remarks>
		/// This may or may not be the same as the Claimed Identifier that the user agent
		/// originally supplied to the relying party.  The Claimed Identifier
		/// endpoint may be delegating authentication to this provider using
		/// this provider's local id, which is what this property contains.
		/// Use this identifier when looking up this user in the provider's user account
		/// list.
		/// </remarks>
		public Identifier LocalIdentifier {
			get {
				return this.positiveResponse.LocalIdentifier;
			}

			set {
				// Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity.
				if (this.IsDirectedIdentity) {
					if (this.ClaimedIdentifier != null && this.ClaimedIdentifier != value) {
						throw new InvalidOperationException(OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers);
					}

					this.positiveResponse.ClaimedIdentifier = value;
				}

				this.positiveResponse.LocalIdentifier = value;
			}
		}

		/// <summary>
		/// Gets or sets the identifier that the user agent is claiming at the relying party site.
		/// Check <see cref="IsDirectedIdentity"/> to see if this value is valid.
		/// </summary>
		/// <remarks>
		/// 	<para>This property can only be set if <see cref="IsDelegatedIdentifier"/> is
		/// false, to prevent breaking URL delegation.</para>
		/// 	<para>This will not be the same as this provider's local identifier for the user
		/// if the user has set up his/her own identity page that points to this
		/// provider for authentication.</para>
		/// 	<para>The provider may use this identifier for displaying to the user when
		/// asking for the user's permission to authenticate to the relying party.</para>
		/// </remarks>
		/// <exception cref="InvalidOperationException">Thrown from the setter
		/// if <see cref="IsDelegatedIdentifier"/> is true.</exception>
		public Identifier ClaimedIdentifier {
			get {
				return this.positiveResponse.ClaimedIdentifier;
			}

			set {
				// Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity.
				if (this.IsDirectedIdentity) {
					this.positiveResponse.LocalIdentifier = value;
				}

				this.positiveResponse.ClaimedIdentifier = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the provider has determined that the
		/// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user
		/// and wishes to share this information with the consumer.
		/// </summary>
		public bool? IsAuthenticated { get; set; }

		#endregion

		/// <summary>
		/// Gets the original request message.
		/// </summary>
		protected new CheckIdRequest RequestMessage {
			get { return (CheckIdRequest)base.RequestMessage; }
		}

		#region IAuthenticationRequest Methods

		/// <summary>
		/// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier.
		/// Useful for identifier recycling.
		/// </summary>
		/// <param name="fragment">Should not include the # prefix character as that will be added internally.
		/// May be null or the empty string to clear a previously set fragment.</param>
		/// <remarks>
		/// 	<para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if
		/// using directed identity, this method can be called on any URI claimed identifier.</para>
		/// 	<para>Because XRI claimed identifiers (the canonical IDs) are never recycled,
		/// this method should<i>not</i> be called for XRIs.</para>
		/// </remarks>
		/// <exception cref="InvalidOperationException">
		/// Thrown when this method is called on an XRI, or on a directed identity
		/// request before the <see cref="ClaimedIdentifier"/> property is set.
		/// </exception>
		public void SetClaimedIdentifierFragment(string fragment) {
			UriBuilder builder = new UriBuilder(this.ClaimedIdentifier);
			builder.Fragment = fragment;
			this.positiveResponse.ClaimedIdentifier = builder.Uri;
		}

		#endregion

		/// <summary>
		/// Sets the Claimed and Local identifiers even after they have been initially set.
		/// </summary>
		/// <param name="identifier">The value to set to the <see cref="ClaimedIdentifier"/> and <see cref="LocalIdentifier"/> properties.</param>
		internal void ResetClaimedAndLocalIdentifiers(Identifier identifier) {
			Requires.NotNull(identifier, "identifier");

			this.positiveResponse.ClaimedIdentifier = identifier;
			this.positiveResponse.LocalIdentifier = identifier;
		}

		/// <summary>
		/// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>.
		/// </summary>
		protected override async Task<IProtocolMessage> GetResponseMessageAsync(CancellationToken cancellationToken) {
			if (this.IsAuthenticated.HasValue) {
				return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : (await this.GetNegativeResponseAsync());
			} else {
				return null;
			}
		}
	}
}