Added StandardProviderApplicationStore and several OpenIdProvider unit tests.
[dotnetoauth.git] / src / DotNetOpenAuth / Messaging / Channel.cs
blob0bb76d72773c7b05d623f1e294b7a50e2a6bb589
1 //-----------------------------------------------------------------------
2 // <copyright file="Channel.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.Messaging {
8 using System;
9 using System.Collections.Generic;
10 using System.Collections.ObjectModel;
11 using System.Diagnostics;
12 using System.Diagnostics.CodeAnalysis;
13 using System.Globalization;
14 using System.IO;
15 using System.Linq;
16 using System.Net;
17 using System.Text;
18 using System.Web;
19 using DotNetOpenAuth.Messaging.Reflection;
21 /// <summary>
22 /// Manages sending direct messages to a remote party and receiving responses.
23 /// </summary>
24 public abstract class Channel {
25 /// <summary>
26 /// The maximum allowable size for a 301 Redirect response before we send
27 /// a 200 OK response with a scripted form POST with the parameters instead
28 /// in order to ensure successfully sending a large payload to another server
29 /// that might have a maximum allowable size restriction on its GET request.
30 /// </summary>
31 private static int indirectMessageGetToPostThreshold = 2 * 1024; // 2KB, recommended by OpenID group
33 /// <summary>
34 /// The template for indirect messages that require form POST to forward through the user agent.
35 /// </summary>
36 /// <remarks>
37 /// We are intentionally using " instead of the html single quote ' below because
38 /// the HtmlEncode'd values that we inject will only escape the double quote, so
39 /// only the double-quote used around these values is safe.
40 /// </remarks>
41 private static string indirectMessageFormPostFormat = @"
42 <html>
43 <body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()"">
44 <form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;"">
45 {1}
46 <input id=""submit_button"" type=""submit"" value=""Continue"" />
47 </form>
48 </body>
49 </html>
52 /// <summary>
53 /// A tool that can figure out what kind of message is being received
54 /// so it can be deserialized.
55 /// </summary>
56 private IMessageFactory messageTypeProvider;
58 /// <summary>
59 /// A list of binding elements in the order they must be applied to outgoing messages.
60 /// </summary>
61 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
62 private List<IChannelBindingElement> outgoingBindingElements = new List<IChannelBindingElement>();
64 /// <summary>
65 /// A list of binding elements in the order they must be applied to incoming messages.
66 /// </summary>
67 private List<IChannelBindingElement> incomingBindingElements = new List<IChannelBindingElement>();
69 /// <summary>
70 /// Initializes a new instance of the <see cref="Channel"/> class.
71 /// </summary>
72 /// <param name="messageTypeProvider">
73 /// A class prepared to analyze incoming messages and indicate what concrete
74 /// message types can deserialize from it.
75 /// </param>
76 /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param>
77 protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) {
78 ErrorUtilities.VerifyArgumentNotNull(messageTypeProvider, "messageTypeProvider");
80 this.messageTypeProvider = messageTypeProvider;
81 this.WebRequestHandler = new StandardWebRequestHandler();
82 this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements));
83 this.incomingBindingElements = new List<IChannelBindingElement>(this.outgoingBindingElements);
84 this.incomingBindingElements.Reverse();
86 foreach (var element in this.outgoingBindingElements) {
87 element.Channel = this;
91 /// <summary>
92 /// An event fired whenever a message is about to be encoded and sent.
93 /// </summary>
94 internal event EventHandler<ChannelEventArgs> Sending;
96 /// <summary>
97 /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when
98 /// submitting HTTP requests and waiting for responses.
99 /// </summary>
100 /// <remarks>
101 /// This defaults to a straightforward implementation, but can be set
102 /// to a mock object for testing purposes.
103 /// </remarks>
104 public IDirectWebRequestHandler WebRequestHandler { get; set; }
106 /// <summary>
107 /// Gets the binding elements used by this channel, in no particular guaranteed order.
108 /// </summary>
109 protected internal ReadOnlyCollection<IChannelBindingElement> BindingElements {
110 get { return this.outgoingBindingElements.AsReadOnly(); }
113 /// <summary>
114 /// Gets the binding elements used by this channel, in the order applied to outgoing messages.
115 /// </summary>
116 protected internal ReadOnlyCollection<IChannelBindingElement> OutgoingBindingElements {
117 get { return this.outgoingBindingElements.AsReadOnly(); }
120 /// <summary>
121 /// Gets the binding elements used by this channel, in the order applied to incoming messages.
122 /// </summary>
123 protected internal ReadOnlyCollection<IChannelBindingElement> IncomingBindingElements {
124 get { return this.incomingBindingElements.AsReadOnly(); }
127 /// <summary>
128 /// Gets a tool that can figure out what kind of message is being received
129 /// so it can be deserialized.
130 /// </summary>
131 protected IMessageFactory MessageFactory {
132 get { return this.messageTypeProvider; }
135 /// <summary>
136 /// Queues an indirect message (either a request or response)
137 /// or direct message response for transmission to a remote party.
138 /// </summary>
139 /// <param name="message">The one-way message to send</param>
140 /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
141 public UserAgentResponse Send(IProtocolMessage message) {
142 if (message == null) {
143 throw new ArgumentNullException("message");
145 this.PrepareMessageForSending(message);
146 Logger.DebugFormat("Sending message: {0}", message);
148 switch (message.Transport) {
149 case MessageTransport.Direct:
150 // This is a response to a direct message.
151 return this.SendDirectMessageResponse(message);
152 case MessageTransport.Indirect:
153 var directedMessage = message as IDirectedProtocolMessage;
154 if (directedMessage == null) {
155 throw new ArgumentException(
156 string.Format(
157 CultureInfo.CurrentCulture,
158 MessagingStrings.IndirectMessagesMustImplementIDirectedProtocolMessage,
159 typeof(IDirectedProtocolMessage).FullName),
160 "message");
162 if (directedMessage.Recipient == null) {
163 throw new ArgumentException(MessagingStrings.DirectedMessageMissingRecipient, "message");
165 return this.SendIndirectMessage(directedMessage);
166 default:
167 throw new ArgumentException(
168 string.Format(
169 CultureInfo.CurrentCulture,
170 MessagingStrings.UnrecognizedEnumValue,
171 "Transport",
172 message.Transport),
173 "message");
177 /// <summary>
178 /// Gets the protocol message embedded in the given HTTP request, if present.
179 /// </summary>
180 /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
181 /// <remarks>
182 /// Requires an HttpContext.Current context.
183 /// </remarks>
184 /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
185 public IDirectedProtocolMessage ReadFromRequest() {
186 return this.ReadFromRequest(this.GetRequestFromContext());
189 /// <summary>
190 /// Gets the protocol message embedded in the given HTTP request, if present.
191 /// </summary>
192 /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
193 /// <param name="request">The deserialized message, if one is found. Null otherwise.</param>
194 /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns>
195 /// <remarks>
196 /// Requires an HttpContext.Current context.
197 /// </remarks>
198 /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
199 /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception>
200 public bool TryReadFromRequest<TRequest>(out TRequest request)
201 where TRequest : class, IProtocolMessage {
202 return TryReadFromRequest<TRequest>(this.GetRequestFromContext(), out request);
205 /// <summary>
206 /// Gets the protocol message embedded in the given HTTP request, if present.
207 /// </summary>
208 /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
209 /// <param name="httpRequest">The request to search for an embedded message.</param>
210 /// <param name="request">The deserialized message, if one is found. Null otherwise.</param>
211 /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns>
212 /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
213 /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception>
214 public bool TryReadFromRequest<TRequest>(HttpRequestInfo httpRequest, out TRequest request)
215 where TRequest : class, IProtocolMessage {
216 IProtocolMessage untypedRequest = this.ReadFromRequest(httpRequest);
217 if (untypedRequest == null) {
218 request = null;
219 return false;
222 request = untypedRequest as TRequest;
223 ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.UnexpectedMessageReceived, typeof(TRequest), untypedRequest.GetType());
225 return true;
228 /// <summary>
229 /// Gets the protocol message embedded in the given HTTP request, if present.
230 /// </summary>
231 /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
232 /// <returns>The deserialized message.</returns>
233 /// <remarks>
234 /// Requires an HttpContext.Current context.
235 /// </remarks>
236 /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
237 /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception>
238 [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")]
239 public TRequest ReadFromRequest<TRequest>()
240 where TRequest : class, IProtocolMessage {
241 return this.ReadFromRequest<TRequest>(this.GetRequestFromContext());
244 /// <summary>
245 /// Gets the protocol message that may be embedded in the given HTTP request.
246 /// </summary>
247 /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
248 /// <param name="httpRequest">The request to search for an embedded message.</param>
249 /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
250 /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception>
251 [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")]
252 public TRequest ReadFromRequest<TRequest>(HttpRequestInfo httpRequest)
253 where TRequest : class, IProtocolMessage {
254 TRequest request;
255 if (this.TryReadFromRequest<TRequest>(httpRequest, out request)) {
256 return request;
257 } else {
258 throw ErrorUtilities.ThrowProtocol(MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest));
262 /// <summary>
263 /// Gets the protocol message that may be embedded in the given HTTP request.
264 /// </summary>
265 /// <param name="httpRequest">The request to search for an embedded message.</param>
266 /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
267 public IDirectedProtocolMessage ReadFromRequest(HttpRequestInfo httpRequest) {
268 IDirectedProtocolMessage requestMessage = this.ReadFromRequestInternal(httpRequest);
269 if (requestMessage != null) {
270 Logger.DebugFormat("Incoming request received: {0}", requestMessage);
271 this.VerifyMessageAfterReceiving(requestMessage);
274 return requestMessage;
277 /// <summary>
278 /// Sends a direct message to a remote party and waits for the response.
279 /// </summary>
280 /// <typeparam name="TResponse">The expected type of the message to be received.</typeparam>
281 /// <param name="requestMessage">The message to send.</param>
282 /// <returns>The remote party's response.</returns>
283 /// <exception cref="ProtocolException">
284 /// Thrown if no message is recognized in the response
285 /// or an unexpected type of message is received.
286 /// </exception>
287 [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")]
288 public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage)
289 where TResponse : class, IProtocolMessage {
290 IProtocolMessage response = this.Request(requestMessage);
291 ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse));
293 var expectedResponse = response as TResponse;
294 ErrorUtilities.VerifyProtocol(expectedResponse != null, MessagingStrings.UnexpectedMessageReceived, typeof(TResponse), response.GetType());
296 return expectedResponse;
299 /// <summary>
300 /// Sends a direct message to a remote party and waits for the response.
301 /// </summary>
302 /// <param name="requestMessage">The message to send.</param>
303 /// <returns>The remote party's response. Guaranteed to never be null.</returns>
304 /// <exception cref="ProtocolException">Thrown if the response does not include a protocol message.</exception>
305 public IProtocolMessage Request(IDirectedProtocolMessage requestMessage) {
306 ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage");
308 this.PrepareMessageForSending(requestMessage);
309 Logger.DebugFormat("Sending request: {0}", requestMessage);
310 var responseMessage = this.RequestInternal(requestMessage);
311 ErrorUtilities.VerifyProtocol(responseMessage != null, MessagingStrings.ExpectedMessageNotReceived, typeof(IProtocolMessage).Name);
313 Logger.DebugFormat("Received message response: {0}", responseMessage);
314 this.VerifyMessageAfterReceiving(responseMessage);
316 return responseMessage;
319 /// <summary>
320 /// Gets the current HTTP request being processed.
321 /// </summary>
322 /// <returns>The HttpRequestInfo for the current request.</returns>
323 /// <remarks>
324 /// Requires an <see cref="HttpContext.Current"/> context.
325 /// </remarks>
326 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
327 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")]
328 protected internal virtual HttpRequestInfo GetRequestFromContext() {
329 ErrorUtilities.VerifyHttpContext();
331 return new HttpRequestInfo(HttpContext.Current.Request);
334 /// <summary>
335 /// Fires the <see cref="Sending"/> event.
336 /// </summary>
337 /// <param name="message">The message about to be encoded and sent.</param>
338 protected virtual void OnSending(IProtocolMessage message) {
339 ErrorUtilities.VerifyArgumentNotNull(message, "message");
341 var sending = this.Sending;
342 if (sending != null) {
343 sending(this, new ChannelEventArgs(message));
347 /// <summary>
348 /// Submits a direct request message to some remote party and blocks waiting for an immediately reply.
349 /// </summary>
350 /// <param name="request">The request message.</param>
351 /// <returns>The response message, or null if the response did not carry a message.</returns>
352 /// <remarks>
353 /// Typically a deriving channel will override <see cref="CreateHttpRequest"/> to customize this method's
354 /// behavior. However in non-HTTP frameworks, such as unit test mocks, it may be appropriate to override
355 /// this method to eliminate all use of an HTTP transport.
356 /// </remarks>
357 protected virtual IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
358 HttpWebRequest webRequest = this.CreateHttpRequest(request);
359 IDictionary<string, string> responseFields;
361 using (DirectWebResponse response = this.WebRequestHandler.GetResponse(webRequest)) {
362 if (response.ResponseStream == null) {
363 return null;
366 responseFields = this.ReadFromResponseInternal(response);
369 IDirectResponseProtocolMessage responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields);
370 if (responseMessage == null) {
371 return null;
374 var responseSerialize = MessageSerializer.Get(responseMessage.GetType());
375 responseSerialize.Deserialize(responseFields, responseMessage);
377 return responseMessage;
380 /// <summary>
381 /// Gets the protocol message that may be embedded in the given HTTP request.
382 /// </summary>
383 /// <param name="request">The request to search for an embedded message.</param>
384 /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
385 protected virtual IDirectedProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) {
386 ErrorUtilities.VerifyArgumentNotNull(request, "request");
388 // Search Form data first, and if nothing is there search the QueryString
389 var fields = request.Form.ToDictionary();
390 if (fields.Count == 0) {
391 fields = request.QueryString.ToDictionary();
394 return (IDirectedProtocolMessage)this.Receive(fields, request.GetRecipient());
397 /// <summary>
398 /// Deserializes a dictionary of values into a message.
399 /// </summary>
400 /// <param name="fields">The dictionary of values that were read from an HTTP request or response.</param>
401 /// <param name="recipient">Information about where the message was been directed. Null for direct response messages.</param>
402 /// <returns>The deserialized message, or null if no message could be recognized in the provided data.</returns>
403 protected virtual IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) {
404 ErrorUtilities.VerifyArgumentNotNull(fields, "fields");
406 IProtocolMessage message = this.MessageFactory.GetNewRequestMessage(recipient, fields);
408 // If there was no data, or we couldn't recognize it as a message, abort.
409 if (message == null) {
410 return null;
413 // We have a message! Assemble it.
414 var serializer = MessageSerializer.Get(message.GetType());
415 serializer.Deserialize(fields, message);
417 return message;
420 /// <summary>
421 /// Queues an indirect message for transmittal via the user agent.
422 /// </summary>
423 /// <param name="message">The message to send.</param>
424 /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
425 protected virtual UserAgentResponse SendIndirectMessage(IDirectedProtocolMessage message) {
426 ErrorUtilities.VerifyArgumentNotNull(message, "message");
428 var serializer = MessageSerializer.Get(message.GetType());
429 var fields = serializer.Serialize(message);
431 // First try creating a 301 redirect, and fallback to a form POST
432 // if the message is too big.
433 UserAgentResponse response = this.Create301RedirectResponse(message, fields);
434 if (response.Headers[HttpResponseHeader.Location].Length > indirectMessageGetToPostThreshold) {
435 response = this.CreateFormPostResponse(message, fields);
438 return response;
441 /// <summary>
442 /// Encodes an HTTP response that will instruct the user agent to forward a message to
443 /// some remote third party using a 301 Redirect GET method.
444 /// </summary>
445 /// <param name="message">The message to forward.</param>
446 /// <param name="fields">The pre-serialized fields from the message.</param>
447 /// <returns>The encoded HTTP response.</returns>
448 protected virtual UserAgentResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
449 ErrorUtilities.VerifyArgumentNotNull(message, "message");
450 ErrorUtilities.VerifyArgumentNamed(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
451 ErrorUtilities.VerifyArgumentNotNull(fields, "fields");
453 WebHeaderCollection headers = new WebHeaderCollection();
454 UriBuilder builder = new UriBuilder(message.Recipient);
455 MessagingUtilities.AppendQueryArgs(builder, fields);
456 headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
457 Logger.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri);
458 UserAgentResponse response = new UserAgentResponse {
459 Status = HttpStatusCode.Redirect,
460 Headers = headers,
461 Body = null,
462 OriginalMessage = message
465 return response;
468 /// <summary>
469 /// Encodes an HTTP response that will instruct the user agent to forward a message to
470 /// some remote third party using a form POST method.
471 /// </summary>
472 /// <param name="message">The message to forward.</param>
473 /// <param name="fields">The pre-serialized fields from the message.</param>
474 /// <returns>The encoded HTTP response.</returns>
475 protected virtual UserAgentResponse CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
476 ErrorUtilities.VerifyArgumentNotNull(message, "message");
477 ErrorUtilities.VerifyArgumentNamed(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
478 ErrorUtilities.VerifyArgumentNotNull(fields, "fields");
480 WebHeaderCollection headers = new WebHeaderCollection();
481 StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture);
482 StringBuilder hiddenFields = new StringBuilder();
483 foreach (var field in fields) {
484 hiddenFields.AppendFormat(
485 "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
486 HttpUtility.HtmlEncode(field.Key),
487 HttpUtility.HtmlEncode(field.Value));
489 bodyWriter.WriteLine(
490 indirectMessageFormPostFormat,
491 HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
492 hiddenFields);
493 bodyWriter.Flush();
494 UserAgentResponse response = new UserAgentResponse {
495 Status = HttpStatusCode.OK,
496 Headers = headers,
497 Body = bodyWriter.ToString(),
498 OriginalMessage = message
501 return response;
504 /// <summary>
505 /// Gets the protocol message that may be in the given HTTP response.
506 /// </summary>
507 /// <param name="response">The response that is anticipated to contain an protocol message.</param>
508 /// <returns>The deserialized message parts, if found. Null otherwise.</returns>
509 protected abstract IDictionary<string, string> ReadFromResponseInternal(DirectWebResponse response);
511 /// <summary>
512 /// Prepares an HTTP request that carries a given message.
513 /// </summary>
514 /// <param name="request">The message to send.</param>
515 /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns>
516 /// <remarks>
517 /// This method must be overridden by a derived class, unless the <see cref="RequestInternal"/> method
518 /// is overridden and does not require this method.
519 /// </remarks>
520 protected virtual HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) {
521 throw new NotImplementedException();
524 /// <summary>
525 /// Queues a message for sending in the response stream where the fields
526 /// are sent in the response stream in querystring style.
527 /// </summary>
528 /// <param name="response">The message to send as a response.</param>
529 /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
530 /// <remarks>
531 /// This method implements spec V1.0 section 5.3.
532 /// </remarks>
533 protected abstract UserAgentResponse SendDirectMessageResponse(IProtocolMessage response);
535 /// <summary>
536 /// Prepares a message for transmit by applying signatures, nonces, etc.
537 /// </summary>
538 /// <param name="message">The message to prepare for sending.</param>
539 /// <remarks>
540 /// This method should NOT be called by derived types
541 /// except when sending ONE WAY request messages.
542 /// </remarks>
543 protected void PrepareMessageForSending(IProtocolMessage message) {
544 ErrorUtilities.VerifyArgumentNotNull(message, "message");
546 Logger.DebugFormat("Preparing to send {0} ({1}) message.", message.GetType().Name, message.Version);
547 this.OnSending(message);
549 MessageProtections appliedProtection = MessageProtections.None;
550 foreach (IChannelBindingElement bindingElement in this.outgoingBindingElements) {
551 if (bindingElement.PrepareMessageForSending(message)) {
552 Logger.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName);
554 // Ensure that only one protection binding element applies to this message
555 // for each protection type.
556 ErrorUtilities.VerifyProtocol((appliedProtection & bindingElement.Protection) == 0, MessagingStrings.TooManyBindingsOfferingSameProtection, bindingElement.Protection);
557 appliedProtection |= bindingElement.Protection;
558 } else {
559 Logger.DebugFormat("Binding element {0} did not apply to message.", bindingElement.GetType().FullName);
563 // Ensure that the message's protection requirements have been satisfied.
564 if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) {
565 throw new UnprotectedMessageException(message, appliedProtection);
568 EnsureValidMessageParts(message);
569 message.EnsureValidMessage();
572 /// <summary>
573 /// Prepares to send a request to the Service Provider as the query string in a GET request.
574 /// </summary>
575 /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
576 /// <returns>The web request ready to send.</returns>
577 /// <remarks>
578 /// This method is simply a standard HTTP Get request with the message parts serialized to the query string.
579 /// This method satisfies OAuth 1.0 section 5.2, item #3.
580 /// </remarks>
581 protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
582 ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage");
584 var serializer = MessageSerializer.Get(requestMessage.GetType());
585 var fields = serializer.Serialize(requestMessage);
587 UriBuilder builder = new UriBuilder(requestMessage.Recipient);
588 MessagingUtilities.AppendQueryArgs(builder, fields);
589 HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
591 return httpRequest;
594 /// <summary>
595 /// Prepares to send a request to the Service Provider as the payload of a POST request.
596 /// </summary>
597 /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
598 /// <returns>The web request ready to send.</returns>
599 /// <remarks>
600 /// This method is simply a standard HTTP POST request with the message parts serialized to the POST entity
601 /// with the application/x-www-form-urlencoded content type
602 /// This method satisfies OAuth 1.0 section 5.2, item #2 and OpenID 2.0 section 4.1.2.
603 /// </remarks>
604 protected virtual HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
605 ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage");
607 var serializer = MessageSerializer.Get(requestMessage.GetType());
608 var fields = serializer.Serialize(requestMessage);
610 HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
611 httpRequest.Method = "POST";
612 httpRequest.ContentType = "application/x-www-form-urlencoded";
613 string requestBody = MessagingUtilities.CreateQueryString(fields);
614 httpRequest.ContentLength = requestBody.Length;
615 using (TextWriter writer = this.WebRequestHandler.GetRequestStream(httpRequest)) {
616 writer.Write(requestBody);
619 return httpRequest;
622 /// <summary>
623 /// Verifies the integrity and applicability of an incoming message.
624 /// </summary>
625 /// <param name="message">The message just received.</param>
626 /// <exception cref="ProtocolException">
627 /// Thrown when the message is somehow invalid.
628 /// This can be due to tampering, replay attack or expiration, among other things.
629 /// </exception>
630 protected virtual void VerifyMessageAfterReceiving(IProtocolMessage message) {
631 Debug.Assert(message != null, "message == null");
633 Logger.DebugFormat("Preparing to receive {0} ({1}) message.", message.GetType().Name, message.Version);
635 MessageProtections appliedProtection = MessageProtections.None;
636 foreach (IChannelBindingElement bindingElement in this.incomingBindingElements) {
637 if (bindingElement.PrepareMessageForReceiving(message)) {
638 Logger.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName);
640 // Ensure that only one protection binding element applies to this message
641 // for each protection type.
642 ErrorUtilities.VerifyInternal((appliedProtection & bindingElement.Protection) == 0, MessagingStrings.TooManyBindingsOfferingSameProtection, bindingElement.Protection);
643 appliedProtection |= bindingElement.Protection;
644 } else {
645 Logger.DebugFormat("Binding element {0} did not apply to message.", bindingElement.GetType().FullName);
649 // Ensure that the message's protection requirements have been satisfied.
650 if ((message.RequiredProtection & appliedProtection) != message.RequiredProtection) {
651 throw new UnprotectedMessageException(message, appliedProtection);
654 // We do NOT verify that all required message parts are present here... the
655 // message deserializer did for us. It would be too late to do it here since
656 // they might look initialized by the time we have an IProtocolMessage instance.
657 message.EnsureValidMessage();
660 /// <summary>
661 /// Customizes the binding element order for outgoing and incoming messages.
662 /// </summary>
663 /// <param name="outgoingOrder">The outgoing order.</param>
664 /// <param name="incomingOrder">The incoming order.</param>
665 /// <remarks>
666 /// No binding elements can be added or removed from the channel using this method.
667 /// Only a customized order is allowed.
668 /// </remarks>
669 /// <exception cref="ArgumentException">Thrown if a binding element is new or missing in one of the ordered lists.</exception>
670 protected void CustomizeBindingElementOrder(IEnumerable<IChannelBindingElement> outgoingOrder, IEnumerable<IChannelBindingElement> incomingOrder) {
671 ErrorUtilities.VerifyArgumentNotNull(outgoingOrder, "outgoingOrder");
672 ErrorUtilities.VerifyArgumentNotNull(incomingOrder, "incomingOrder");
674 ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(outgoingOrder), MessagingStrings.InvalidCustomBindingElementOrder);
675 ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(incomingOrder), MessagingStrings.InvalidCustomBindingElementOrder);
677 this.outgoingBindingElements.Clear();
678 this.outgoingBindingElements.AddRange(outgoingOrder);
679 this.incomingBindingElements.Clear();
680 this.incomingBindingElements.AddRange(incomingOrder);
683 /// <summary>
684 /// Verifies that all required message parts are initialized to values
685 /// prior to sending the message to a remote party.
686 /// </summary>
687 /// <param name="message">The message to verify.</param>
688 /// <exception cref="ProtocolException">
689 /// Thrown when any required message part does not have a value.
690 /// </exception>
691 private static void EnsureValidMessageParts(IProtocolMessage message) {
692 Debug.Assert(message != null, "message == null");
694 MessageDictionary dictionary = new MessageDictionary(message);
695 MessageDescription description = MessageDescription.Get(message.GetType(), message.Version);
696 description.EnsureMessagePartsPassBasicValidation(dictionary);
699 /// <summary>
700 /// Ensures a consistent and secure set of binding elements and
701 /// sorts them as necessary for a valid sequence of operations.
702 /// </summary>
703 /// <param name="elements">The binding elements provided to the channel.</param>
704 /// <returns>The properly ordered list of elements.</returns>
705 /// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception>
706 private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) {
707 if (elements == null) {
708 return new IChannelBindingElement[0];
711 ErrorUtilities.VerifyArgumentNamed(!elements.Contains(null), "elements", MessagingStrings.SequenceContainsNullElement);
713 // Filter the elements between the mere transforming ones and the protection ones.
714 var transformationElements = new List<IChannelBindingElement>(
715 elements.Where(element => element.Protection == MessageProtections.None));
716 var protectionElements = new List<IChannelBindingElement>(
717 elements.Where(element => element.Protection != MessageProtections.None));
719 bool wasLastProtectionPresent = true;
720 foreach (MessageProtections protectionKind in Enum.GetValues(typeof(MessageProtections))) {
721 if (protectionKind == MessageProtections.None) {
722 continue;
725 int countProtectionsOfThisKind = protectionElements.Count(element => (element.Protection & protectionKind) == protectionKind);
727 // Each protection binding element is backed by the presence of its dependent protection(s).
728 ErrorUtilities.VerifyProtocol(!(countProtectionsOfThisKind > 0 && !wasLastProtectionPresent), MessagingStrings.RequiredProtectionMissing, protectionKind);
730 wasLastProtectionPresent = countProtectionsOfThisKind > 0;
733 // Put the binding elements in order so they are correctly applied to outgoing messages.
734 // Start with the transforming (non-protecting) binding elements first and preserve their original order.
735 var orderedList = new List<IChannelBindingElement>(transformationElements);
737 // Now sort the protection binding elements among themselves and add them to the list.
738 orderedList.AddRange(protectionElements.OrderBy(element => element.Protection, BindingElementOutgoingMessageApplicationOrder));
739 return orderedList;
742 /// <summary>
743 /// Puts binding elements in their correct outgoing message processing order.
744 /// </summary>
745 /// <param name="protection1">The first protection type to compare.</param>
746 /// <param name="protection2">The second protection type to compare.</param>
747 /// <returns>
748 /// -1 if <paramref name="element1"/> should be applied to an outgoing message before <paramref name="element2"/>.
749 /// 1 if <paramref name="element2"/> should be applied to an outgoing message before <paramref name="element1"/>.
750 /// 0 if it doesn't matter.
751 /// </returns>
752 private static int BindingElementOutgoingMessageApplicationOrder(MessageProtections protection1, MessageProtections protection2) {
753 ErrorUtilities.VerifyInternal(protection1 != MessageProtections.None || protection2 != MessageProtections.None, "This comparison function should only be used to compare protection binding elements. Otherwise we change the order of user-defined message transformations.");
755 // Now put the protection ones in the right order.
756 return -((int)protection1).CompareTo((int)protection2); // descending flag ordinal order
759 /// <summary>
760 /// Determines whether a given ordered list of binding elements includes every
761 /// binding element in this channel exactly once.
762 /// </summary>
763 /// <param name="order">The list of binding elements to test.</param>
764 /// <returns>
765 /// <c>true</c> if the given list is a valid description of a binding element ordering; otherwise, <c>false</c>.
766 /// </returns>
767 private bool IsBindingElementOrderValid(IEnumerable<IChannelBindingElement> order) {
768 ErrorUtilities.VerifyArgumentNotNull(order, "order");
770 // Check that the same number of binding elements are defined.
771 if (order.Count() != this.OutgoingBindingElements.Count) {
772 return false;
775 // Check that every binding element appears exactly once.
776 if (order.Any(el => !this.OutgoingBindingElements.Contains(el))) {
777 return false;
780 return true;