diff options
Diffstat (limited to 'src/DotNetOpenAuth.OAuth/OAuth/ChannelElements')
5 files changed, 80 insertions, 70 deletions
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs index 9cb0a44..679b1dc 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs @@ -7,6 +7,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Net.Http; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; @@ -37,7 +39,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <summary> /// Gets or sets the HTTP method that will be used to transmit the message. /// </summary> - string HttpMethod { get; set; } + HttpMethod HttpMethod { get; set; } /// <summary> /// Gets or sets the URL of the intended receiver of this message. diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs index 1362ca9..f483ade 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -13,8 +13,12 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System.IO; using System.Linq; using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; using System.Net.Mime; using System.Text; + using System.Threading; + using System.Threading.Tasks; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; @@ -22,12 +26,14 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using DotNetOpenAuth.OAuth.Messages; using Validation; + using HttpRequestHeaders = DotNetOpenAuth.Messaging.HttpRequestHeaders; + /// <summary> /// An OAuth-specific implementation of the <see cref="Channel"/> class. /// </summary> internal abstract class OAuthChannel : Channel { /// <summary> - /// Initializes a new instance of the <see cref="OAuthChannel"/> class. + /// Initializes a new instance of the <see cref="OAuthChannel" /> class. /// </summary> /// <param name="signingBindingElement">The binding element to use for signing.</param> /// <param name="tokenManager">The ITokenManager instance to use.</param> @@ -36,9 +42,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Except for mock testing, this should always be one of /// OAuthConsumerMessageFactory or OAuthServiceProviderMessageFactory.</param> /// <param name="bindingElements">The binding elements.</param> + /// <param name="hostFactories">The host factories.</param> [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] - protected OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, ITokenManager tokenManager, SecuritySettings securitySettings, IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements) - : base(messageTypeProvider, bindingElements) { + protected OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, ITokenManager tokenManager, SecuritySettings securitySettings, IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements, IHostFactories hostFactories = null) + : base(messageTypeProvider, bindingElements, hostFactories ?? new DefaultOAuthHostFactories()) { Requires.NotNull(tokenManager, "tokenManager"); Requires.NotNull(securitySettings, "securitySettings"); Requires.NotNull(signingBindingElement, "signingBindingElement"); @@ -76,11 +83,12 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// expect an OAuth message response to. /// </summary> /// <param name="request">The message to attach.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The initialized web request.</returns> - internal HttpWebRequest InitializeRequest(IDirectedProtocolMessage request) { + internal async Task<HttpRequestMessage> InitializeRequestAsync(IDirectedProtocolMessage request, CancellationToken cancellationToken) { Requires.NotNull(request, "request"); - ProcessOutgoingMessage(request); + await this.ProcessOutgoingMessageAsync(request, cancellationToken); return this.CreateHttpRequest(request); } @@ -108,29 +116,25 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// a protocol request message. /// </summary> /// <param name="request">The HTTP request to search.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request) { + protected override async Task<IDirectedProtocolMessage> ReadFromRequestCoreAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // First search the Authorization header. - string authorization = request.Headers[HttpRequestHeaders.Authorization]; + var authorization = request.Headers.Authorization; var fields = MessagingUtilities.ParseAuthorizationHeader(Protocol.AuthorizationHeaderScheme, authorization).ToDictionary(); fields.Remove("realm"); // ignore the realm parameter, since we don't use it, and it must be omitted from signature base string. // Scrape the entity - if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeaders.ContentType])) { - var contentType = new ContentType(request.Headers[HttpRequestHeaders.ContentType]); - if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) { - foreach (string key in request.Form) { - if (key != null) { - fields.Add(key, request.Form[key]); - } else { - Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.Form[key]); - } - } + foreach (var pair in await ParseUrlEncodedFormContentAsync(request, cancellationToken)) { + if (pair.Key != null) { + fields.Add(pair.Key, pair.Value); + } else { + Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", pair.Value); } } // Scrape the query string - var qs = request.GetQueryStringBeforeRewriting(); + var qs = HttpUtility.ParseQueryString(request.RequestUri.Query); foreach (string key in qs) { if (key != null) { fields.Add(key, qs[key]); @@ -153,8 +157,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { // Add receiving HTTP transport information required for signature generation. var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null) { - signedMessage.Recipient = request.GetPublicFacingUrl(); - signedMessage.HttpMethod = request.HttpMethod; + signedMessage.Recipient = request.RequestUri; + signedMessage.HttpMethod = request.Method; } return message; @@ -167,8 +171,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <returns> /// The deserialized message parts, if found. Null otherwise. /// </returns> - protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - string body = response.GetResponseReader().ReadToEnd(); + protected override async Task<IDictionary<string, string>> ReadFromResponseCoreAsync(HttpResponseMessage response, CancellationToken cancellationToken) { + string body = await response.Content.ReadAsStringAsync(); return HttpUtility.ParseQueryString(body).ToDictionary(); } @@ -179,8 +183,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <returns> /// The <see cref="HttpRequest"/> prepared to send the request. /// </returns> - protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - HttpWebRequest httpRequest; + protected override HttpRequestMessage CreateHttpRequest(IDirectedProtocolMessage request) { + HttpRequestMessage httpRequest; HttpDeliveryMethods transmissionMethod = request.HttpMethods; if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { @@ -200,6 +204,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { } else { throw new NotSupportedException(); } + return httpRequest; } @@ -212,16 +217,12 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <remarks> /// This method implements spec V1.0 section 5.3. /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + protected override HttpResponseMessage PrepareDirectResponse(IProtocolMessage response) { var messageAccessor = this.MessageDescriptions.GetAccessor(response); var fields = messageAccessor.Serialize(); - string responseBody = MessagingUtilities.CreateQueryString(fields); - OutgoingWebResponse encodedResponse = new OutgoingWebResponse { - Body = responseBody, - OriginalMessage = response, - Status = HttpStatusCode.OK, - Headers = new System.Net.WebHeaderCollection(), + var encodedResponse = new HttpResponseMessage { + Content = new FormUrlEncodedContent(fields), }; ApplyMessageTemplate(response, encodedResponse); @@ -256,7 +257,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <param name="message">The message.</param> /// <returns>"POST", "GET" or some other similar http verb.</returns> - private static string GetHttpMethod(IDirectedProtocolMessage message) { + private static HttpMethod GetHttpMethod(IDirectedProtocolMessage message) { Requires.NotNull(message, "message"); var signedMessage = message as ITamperResistantOAuthMessage; @@ -273,11 +274,9 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> /// <returns>The web request ready to send.</returns> /// <remarks> - /// <para>If the message has non-empty ExtraData in it, the request stream is sent to - /// the server automatically. If it is empty, the request stream must be sent by the caller.</para> - /// <para>This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).</para> + /// This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4). /// </remarks> - private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) { + private HttpRequestMessage InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) { var dictionary = this.MessageDescriptions.GetAccessor(requestMessage); // copy so as to not modify original @@ -285,41 +284,38 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { foreach (string key in dictionary.DeclaredKeys) { fields.Add(key, dictionary[key]); } + if (this.Realm != null) { fields.Add("realm", this.Realm.AbsoluteUri); } - HttpWebRequest httpRequest; UriBuilder recipientBuilder = new UriBuilder(requestMessage.Recipient); bool hasEntity = HttpMethodHasEntity(GetHttpMethod(requestMessage)); if (!hasEntity) { MessagingUtilities.AppendQueryArgs(recipientBuilder, requestMessage.ExtraData); } - httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri); + + var httpRequest = new HttpRequestMessage(GetHttpMethod(requestMessage), recipientBuilder.Uri); this.PrepareHttpWebRequest(httpRequest); - httpRequest.Method = GetHttpMethod(requestMessage); - httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields)); + httpRequest.Headers.Authorization = new AuthenticationHeaderValue(Protocol.AuthorizationHeaderScheme, MessagingUtilities.AssembleAuthorizationHeader(fields)); if (hasEntity) { - // WARNING: We only set up the request stream for the caller if there is - // extra data. If there isn't any extra data, the caller must do this themselves. var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData; if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) { // Include the binary data in the multipart entity, and any standard text extra message data. // The standard declared message parts are included in the authorization header. - var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData); - multiPartFields.AddRange(requestMessage.ExtraData.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value))); - this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields); + var content = InitializeMultipartFormDataContent(requestMessageWithBinaryData); + httpRequest.Content = content; + + foreach (var extraData in requestMessage.ExtraData) { + content.Add(new StringContent(extraData.Value), extraData.Key); + } } else { ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart); if (requestMessage.ExtraData.Count > 0) { - this.SendParametersInEntity(httpRequest, requestMessage.ExtraData); - } else { - // We'll assume the content length is zero since the caller may not have - // anything. They're responsible to change it when the add the payload if they have one. - httpRequest.ContentLength = 0; + httpRequest.Content = new FormUrlEncodedContent(requestMessage.ExtraData); } } } diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs index 98eb9b3..29ef8d5 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs @@ -9,6 +9,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System.Collections.Generic; using System.Linq; using System.Text; + using System.Threading; + using System.Threading.Tasks; using DotNetOpenAuth.Messaging; /// <summary> @@ -33,24 +35,25 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Prepares a message for sending based on the rules of this channel binding element. /// </summary> /// <param name="message">The message to prepare for sending.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// True if the <paramref name="message"/> applied to this binding element /// and the operation was successful. False otherwise. /// </returns> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var oauthMessage = message as ITamperResistantOAuthMessage; if (oauthMessage != null) { HttpDeliveryMethods transmissionMethod = oauthMessage.HttpMethods; try { oauthMessage.HttpMethod = MessagingUtilities.GetHttpVerb(transmissionMethod); - return MessageProtections.None; + return MessageProtectionTasks.None; } catch (ArgumentException ex) { Logger.OAuth.Error("Unrecognized HttpDeliveryMethods value.", ex); - return null; + return MessageProtectionTasks.Null; } } else { - return null; + return MessageProtectionTasks.Null; } } @@ -59,6 +62,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// validates an incoming message based on the rules of this channel binding element. /// </summary> /// <param name="message">The incoming message to process.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// True if the <paramref name="message"/> applied to this binding element /// and the operation was successful. False if the operation did not apply to this message. @@ -67,8 +71,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Thrown when the binding element rules indicate that this message is invalid and should /// NOT be processed. /// </exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - return null; + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { + return MessageProtectionTasks.Null; } #endregion diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs index 780afdc..357ca45 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -12,6 +12,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System.Globalization; using System.Linq; using System.Text; + using System.Threading; + using System.Threading.Tasks; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; @@ -79,11 +81,12 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Signs the outgoing message. /// </summary> /// <param name="message">The message to sign.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + public Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { if (this.SignatureCallback != null) { @@ -95,29 +98,30 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { signedMessage.SignatureMethod = this.signatureMethod; Logger.Bindings.DebugFormat("Signing {0} message using {1}.", message.GetType().Name, this.signatureMethod); signedMessage.Signature = this.GetSignature(signedMessage); - return MessageProtections.TamperProtection; + return MessageProtectionTasks.TamperProtection; } - return null; + return MessageProtectionTasks.Null; } /// <summary> /// Verifies the signature on an incoming message. /// </summary> /// <param name="message">The message whose signature should be verified.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> /// <exception cref="InvalidSignatureException">Thrown if the signature is invalid.</exception> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + public Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null && this.IsMessageApplicable(signedMessage)) { Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature); if (!string.Equals(signedMessage.SignatureMethod, this.signatureMethod, StringComparison.Ordinal)) { Logger.Bindings.WarnFormat("Expected signature method '{0}' but received message with a signature method of '{1}'.", this.signatureMethod, signedMessage.SignatureMethod); - return MessageProtections.None; + return MessageProtectionTasks.None; } if (this.SignatureCallback != null) { @@ -131,10 +135,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { throw new InvalidSignatureException(message); } - return MessageProtections.TamperProtection; + return MessageProtectionTasks.TamperProtection; } - return null; + return MessageProtectionTasks.Null; } #endregion @@ -151,13 +155,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] internal static string ConstructSignatureBaseString(ITamperResistantOAuthMessage message, MessageDictionary messageDictionary) { Requires.NotNull(message, "message"); - Requires.NotNullOrEmpty(message.HttpMethod, "message.HttpMethod"); + Requires.NotNull(message.HttpMethod, "message.HttpMethod"); Requires.NotNull(messageDictionary, "messageDictionary"); ErrorUtilities.VerifyInternal(messageDictionary.Message == message, "Message references are not equal."); List<string> signatureBaseStringElements = new List<string>(3); - signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant()); + signatureBaseStringElements.Add(message.HttpMethod.ToString().ToUpperInvariant()); // For multipart POST messages, only include the message parts that are NOT // in the POST entity (those parts that may appear in an OAuth authorization header). diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs index 2b25566..c37d239 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs @@ -9,6 +9,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System.Collections.Generic; using System.Linq; using System.Text; + using System.Threading; + using System.Threading.Tasks; using DotNetOpenAuth.Messaging; using Validation; @@ -86,14 +88,15 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Prepares a message for sending based on the rules of this channel binding element. /// </summary> /// <param name="message">The message to prepare for sending.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> - public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + public async Task<MessageProtections?> ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { foreach (IChannelBindingElement signer in this.signers) { ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null."); - MessageProtections? result = signer.ProcessOutgoingMessage(message); + MessageProtections? result = await signer.ProcessOutgoingMessageAsync(message, cancellationToken); if (result.HasValue) { return result; } @@ -107,14 +110,15 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// validates an incoming message based on the rules of this channel binding element. /// </summary> /// <param name="message">The incoming message to process.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The protections (if any) that this binding element applied to the message. /// Null if this binding element did not even apply to this binding element. /// </returns> - public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + public async Task<MessageProtections?> ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) { foreach (IChannelBindingElement signer in this.signers) { ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null."); - MessageProtections? result = signer.ProcessIncomingMessage(message); + MessageProtections? result = await signer.ProcessIncomingMessageAsync(message, cancellationToken); if (result.HasValue) { return result; } |