1
//-----------------------------------------------------------------------
2 // <copyright file="Channel.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.Messaging
{
9 using System
.Collections
.Generic
;
10 using System
.Collections
.ObjectModel
;
11 using System
.Diagnostics
;
12 using System
.Diagnostics
.CodeAnalysis
;
13 using System
.Globalization
;
19 using DotNetOpenAuth
.Messaging
.Reflection
;
22 /// Manages sending direct messages to a remote party and receiving responses.
24 public abstract class Channel
{
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.
31 private static int indirectMessageGetToPostThreshold
= 2 * 1024; // 2KB, recommended by OpenID group
34 /// The template for indirect messages that require form POST to forward through the user agent.
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.
41 private static string indirectMessageFormPostFormat
= @"
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;"">
46 <input id=""submit_button"" type=""submit"" value=""Continue"" />
53 /// A tool that can figure out what kind of message is being received
54 /// so it can be deserialized.
56 private IMessageFactory messageTypeProvider
;
59 /// A list of binding elements in the order they must be applied to outgoing messages.
61 [DebuggerBrowsable(DebuggerBrowsableState
.Never
)]
62 private List
<IChannelBindingElement
> outgoingBindingElements
= new List
<IChannelBindingElement
>();
65 /// A list of binding elements in the order they must be applied to incoming messages.
67 private List
<IChannelBindingElement
> incomingBindingElements
= new List
<IChannelBindingElement
>();
70 /// Initializes a new instance of the <see cref="Channel"/> class.
72 /// <param name="messageTypeProvider">
73 /// A class prepared to analyze incoming messages and indicate what concrete
74 /// message types can deserialize from it.
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;
92 /// An event fired whenever a message is about to be encoded and sent.
94 internal event EventHandler
<ChannelEventArgs
> Sending
;
97 /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when
98 /// submitting HTTP requests and waiting for responses.
101 /// This defaults to a straightforward implementation, but can be set
102 /// to a mock object for testing purposes.
104 public IDirectWebRequestHandler WebRequestHandler { get; set; }
107 /// Gets the binding elements used by this channel, in no particular guaranteed order.
109 protected internal ReadOnlyCollection
<IChannelBindingElement
> BindingElements
{
110 get { return this.outgoingBindingElements.AsReadOnly(); }
114 /// Gets the binding elements used by this channel, in the order applied to outgoing messages.
116 protected internal ReadOnlyCollection
<IChannelBindingElement
> OutgoingBindingElements
{
117 get { return this.outgoingBindingElements.AsReadOnly(); }
121 /// Gets the binding elements used by this channel, in the order applied to incoming messages.
123 protected internal ReadOnlyCollection
<IChannelBindingElement
> IncomingBindingElements
{
124 get { return this.incomingBindingElements.AsReadOnly(); }
128 /// Gets a tool that can figure out what kind of message is being received
129 /// so it can be deserialized.
131 protected IMessageFactory MessageFactory
{
132 get { return this.messageTypeProvider; }
136 /// Queues an indirect message (either a request or response)
137 /// or direct message response for transmission to a remote party.
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(
157 CultureInfo
.CurrentCulture
,
158 MessagingStrings
.IndirectMessagesMustImplementIDirectedProtocolMessage
,
159 typeof(IDirectedProtocolMessage
).FullName
),
162 if (directedMessage
.Recipient
== null) {
163 throw new ArgumentException(MessagingStrings
.DirectedMessageMissingRecipient
, "message");
165 return this.SendIndirectMessage(directedMessage
);
167 throw new ArgumentException(
169 CultureInfo
.CurrentCulture
,
170 MessagingStrings
.UnrecognizedEnumValue
,
178 /// Gets the protocol message embedded in the given HTTP request, if present.
180 /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
182 /// Requires an HttpContext.Current context.
184 /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
185 public IDirectedProtocolMessage
ReadFromRequest() {
186 return this.ReadFromRequest(this.GetRequestFromContext());
190 /// Gets the protocol message embedded in the given HTTP request, if present.
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>
196 /// Requires an HttpContext.Current context.
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
);
206 /// Gets the protocol message embedded in the given HTTP request, if present.
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) {
222 request
= untypedRequest
as TRequest
;
223 ErrorUtilities
.VerifyProtocol(request
!= null, MessagingStrings
.UnexpectedMessageReceived
, typeof(TRequest
), untypedRequest
.GetType());
229 /// Gets the protocol message embedded in the given HTTP request, if present.
231 /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
232 /// <returns>The deserialized message.</returns>
234 /// Requires an HttpContext.Current context.
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());
245 /// Gets the protocol message that may be embedded in the given HTTP request.
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
{
255 if (this.TryReadFromRequest
<TRequest
>(httpRequest
, out request
)) {
258 throw ErrorUtilities
.ThrowProtocol(MessagingStrings
.ExpectedMessageNotReceived
, typeof(TRequest
));
263 /// Gets the protocol message that may be embedded in the given HTTP request.
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
;
278 /// Sends a direct message to a remote party and waits for the response.
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.
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
;
300 /// Sends a direct message to a remote party and waits for the response.
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
;
320 /// Gets the current HTTP request being processed.
322 /// <returns>The HttpRequestInfo for the current request.</returns>
324 /// Requires an <see cref="HttpContext.Current"/> context.
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
);
335 /// Fires the <see cref="Sending"/> event.
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
));
348 /// Submits a direct request message to some remote party and blocks waiting for an immediately reply.
350 /// <param name="request">The request message.</param>
351 /// <returns>The response message, or null if the response did not carry a message.</returns>
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.
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) {
366 responseFields
= this.ReadFromResponseInternal(response
);
369 IDirectResponseProtocolMessage responseMessage
= this.MessageFactory
.GetNewResponseMessage(request
, responseFields
);
370 if (responseMessage
== null) {
374 var responseSerialize
= MessageSerializer
.Get(responseMessage
.GetType());
375 responseSerialize
.Deserialize(responseFields
, responseMessage
);
377 return responseMessage
;
381 /// Gets the protocol message that may be embedded in the given HTTP request.
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());
398 /// Deserializes a dictionary of values into a message.
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) {
413 // We have a message! Assemble it.
414 var serializer
= MessageSerializer
.Get(message
.GetType());
415 serializer
.Deserialize(fields
, message
);
421 /// Queues an indirect message for transmittal via the user agent.
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
);
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.
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
,
462 OriginalMessage
= message
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.
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
),
494 UserAgentResponse response
= new UserAgentResponse
{
495 Status
= HttpStatusCode
.OK
,
497 Body
= bodyWriter
.ToString(),
498 OriginalMessage
= message
505 /// Gets the protocol message that may be in the given HTTP response.
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
);
512 /// Prepares an HTTP request that carries a given message.
514 /// <param name="request">The message to send.</param>
515 /// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns>
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.
520 protected virtual HttpWebRequest
CreateHttpRequest(IDirectedProtocolMessage request
) {
521 throw new NotImplementedException();
525 /// Queues a message for sending in the response stream where the fields
526 /// are sent in the response stream in querystring style.
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>
531 /// This method implements spec V1.0 section 5.3.
533 protected abstract UserAgentResponse
SendDirectMessageResponse(IProtocolMessage response
);
536 /// Prepares a message for transmit by applying signatures, nonces, etc.
538 /// <param name="message">The message to prepare for sending.</param>
540 /// This method should NOT be called by derived types
541 /// except when sending ONE WAY request messages.
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
;
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();
573 /// Prepares to send a request to the Service Provider as the query string in a GET request.
575 /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
576 /// <returns>The web request ready to send.</returns>
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.
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
);
595 /// Prepares to send a request to the Service Provider as the payload of a POST request.
597 /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
598 /// <returns>The web request ready to send.</returns>
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.
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
);
623 /// Verifies the integrity and applicability of an incoming message.
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.
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
;
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();
661 /// Customizes the binding element order for outgoing and incoming messages.
663 /// <param name="outgoingOrder">The outgoing order.</param>
664 /// <param name="incomingOrder">The incoming order.</param>
666 /// No binding elements can be added or removed from the channel using this method.
667 /// Only a customized order is allowed.
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
);
684 /// Verifies that all required message parts are initialized to values
685 /// prior to sending the message to a remote party.
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.
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
);
700 /// Ensures a consistent and secure set of binding elements and
701 /// sorts them as necessary for a valid sequence of operations.
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
) {
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
));
743 /// Puts binding elements in their correct outgoing message processing order.
745 /// <param name="protection1">The first protection type to compare.</param>
746 /// <param name="protection2">The second protection type to compare.</param>
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.
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
760 /// Determines whether a given ordered list of binding elements includes every
761 /// binding element in this channel exactly once.
763 /// <param name="order">The list of binding elements to test.</param>
765 /// <c>true</c> if the given list is a valid description of a binding element ordering; otherwise, <c>false</c>.
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
) {
775 // Check that every binding element appears exactly once.
776 if (order
.Any(el
=> !this.OutgoingBindingElements
.Contains(el
))) {