diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2013-01-13 20:08:23 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2013-01-13 20:08:23 -0800 |
commit | dc4ea8b94bdff46e230c2a8e2bde811cb9c8cff8 (patch) | |
tree | 6644fc98becbcc06cfd8e7b7a1198bb55c35049a | |
parent | 6ab89555a4713182f7a7381d2f73913f3ad87b3b (diff) | |
download | DotNetOpenAuth-dc4ea8b94bdff46e230c2a8e2bde811cb9c8cff8.zip DotNetOpenAuth-dc4ea8b94bdff46e230c2a8e2bde811cb9c8cff8.tar.gz DotNetOpenAuth-dc4ea8b94bdff46e230c2a8e2bde811cb9c8cff8.tar.bz2 |
OAuth2.Client builds.
13 files changed, 168 insertions, 93 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index d9fedf2..4b04052 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -15,6 +15,7 @@ namespace DotNetOpenAuth.Messaging { using System.Linq; using System.Net; using System.Net.Http; + using System.Net.Http.Headers; using System.Net.Mime; using System.Runtime.Serialization.Json; using System.Security; @@ -625,6 +626,26 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Adds a Set-Cookie HTTP header for the specified cookie. + /// WARNING: support for cookie properties is currently VERY LIMITED. + /// </summary> + internal static void SetCookie(this HttpResponseHeaders headers, Cookie cookie) { + Requires.NotNull(headers, "headers"); + Requires.NotNull(cookie, "cookie"); + + var cookieBuilder = new StringBuilder(HttpUtility.UrlEncode(cookie.Name) + "=" + HttpUtility.UrlEncode(cookie.Value)); + if (cookie.HttpOnly) { + cookieBuilder.Append("; HttpOnly"); + } + + if (cookie.Secure) { + cookieBuilder.Append("; Secure"); + } + + headers.Add("Set-Cookie", cookieBuilder.ToString()); + } + + /// <summary> /// Gets a random string made up of a given set of allowable characters. /// </summary> /// <param name="length">The length of the desired random string.</param> @@ -1525,6 +1546,27 @@ namespace DotNetOpenAuth.Messaging { } } + internal static Uri GetDirectUriRequest(this HttpResponseMessage response) { + Requires.NotNull(response, "response"); + Requires.Argument( + response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb + || response.StatusCode == HttpStatusCode.RedirectMethod || response.StatusCode == HttpStatusCode.TemporaryRedirect, + "response", + "Redirecting response expected."); + Requires.Argument(response.Headers.Location != null, "response", "Redirect URL header expected."); + Requires.Argument(response.Content == null || response.Content is FormUrlEncodedContent, "response", "FormUrlEncodedContent expected"); + + var builder = new UriBuilder(response.Headers.Location); + if (response.Content != null) { + var content = response.Content.ReadAsStringAsync(); + Assumes.True(content.IsCompleted); // cached in memory, so it should never complete asynchronously. + var formFields = HttpUtility.ParseQueryString(content.Result).ToDictionary(); + MessagingUtilities.AppendQueryArgs(builder, formFields); + } + + return builder.Uri; + } + /// <summary> /// Collects a sequence of key=value pairs into a dictionary. /// </summary> diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs index da4c869..9ebca32 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs @@ -72,21 +72,21 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns> /// Returns <see cref="T:System.Threading.Tasks.Task`1" />. The task object representing the asynchronous operation. /// </returns> - protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { string bearerToken = this.BearerToken; if (bearerToken == null) { ErrorUtilities.VerifyProtocol(!this.Authorization.AccessTokenExpirationUtc.HasValue || this.Authorization.AccessTokenExpirationUtc < DateTime.UtcNow || this.Authorization.RefreshToken != null, ClientStrings.AuthorizationExpired); if (this.Authorization.AccessTokenExpirationUtc.HasValue && this.Authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { ErrorUtilities.VerifyProtocol(this.Authorization.RefreshToken != null, ClientStrings.AccessTokenRefreshFailed); - this.Client.RefreshAuthorization(this.Authorization); + await this.Client.RefreshAuthorizationAsync(this.Authorization, cancellationToken: cancellationToken); } bearerToken = this.Authorization.AccessToken; } request.Headers.Authorization = new AuthenticationHeaderValue(Protocol.BearerHttpAuthorizationScheme, bearerToken); - return base.SendAsync(request, cancellationToken); + return await base.SendAsync(request, cancellationToken); } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs index cf57618..fc1731d 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs @@ -9,9 +9,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using System.Collections.Generic; using System.Collections.Specialized; using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Xml; - using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.Messages; @@ -67,8 +69,8 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCore"/> method /// is overridden and does not require this method. /// </remarks> - protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - HttpWebRequest httpRequest; + protected override HttpRequestMessage CreateHttpRequest(IDirectedProtocolMessage request) { + HttpRequestMessage httpRequest; if ((request.HttpMethods & HttpDeliveryMethods.GetRequest) != 0) { httpRequest = InitializeRequestAsGet(request); } else if ((request.HttpMethods & HttpDeliveryMethods.PostRequest) != 0) { @@ -88,16 +90,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// The deserialized message parts, if found. Null otherwise. /// </returns> /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> - protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { + protected override async Task<IDictionary<string, string>> ReadFromResponseCoreAsync(HttpResponseMessage response) { // The spec says direct responses should be JSON objects, but Facebook uses HttpFormUrlEncoded instead, calling it text/plain // Others return text/javascript. Again bad. - string body = response.GetResponseReader().ReadToEnd(); - if (response.ContentType.MediaType == JsonEncoded || response.ContentType.MediaType == JsonTextEncoded) { + string body = await response.Content.ReadAsStringAsync(); + var contentType = response.Content.Headers.ContentType.MediaType; + if (contentType == JsonEncoded || contentType == JsonTextEncoded) { return this.DeserializeFromJson(body); - } else if (response.ContentType.MediaType == HttpFormUrlEncoded || response.ContentType.MediaType == PlainTextEncoded) { + } else if (contentType == HttpFormUrlEncoded || contentType == PlainTextEncoded) { return HttpUtility.ParseQueryString(body).ToDictionary(); } else { - throw ErrorUtilities.ThrowProtocol(ClientStrings.UnexpectedResponseContentType, response.ContentType.MediaType); + throw ErrorUtilities.ThrowProtocol(ClientStrings.UnexpectedResponseContentType, contentType); } } @@ -108,7 +111,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <returns> /// The deserialized message, if one is found. Null otherwise. /// </returns> - protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request) { + protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request, CancellationToken cancellationToken) { Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.GetPublicFacingUrl().AbsoluteUri); var fields = request.GetQueryStringBeforeRewriting().ToDictionary(); @@ -146,7 +149,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <remarks> /// This method implements spec OAuth V1.0 section 5.3. /// </remarks> - protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { + protected override HttpResponseMessage PrepareDirectResponse(IProtocolMessage response) { // Clients don't ever send direct responses. throw new NotImplementedException(); } @@ -155,7 +158,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Performs additional processing on an outgoing web request before it is sent to the remote server. /// </summary> /// <param name="request">The request.</param> - protected override void PrepareHttpWebRequest(HttpWebRequest request) { + protected override void PrepareHttpWebRequest(HttpRequestMessage request) { base.PrepareHttpWebRequest(request); if (this.ClientCredentialApplicator != null) { diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index d66f4fd..05bcb57 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs @@ -13,8 +13,9 @@ namespace DotNetOpenAuth.OAuth2 { using System.Net.Http; using System.Security; using System.Text; + using System.Threading; + using System.Threading.Tasks; using System.Xml; - using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; @@ -116,11 +117,11 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="request">The request for protected resources from the service provider.</param> /// <param name="authorization">The authorization for this request previously obtained via OAuth.</param> - public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) { + public Task AuthorizeRequestAsync(HttpWebRequest request, IAuthorizationState authorization, CancellationToken cancellationToken) { Requires.NotNull(request, "request"); Requires.NotNull(authorization, "authorization"); - this.AuthorizeRequest(request.Headers, authorization); + return this.AuthorizeRequestAsync(request.Headers, authorization, cancellationToken); } /// <summary> @@ -129,7 +130,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="requestHeaders">The headers on the request for protected resources from the service provider.</param> /// <param name="authorization">The authorization for this request previously obtained via OAuth.</param> - public void AuthorizeRequest(WebHeaderCollection requestHeaders, IAuthorizationState authorization) { + public async Task AuthorizeRequestAsync(WebHeaderCollection requestHeaders, IAuthorizationState authorization, CancellationToken cancellationToken) { Requires.NotNull(requestHeaders, "requestHeaders"); Requires.NotNull(authorization, "authorization"); Requires.That(!string.IsNullOrEmpty(authorization.AccessToken), "authorization", "AccessToken required."); @@ -137,7 +138,7 @@ namespace DotNetOpenAuth.OAuth2 { if (authorization.AccessTokenExpirationUtc.HasValue && authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { ErrorUtilities.VerifyProtocol(authorization.RefreshToken != null, ClientStrings.AccessTokenRefreshFailed); - this.RefreshAuthorization(authorization); + await this.RefreshAuthorizationAsync(authorization, cancellationToken: cancellationToken); } AuthorizeRequest(requestHeaders, authorization.AccessToken); @@ -180,7 +181,7 @@ namespace DotNetOpenAuth.OAuth2 { /// the <paramref name="authorization"/> parameter if the authorization server has cycled out your refresh token. /// If the parameter value was updated, this method calls <see cref="IAuthorizationState.SaveChanges"/> on that instance. /// </remarks> - public bool RefreshAuthorization(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null) { + public async Task<bool> RefreshAuthorizationAsync(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(authorization, "authorization"); Requires.That(!string.IsNullOrEmpty(authorization.RefreshToken), "authorization", "RefreshToken required."); @@ -200,7 +201,7 @@ namespace DotNetOpenAuth.OAuth2 { this.ApplyClientCredential(request); - var response = this.Channel.Request<AccessTokenSuccessResponse>(request); + var response = await this.Channel.RequestAsync<AccessTokenSuccessResponse>(request, cancellationToken); UpdateAuthorizationWithResponse(authorization, response); return true; } @@ -216,7 +217,7 @@ namespace DotNetOpenAuth.OAuth2 { /// If the return value includes a new refresh token, the old refresh token should be discarded and /// replaced with the new one. /// </remarks> - public IAuthorizationState GetScopedAccessToken(string refreshToken, HashSet<string> scope) { + public async Task<IAuthorizationState> GetScopedAccessTokenAsync(string refreshToken, HashSet<string> scope, CancellationToken cancellationToken) { Requires.NotNullOrEmpty(refreshToken, "refreshToken"); Requires.NotNull(scope, "scope"); @@ -227,7 +228,7 @@ namespace DotNetOpenAuth.OAuth2 { this.ApplyClientCredential(request); - var response = this.Channel.Request<AccessTokenSuccessResponse>(request); + var response = await this.Channel.RequestAsync<AccessTokenSuccessResponse>(request, cancellationToken); var authorization = new AuthorizationState(); UpdateAuthorizationWithResponse(authorization, response); @@ -241,7 +242,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="password">The resource owner's account password.</param> /// <param name="scopes">The desired scope of access.</param> /// <returns>The result, containing the tokens if successful.</returns> - public IAuthorizationState ExchangeUserCredentialForToken(string userName, string password, IEnumerable<string> scopes = null) { + public Task<IAuthorizationState> ExchangeUserCredentialForTokenAsync(string userName, string password, IEnumerable<string> scopes = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNullOrEmpty(userName, "userName"); Requires.NotNull(password, "password"); @@ -250,7 +251,7 @@ namespace DotNetOpenAuth.OAuth2 { Password = password, }; - return this.RequestAccessToken(request, scopes); + return this.RequestAccessTokenAsync(request, scopes, cancellationToken); } /// <summary> @@ -258,9 +259,9 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="scopes">The desired scopes.</param> /// <returns>The result of the authorization request.</returns> - public IAuthorizationState GetClientAccessToken(IEnumerable<string> scopes = null) { + public Task<IAuthorizationState> GetClientAccessTokenAsync(IEnumerable<string> scopes = null, CancellationToken cancellationToken = default(CancellationToken)) { var request = new AccessTokenClientCredentialsRequest(this.AuthorizationServer.TokenEndpoint, this.AuthorizationServer.Version); - return this.RequestAccessToken(request, scopes); + return this.RequestAccessTokenAsync(request, scopes, cancellationToken); } /// <summary> @@ -322,7 +323,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorizationState">The authorization state to update.</param> /// <param name="authorizationSuccess">The authorization success message obtained from the authorization server.</param> - internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) { + internal async Task UpdateAuthorizationWithResponseAsync(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess, CancellationToken cancellationToken) { Requires.NotNull(authorizationState, "authorizationState"); Requires.NotNull(authorizationSuccess, "authorizationSuccess"); @@ -332,7 +333,7 @@ namespace DotNetOpenAuth.OAuth2 { AuthorizationCode = authorizationSuccess.AuthorizationCode, }; this.ApplyClientCredential(accessTokenRequest); - IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); + IProtocolMessage accessTokenResponse = await this.Channel.RequestAsync(accessTokenRequest, cancellationToken); var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; if (accessTokenSuccess != null) { @@ -388,7 +389,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="request">The request message.</param> /// <param name="scopes">The scopes requested by the client.</param> /// <returns>The result of the request.</returns> - private IAuthorizationState RequestAccessToken(ScopedAccessTokenRequest request, IEnumerable<string> scopes = null) { + private async Task<IAuthorizationState> RequestAccessTokenAsync(ScopedAccessTokenRequest request, IEnumerable<string> scopes, CancellationToken cancellationToken) { Requires.NotNull(request, "request"); var authorizationState = new AuthorizationState(scopes); @@ -397,7 +398,7 @@ namespace DotNetOpenAuth.OAuth2 { this.ApplyClientCredential(request); request.Scope.UnionWith(authorizationState.Scope); - var response = this.Channel.Request(request); + var response = await this.Channel.RequestAsync(request, cancellationToken); var success = response as AccessTokenSuccessResponse; var failure = response as AccessTokenFailedResponse; ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs index 39ba1cb..769cf56 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OAuth2 { using System.Collections.Generic; using System.Linq; using System.Net; + using System.Net.Http; using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.Messages; @@ -75,7 +76,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> /// <param name="request">The outbound message to apply authentication information to.</param> - public virtual void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + public virtual void ApplyClientCredential(string clientIdentifier, HttpRequestMessage request) { } /// <summary> @@ -126,7 +127,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> /// <param name="request">The outbound message to apply authentication information to.</param> - public override void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + public override void ApplyClientCredential(string clientIdentifier, HttpRequestMessage request) { if (clientIdentifier != null) { if (this.credential != null) { ErrorUtilities.VerifyHost( diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs index dcb3826..e87ecaa 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs @@ -9,8 +9,9 @@ namespace DotNetOpenAuth.OAuth2 { using System.Collections.Generic; using System.Linq; using System.Text; + using System.Threading; + using System.Threading.Tasks; using System.Web; - using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.Messages; using Validation; @@ -80,12 +81,12 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns> /// A fully-qualified URL suitable to initiate the authorization flow. /// </returns> - public Uri RequestUserAuthorization(IEnumerable<string> scope = null, string state = null, Uri returnTo = null) { + public Task<Uri> RequestUserAuthorizationAsync(IEnumerable<string> scope = null, string state = null, Uri returnTo = null, CancellationToken cancellationToken = default(CancellationToken)) { var authorization = new AuthorizationState(scope) { Callback = returnTo, }; - return this.RequestUserAuthorization(authorization, state: state); + return this.RequestUserAuthorizationAsync(authorization, state: state, cancellationToken: cancellationToken); } /// <summary> @@ -101,12 +102,13 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns> /// A fully-qualified URL suitable to initiate the authorization flow. /// </returns> - public Uri RequestUserAuthorization(IAuthorizationState authorization, bool implicitResponseType = false, string state = null) { + public async Task<Uri> RequestUserAuthorizationAsync(IAuthorizationState authorization, bool implicitResponseType = false, string state = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); var request = this.PrepareRequestUserAuthorization(authorization, implicitResponseType, state); - return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel); + var response = await this.Channel.PrepareResponseAsync(request, cancellationToken); + return response.GetDirectUriRequest(); } /// <summary> @@ -115,7 +117,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="actualRedirectUrl">The actual URL of the incoming HTTP request.</param> /// <param name="authorizationState">The authorization.</param> /// <returns>The granted authorization, or <c>null</c> if the incoming HTTP request did not contain an authorization server response or authorization was rejected.</returns> - public IAuthorizationState ProcessUserAuthorization(Uri actualRedirectUrl, IAuthorizationState authorizationState = null) { + public async Task<IAuthorizationState> ProcessUserAuthorizationAsync(Uri actualRedirectUrl, IAuthorizationState authorizationState = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(actualRedirectUrl, "actualRedirectUrl"); if (authorizationState == null) { @@ -123,12 +125,12 @@ namespace DotNetOpenAuth.OAuth2 { } var carrier = new HttpRequestInfo("GET", actualRedirectUrl); - IDirectedProtocolMessage response = this.Channel.ReadFromRequest(carrier); + IDirectedProtocolMessage response = await this.Channel.ReadFromRequestAsync(carrier, cancellationToken); if (response == null) { return null; } - return this.ProcessUserAuthorization(authorizationState, response); + return await this.ProcessUserAuthorizationAsync(authorizationState, response, cancellationToken); } /// <summary> @@ -139,7 +141,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns> /// The granted authorization, or <c>null</c> if the incoming HTTP request did not contain an authorization server response or authorization was rejected. /// </returns> - internal IAuthorizationState ProcessUserAuthorization(IAuthorizationState authorizationState, IDirectedProtocolMessage response) { + internal async Task<IAuthorizationState> ProcessUserAuthorizationAsync(IAuthorizationState authorizationState, IDirectedProtocolMessage response, CancellationToken cancellationToken) { Requires.NotNull(authorizationState, "authorizationState"); Requires.NotNull(response, "response"); @@ -148,7 +150,7 @@ namespace DotNetOpenAuth.OAuth2 { if ((accessTokenSuccess = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); } else if ((authCodeSuccess = response as EndUserAuthorizationSuccessAuthCodeResponse) != null) { - this.UpdateAuthorizationWithResponse(authorizationState, authCodeSuccess); + await this.UpdateAuthorizationWithResponseAsync(authorizationState, authCodeSuccess, cancellationToken); } else if (response is EndUserAuthorizationFailedResponse) { authorizationState.Delete(); return null; diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs index 63d96e1..dd7aff8 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs @@ -10,10 +10,12 @@ namespace DotNetOpenAuth.OAuth2 { using System.Globalization; using System.Linq; using System.Net; + using System.Net.Http; using System.Text; + using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Web.Security; - using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.Messages; @@ -60,26 +62,14 @@ namespace DotNetOpenAuth.OAuth2 { /// <summary> /// Prepares a request for user authorization from an authorization server. /// </summary> - /// <param name="scope">The scope of authorized access requested.</param> - /// <param name="returnTo">The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.</param> - public void RequestUserAuthorization(IEnumerable<string> scope = null, Uri returnTo = null) { - var authorizationState = new AuthorizationState(scope) { - Callback = returnTo, - }; - this.PrepareRequestUserAuthorization(authorizationState).Send(); - } - - /// <summary> - /// Prepares a request for user authorization from an authorization server. - /// </summary> /// <param name="scopes">The scope of authorized access requested.</param> /// <param name="returnTo">The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.</param> /// <returns>The authorization request.</returns> - public OutgoingWebResponse PrepareRequestUserAuthorization(IEnumerable<string> scopes = null, Uri returnTo = null) { + public Task<HttpResponseMessage> PrepareRequestUserAuthorizationAsync(IEnumerable<string> scopes = null, Uri returnTo = null, CancellationToken cancellationToken = default(CancellationToken)) { var authorizationState = new AuthorizationState(scopes) { Callback = returnTo, }; - return this.PrepareRequestUserAuthorization(authorizationState); + return this.PrepareRequestUserAuthorizationAsync(authorizationState, cancellationToken); } /// <summary> @@ -87,7 +77,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorization">The authorization state to associate with this particular request.</param> /// <returns>The authorization request.</returns> - public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization) { + public async Task<HttpResponseMessage> PrepareRequestUserAuthorizationAsync(IAuthorizationState authorization, CancellationToken cancellationToken) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); @@ -108,12 +98,12 @@ namespace DotNetOpenAuth.OAuth2 { // Mitigate XSRF attacks by including a state value that would be unpredictable between users, but // verifiable for the same user/session. // If the host is implementing the authorization tracker though, they're handling this protection themselves. - HttpCookie cookie = null; + Cookie cookie = null; if (this.AuthorizationTracker == null) { var context = this.Channel.GetHttpContext(); string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16); - cookie = new HttpCookie(XsrfCookieName, xsrfKey) { + cookie = new Cookie(XsrfCookieName, xsrfKey) { HttpOnly = true, Secure = FormsAuthentication.RequireSSL, ////Expires = DateTime.Now.Add(OAuth2ClientSection.Configuration.MaxAuthorizationTime), // we prefer session cookies to persistent ones @@ -121,9 +111,9 @@ namespace DotNetOpenAuth.OAuth2 { request.ClientState = xsrfKey; } - var response = this.Channel.PrepareResponse(request); + var response = await this.Channel.PrepareResponseAsync(request, cancellationToken); if (cookie != null) { - response.Cookies.Add(cookie); + response.Headers.SetCookie(cookie); } return response; @@ -134,7 +124,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> /// <returns>The authorization state that contains the details of the authorization.</returns> - public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null) { + public async Task<IAuthorizationState> ProcessUserAuthorizationAsync(HttpRequestBase request = null, CancellationToken cancellationToken = default(CancellationToken)) { RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); RequiresEx.ValidState(this.ClientCredentialApplicator != null, Strings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator"); @@ -142,8 +132,8 @@ namespace DotNetOpenAuth.OAuth2 { request = this.Channel.GetRequestFromContext(); } - IMessageWithClientState response; - if (this.Channel.TryReadFromRequest<IMessageWithClientState>(request, out response)) { + var response = await this.Channel.TryReadFromRequestAsync<IMessageWithClientState>(cancellationToken, request); + if (response != null) { Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.GetPublicFacingUrl(), this.Channel.MessageDescriptions.Get(response)); IAuthorizationState authorizationState; if (this.AuthorizationTracker != null) { @@ -160,7 +150,7 @@ namespace DotNetOpenAuth.OAuth2 { var failure = response as EndUserAuthorizationFailedResponse; ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); if (success != null) { - this.UpdateAuthorizationWithResponse(authorizationState, success); + await this.UpdateAuthorizationWithResponseAsync(authorizationState, success, cancellationToken); } else { // failure Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server."); authorizationState.Delete(); diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/OAuth2ChannelBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/OAuth2ChannelBase.cs index e6be1c4..1c2c990 100644 --- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/OAuth2ChannelBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/OAuth2ChannelBase.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.Messages; + using Validation; /// <summary> @@ -31,8 +32,8 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// The binding elements to use in sending and receiving messages. /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages. /// </param> - internal OAuth2ChannelBase(Type[] messageTypes, params IChannelBindingElement[] channelBindingElements) - : base(Requires.NotNull(messageTypes, "messageTypes"), Versions, channelBindingElements) { + internal OAuth2ChannelBase(Type[] messageTypes, IChannelBindingElement[] channelBindingElements = null, IHostFactories hostFactories = null) + : base(Requires.NotNull(messageTypes, "messageTypes"), Versions, channelBindingElements ?? new IChannelBindingElement[0], hostFactories ?? new DefaultOAuth2HostFactories()) { } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2/DefaultOAuth2HostFactories.cs b/src/DotNetOpenAuth.OAuth2/DefaultOAuth2HostFactories.cs new file mode 100644 index 0000000..cc6aefc --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/DefaultOAuth2HostFactories.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------- +// <copyright file="DefaultOAuth2HostFactories.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Cache; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + + /// <summary> + /// Creates default instances of required dependencies. + /// </summary> + public class DefaultOAuth2HostFactories : IHostFactories { + /// <summary> + /// Initializes a new instance of a concrete derivation of <see cref="HttpMessageHandler" /> + /// to be used for outbound HTTP traffic. + /// </summary> + /// <returns>An instance of <see cref="HttpMessageHandler"/>.</returns> + /// <remarks> + /// An instance of <see cref="WebRequestHandler" /> is recommended where available; + /// otherwise an instance of <see cref="HttpClientHandler" /> is recommended. + /// </remarks> + public virtual HttpMessageHandler CreateHttpMessageHandler() { + var handler = new HttpClientHandler(); + return handler; + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpClient" /> class + /// to be used for outbound HTTP traffic. + /// </summary> + /// <param name="handler">The handler to pass to the <see cref="HttpClient" /> constructor. + /// May be null to use the default that would be provided by <see cref="CreateHttpMessageHandler" />.</param> + /// <returns> + /// An instance of <see cref="HttpClient" />. + /// </returns> + public HttpClient CreateHttpClient(HttpMessageHandler handler) { + handler = handler ?? this.CreateHttpMessageHandler(); + var client = new HttpClient(handler); + client.DefaultRequestHeaders.UserAgent.Add(Util.LibraryVersionHeader); + return client; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj index c570242..555915a 100644 --- a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj +++ b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj @@ -20,6 +20,7 @@ </PropertyGroup> <ItemGroup> <Compile Include="Configuration\OAuth2SectionGroup.cs" /> + <Compile Include="DefaultOAuth2HostFactories.cs" /> <Compile Include="GlobalSuppressions.cs" /> <Compile Include="OAuth2\AccessToken.cs" /> <Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" /> @@ -59,6 +60,8 @@ </ProjectReference> </ItemGroup> <ItemGroup> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="Validation"> <HintPath>..\packages\Validation.2.0.1.12362\lib\portable-windows8+net40+sl5+windowsphone8\Validation.dll</HintPath> <Private>True</Private> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs index f2acf79..13ea287 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs @@ -10,10 +10,13 @@ namespace DotNetOpenAuth.OAuth2 { using System.Globalization; using System.Linq; using System.Net; + using System.Net.Http.Headers; using System.Text; using DotNetOpenAuth.Messaging; using Validation; + using HttpRequestHeaders = DotNetOpenAuth.Messaging.HttpRequestHeaders; + /// <summary> /// Some common utility methods for OAuth 2.0. /// </summary> @@ -161,7 +164,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="headers">The headers collection to set the authorization header to.</param> /// <param name="userName">The username. Cannot be empty.</param> /// <param name="password">The password. Cannot be null.</param> - internal static void ApplyHttpBasicAuth(WebHeaderCollection headers, string userName, string password) { + internal static void ApplyHttpBasicAuth(System.Net.Http.Headers.HttpRequestHeaders headers, string userName, string password) { Requires.NotNull(headers, "headers"); Requires.NotNullOrEmpty(userName, "userName"); Requires.NotNull(password, "password"); @@ -169,8 +172,7 @@ namespace DotNetOpenAuth.OAuth2 { string concat = userName + ":" + password; byte[] bits = HttpBasicEncoding.GetBytes(concat); string base64 = Convert.ToBase64String(bits); - string header = HttpBasicAuthScheme + base64; - headers[HttpRequestHeader.Authorization] = header; + headers.Authorization = new AuthenticationHeaderValue(HttpBasicAuthScheme, base64); } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2/packages.config b/src/DotNetOpenAuth.OAuth2/packages.config index 58890d8..1d93cf5 100644 --- a/src/DotNetOpenAuth.OAuth2/packages.config +++ b/src/DotNetOpenAuth.OAuth2/packages.config @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" /> <package id="Validation" version="2.0.1.12362" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs index b656150..7b65a88 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs @@ -231,27 +231,6 @@ namespace DotNetOpenAuth.OpenId { return hostFactories.CreateHttpClient(rootHandler); } - internal static Uri GetDirectUriRequest(this HttpResponseMessage response) { - Requires.NotNull(response, "response"); - Requires.Argument( - response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb - || response.StatusCode == HttpStatusCode.RedirectMethod || response.StatusCode == HttpStatusCode.TemporaryRedirect, - "response", - "Redirecting response expected."); - Requires.Argument(response.Headers.Location != null, "response", "Redirect URL header expected."); - Requires.Argument(response.Content == null || response.Content is FormUrlEncodedContent, "response", "FormUrlEncodedContent expected"); - - var builder = new UriBuilder(response.Headers.Location); - if (response.Content != null) { - var content = response.Content.ReadAsStringAsync(); - Assumes.True(content.IsCompleted); // cached in memory, so it should never complete asynchronously. - var formFields = HttpUtility.ParseQueryString(content.Result).ToDictionary(); - MessagingUtilities.AppendQueryArgs(builder, formFields); - } - - return builder.Uri; - } - /// <summary> /// Gets the extension factories from the extension aggregator on an OpenID channel. /// </summary> |