diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2013-03-26 11:19:06 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2013-03-26 11:19:06 -0700 |
commit | 3d37ff45cab6838d80b22e6b782a0b9b4c2f4aeb (patch) | |
tree | c15816c3d7f6e74334553f2ff98605ce1c22c538 /src/DotNetOpenAuth.AspNet | |
parent | 5e9014f36b2d53b8e419918675df636540ea24e2 (diff) | |
parent | e6f7409f4caceb7bc2a5b4ddbcb1a4097af340f2 (diff) | |
download | DotNetOpenAuth-3d37ff45cab6838d80b22e6b782a0b9b4c2f4aeb.zip DotNetOpenAuth-3d37ff45cab6838d80b22e6b782a0b9b4c2f4aeb.tar.gz DotNetOpenAuth-3d37ff45cab6838d80b22e6b782a0b9b4c2f4aeb.tar.bz2 |
Move to HttpClient throughout library.
Diffstat (limited to 'src/DotNetOpenAuth.AspNet')
25 files changed, 260 insertions, 1202 deletions
diff --git a/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs b/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs index 9e8492d..4493288 100644 --- a/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs +++ b/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.AspNet { using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using DotNetOpenAuth.Messaging; + using System.Collections.Specialized; /// <summary> /// Represents the result of OAuth or OpenID authentication. @@ -46,10 +47,8 @@ namespace DotNetOpenAuth.AspNet { /// <param name="exception">The exception.</param> /// <param name="provider">The provider name.</param> public AuthenticationResult(Exception exception, string provider) - : this(isSuccessful: false) - { - if (exception == null) - { + : this(isSuccessful: false) { + if (exception == null) { throw new ArgumentNullException("exception"); } @@ -76,15 +75,12 @@ namespace DotNetOpenAuth.AspNet { /// The extra data. /// </param> public AuthenticationResult( - bool isSuccessful, string provider, string providerUserId, string userName, IDictionary<string, string> extraData) { + bool isSuccessful, string provider, string providerUserId, string userName, NameValueCollection extraData) { this.IsSuccessful = isSuccessful; this.Provider = provider; this.ProviderUserId = providerUserId; this.UserName = userName; - if (extraData != null) { - // wrap extraData in a read-only dictionary - this.ExtraData = new ReadOnlyDictionary<string, string>(extraData); - } + this.ExtraData = extraData ?? new NameValueCollection(); } /// <summary> @@ -95,7 +91,7 @@ namespace DotNetOpenAuth.AspNet { /// <summary> /// Gets the optional extra data that may be returned from the provider /// </summary> - public IDictionary<string, string> ExtraData { get; private set; } + public NameValueCollection ExtraData { get; private set; } /// <summary> /// Gets a value indicating whether the authentication step is successful. diff --git a/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs b/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs index f441c07..a84fdcf 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.AspNet.Clients { using System; using System.Collections.Generic; + using System.Collections.Specialized; using System.Xml.Linq; /// <summary> @@ -25,8 +26,8 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <param name="elementName"> /// Name of the element. /// </param> - public static void AddDataIfNotEmpty( - this Dictionary<string, string> dictionary, XDocument document, string elementName) { + internal static void AddDataIfNotEmpty( + this NameValueCollection dictionary, XDocument document, string elementName) { var element = document.Root.Element(elementName); if (element != null) { dictionary.AddItemIfNotEmpty(elementName, element.Value); @@ -45,7 +46,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <param name="value"> /// The value. /// </param> - public static void AddItemIfNotEmpty(this IDictionary<string, string> dictionary, string key, string value) { + internal static void AddItemIfNotEmpty(this NameValueCollection dictionary, string key, string value) { if (key == null) { throw new ArgumentNullException("key"); } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs deleted file mode 100644 index efc382f..0000000 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/AuthenticationOnlyCookieOAuthTokenManager.cs +++ /dev/null @@ -1,127 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthenticationOnlyCookieOAuthTokenManager.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet.Clients { - using System; - using System.Text; - using System.Web; - using System.Web.Security; - - /// <summary> - /// Stores OAuth tokens in the current request's cookie - /// </summary> - public class AuthenticationOnlyCookieOAuthTokenManager : IOAuthTokenManager { - /// <summary> - /// Key used for token cookie - /// </summary> - protected const string TokenCookieKey = "OAuthTokenSecret"; - - /// <summary> - /// Primary request context. - /// </summary> - private readonly HttpContextBase primaryContext; - - /// <summary> - /// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class. - /// </summary> - public AuthenticationOnlyCookieOAuthTokenManager() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class. - /// </summary> - /// <param name="context">The current request context.</param> - public AuthenticationOnlyCookieOAuthTokenManager(HttpContextBase context) { - this.primaryContext = context; - } - - /// <summary> - /// Gets the effective HttpContext object to use. - /// </summary> - protected HttpContextBase Context { - get { - return this.primaryContext ?? new HttpContextWrapper(HttpContext.Current); - } - } - - /// <summary> - /// Gets the token secret from the specified token. - /// </summary> - /// <param name="token">The token.</param> - /// <returns> - /// The token's secret - /// </returns> - public virtual string GetTokenSecret(string token) { - HttpCookie cookie = this.Context.Request.Cookies[TokenCookieKey]; - if (cookie == null || string.IsNullOrEmpty(cookie.Values[token])) { - return null; - } - - string secret = DecodeAndUnprotectToken(token, cookie.Values[token]); - return secret; - } - - /// <summary> - /// Replaces the request token with access token. - /// </summary> - /// <param name="requestToken">The request token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="accessTokenSecret">The access token secret.</param> - public virtual void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret) { - var cookie = new HttpCookie(TokenCookieKey) { - Value = string.Empty, - Expires = DateTime.UtcNow.AddDays(-5) - }; - this.Context.Response.Cookies.Set(cookie); - } - - /// <summary> - /// Stores the request token together with its secret. - /// </summary> - /// <param name="requestToken">The request token.</param> - /// <param name="requestTokenSecret">The request token secret.</param> - public virtual void StoreRequestToken(string requestToken, string requestTokenSecret) { - var cookie = new HttpCookie(TokenCookieKey) { - HttpOnly = true - }; - - if (FormsAuthentication.RequireSSL) { - cookie.Secure = true; - } - - var encryptedToken = ProtectAndEncodeToken(requestToken, requestTokenSecret); - cookie.Values[requestToken] = encryptedToken; - - this.Context.Response.Cookies.Set(cookie); - } - - /// <summary> - /// Protect and url-encode the specified token secret. - /// </summary> - /// <param name="token">The token to be used as a key.</param> - /// <param name="tokenSecret">The token secret to be protected</param> - /// <returns>The encrypted and protected string.</returns> - protected static string ProtectAndEncodeToken(string token, string tokenSecret) - { - byte[] cookieBytes = Encoding.UTF8.GetBytes(tokenSecret); - var secretBytes = MachineKeyUtil.Protect(cookieBytes, TokenCookieKey, "Token:" + token); - return HttpServerUtility.UrlTokenEncode(secretBytes); - } - - /// <summary> - /// Url-decode and unprotect the specified encrypted token string. - /// </summary> - /// <param name="token">The token to be used as a key.</param> - /// <param name="encryptedToken">The encrypted token to be decrypted</param> - /// <returns>The original token secret</returns> - protected static string DecodeAndUnprotectToken(string token, string encryptedToken) - { - byte[] cookieBytes = HttpServerUtility.UrlTokenDecode(encryptedToken); - byte[] clearBytes = MachineKeyUtil.Unprotect(cookieBytes, TokenCookieKey, "Token:" + token); - return Encoding.UTF8.GetString(clearBytes); - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/CookieOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/CookieOAuthTokenManager.cs deleted file mode 100644 index 398ee85..0000000 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/CookieOAuthTokenManager.cs +++ /dev/null @@ -1,79 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="CookieOAuthTokenManager.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet.Clients { - using System.Web; - using System.Web.Security; - - /// <summary> - /// Stores OAuth tokens in the current request's cookie. - /// </summary> - /// <remarks> - /// This class is different from the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> in that - /// it also stores the access token after the authentication has succeeded. - /// </remarks> - public class CookieOAuthTokenManager : AuthenticationOnlyCookieOAuthTokenManager { - /// <summary> - /// Initializes a new instance of the <see cref="CookieOAuthTokenManager"/> class. - /// </summary> - public CookieOAuthTokenManager() { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CookieOAuthTokenManager"/> class. - /// </summary> - /// <param name="context">The current request context.</param> - public CookieOAuthTokenManager(HttpContextBase context) - : base(context) { - } - - /// <summary> - /// Gets the token secret from the specified token. - /// </summary> - /// <param name="token">The token.</param> - /// <returns> - /// The token's secret - /// </returns> - public override string GetTokenSecret(string token) { - string secret = base.GetTokenSecret(token); - if (secret != null) { - return secret; - } - - // The base class checks for cookies in the Request object. - // Here we check in the Response object as well because we - // may have set it earlier in the request life cycle. - HttpCookie cookie = this.Context.Response.Cookies[TokenCookieKey]; - if (cookie == null || string.IsNullOrEmpty(cookie.Values[token])) { - return null; - } - - secret = DecodeAndUnprotectToken(token, cookie.Values[token]); - return secret; - } - - /// <summary> - /// Replaces the request token with access token. - /// </summary> - /// <param name="requestToken">The request token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="accessTokenSecret">The access token secret.</param> - public override void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret) { - var cookie = new HttpCookie(TokenCookieKey) { - HttpOnly = true - }; - - if (FormsAuthentication.RequireSSL) { - cookie.Secure = true; - } - - var encryptedToken = ProtectAndEncodeToken(accessToken, accessTokenSecret); - cookie.Values[accessToken] = encryptedToken; - - this.Context.Response.Cookies.Set(cookie); - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs index e216906..1b6318f 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs @@ -8,6 +8,10 @@ namespace DotNetOpenAuth.AspNet.Clients { using System; using System.Collections.Generic; using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; @@ -17,13 +21,13 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// The dot net open auth web consumer. /// </summary> - public class DotNetOpenAuthWebConsumer : IOAuthWebWorker, IDisposable { + public class DotNetOpenAuthWebConsumer : IOAuthWebWorker { #region Constants and Fields /// <summary> /// The _web consumer. /// </summary> - private readonly WebConsumer webConsumer; + private readonly Consumer webConsumer; #endregion @@ -38,75 +42,65 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <param name="tokenManager"> /// The token manager. /// </param> - public DotNetOpenAuthWebConsumer(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) { + public DotNetOpenAuthWebConsumer(ServiceProviderDescription serviceDescription, string consumerKey, string consumerSecret) { Requires.NotNull(serviceDescription, "serviceDescription"); - Requires.NotNull(tokenManager, "tokenManager"); - this.webConsumer = new WebConsumer(serviceDescription, tokenManager); + this.webConsumer = new Consumer { + ServiceProvider = serviceDescription, + ConsumerKey = consumerKey, + ConsumerSecret = consumerSecret, + TemporaryCredentialStorage = new CookieTemporaryCredentialStorage(), + }; } #endregion - #region Public Methods and Operators - /// <summary> - /// The prepare authorized request. + /// Gets the DotNetOpenAuth <see cref="WebConsumer"/> instance that can be used to make OAuth 1.0 authorized HTTP requests. /// </summary> - /// <param name="profileEndpoint"> - /// The profile endpoint. - /// </param> - /// <param name="accessToken"> - /// The access token. - /// </param> - /// <returns>An HTTP request.</returns> - public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint profileEndpoint, string accessToken) { - return this.webConsumer.PrepareAuthorizedRequest(profileEndpoint, accessToken); + public Consumer Consumer { + get { return this.webConsumer; } } + #region Public Methods and Operators + /// <summary> - /// The process user authorization. + /// Creates an HTTP message handler that authorizes outgoing web requests. /// </summary> - /// <returns>The response message.</returns> - public AuthorizedTokenResponse ProcessUserAuthorization() { - return this.webConsumer.ProcessUserAuthorization(); + /// <param name="accessToken">The access token.</param> + public HttpMessageHandler CreateMessageHandler(AccessToken accessToken) { + Requires.NotNullOrEmpty(accessToken.Token, "accessToken"); + + return this.Consumer.CreateMessageHandler(accessToken); } /// <summary> - /// The request authentication. + /// The process user authorization. /// </summary> - /// <param name="callback"> - /// The callback. - /// </param> - public void RequestAuthentication(Uri callback) { - var redirectParameters = new Dictionary<string, string>(); - UserAuthorizationRequest request = this.webConsumer.PrepareRequestUserAuthorization( - callback, null, redirectParameters); - this.webConsumer.Channel.PrepareResponse(request).Send(); - } - - #endregion + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The response message. + /// </returns> + public Task<AccessTokenResponse> ProcessUserAuthorizationAsync(HttpContextBase context = null, CancellationToken cancellationToken = default(CancellationToken)) { + if (context == null) { + context = new HttpContextWrapper(HttpContext.Current); + } - #region IDisposable members + return this.webConsumer.ProcessUserAuthorizationAsync(context.Request.Url, cancellationToken: cancellationToken); + } /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// The request authentication. /// </summary> - /// <filterpriority>2</filterpriority> - public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); + /// <param name="callback">The callback.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The response message. + /// </returns> + public Task<Uri> RequestAuthenticationAsync(Uri callback, CancellationToken cancellationToken = default(CancellationToken)) { + return this.webConsumer.RequestUserAuthorizationAsync(callback, cancellationToken: cancellationToken); } #endregion - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - this.webConsumer.Dispose(); - } - } } } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthTokenManager.cs deleted file mode 100644 index 92f1c22..0000000 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthTokenManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IOAuthTokenManager.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet.Clients { - /// <summary> - /// A token manager for use by a web site in its role as a consumer of - /// an individual ServiceProvider. - /// </summary> - /// <remarks> - /// This interface is used by clients of the DotNetOpenAuth.AspNet classes. - /// </remarks> - public interface IOAuthTokenManager { - /// <summary> - /// Gets the token secret from the specified token. - /// </summary> - /// <param name="token">The token.</param> - /// <returns>The token's secret</returns> - string GetTokenSecret(string token); - - /// <summary> - /// Stores the request token together with its secret. - /// </summary> - /// <param name="requestToken">The request token.</param> - /// <param name="requestTokenSecret">The request token secret.</param> - void StoreRequestToken(string requestToken, string requestTokenSecret); - - /// <summary> - /// Replaces the request token with access token. - /// </summary> - /// <param name="requestToken">The request token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="accessTokenSecret">The access token secret.</param> - void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret); - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs index a054a1c..e3ee3e8 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs @@ -7,41 +7,39 @@ namespace DotNetOpenAuth.AspNet.Clients { using System; using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using System.Web; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.Messages; /// <summary> - /// The io auth web worker. + /// The interface implemented by all OAuth web authentication modules in this assembly. /// </summary> public interface IOAuthWebWorker { - #region Public Methods and Operators - /// <summary> - /// The prepare authorized request. + /// Creates an HTTP message handler that authorizes outgoing web requests. /// </summary> - /// <param name="profileEndpoint"> - /// The profile endpoint. - /// </param> - /// <param name="accessToken"> - /// The access token. - /// </param> - /// <returns>An HTTP request.</returns> - HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint profileEndpoint, string accessToken); + /// <param name="accessToken">The access token.</param> + HttpMessageHandler CreateMessageHandler(AccessToken accessToken); /// <summary> /// The process user authorization. /// </summary> - /// <returns>The response message.</returns> - AuthorizedTokenResponse ProcessUserAuthorization(); + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The access token, if obtained; otherwise <c>null</c>. + /// </returns> + Task<AccessTokenResponse> ProcessUserAuthorizationAsync(HttpContextBase context = null, CancellationToken cancellationToken = default(CancellationToken)); /// <summary> /// The request authentication. /// </summary> - /// <param name="callback"> - /// The callback. - /// </param> - void RequestAuthentication(Uri callback); - - #endregion + /// <param name="callback">The callback.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The URL to redirect the user agent to.</returns> + Task<Uri> RequestAuthenticationAsync(Uri callback, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs deleted file mode 100644 index a7b641c..0000000 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs +++ /dev/null @@ -1,158 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="InMemoryOAuthTokenManager.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet.Clients { - using System; - using System.Collections.Generic; - using DotNetOpenAuth.OAuth; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; - using Validation; - - /// <summary> - /// An implementation of IOAuthTokenManager which stores keys in memory. - /// </summary> - public sealed class InMemoryOAuthTokenManager : IConsumerTokenManager { - #region Constants and Fields - - /// <summary> - /// The _tokens and secrets. - /// </summary> - private readonly Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); - - #endregion - - #region Constructors and Destructors - - /// <summary> - /// Initializes a new instance of the <see cref="InMemoryOAuthTokenManager"/> class. - /// </summary> - /// <param name="consumerKey"> - /// The consumer key. - /// </param> - /// <param name="consumerSecret"> - /// The consumer secret. - /// </param> - public InMemoryOAuthTokenManager(string consumerKey, string consumerSecret) { - Requires.NotNull(consumerKey, "consumerKey"); - Requires.NotNull(consumerSecret, "consumerSecret"); - - this.ConsumerKey = consumerKey; - this.ConsumerSecret = consumerSecret; - } - - #endregion - - #region Public Properties - - /// <summary> - /// Gets the consumer key. - /// </summary> - public string ConsumerKey { get; private set; } - - /// <summary> - /// Gets the consumer secret. - /// </summary> - public string ConsumerSecret { get; private set; } - - #endregion - - #region Public Methods and Operators - - /// <summary> - /// Deletes a request token and its associated secret and stores a new access token and secret. - /// </summary> - /// <param name="consumerKey"> - /// The Consumer that is exchanging its request token for an access token. - /// </param> - /// <param name="requestToken"> - /// The Consumer's request token that should be deleted/expired. - /// </param> - /// <param name="accessToken"> - /// The new access token that is being issued to the Consumer. - /// </param> - /// <param name="accessTokenSecret"> - /// The secret associated with the newly issued access token. - /// </param> - /// <remarks> - /// <para> - /// Any scope of granted privileges associated with the request token from the - /// original call to - /// <see cref="StoreNewRequestToken"/> - /// should be carried over - /// to the new Access Token. - /// </para> - /// <para> - /// To associate a user account with the new access token, - /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> - /// may be - /// useful in an ASP.NET web application within the implementation of this method. - /// Alternatively you may store the access token here without associating with a user account, - /// and wait until - /// <see cref="WebConsumer.ProcessUserAuthorization()"/> - /// or - /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> - /// return the access - /// token to associate the access token with a user account at that point. - /// </para> - /// </remarks> - public void ExpireRequestTokenAndStoreNewAccessToken( - string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { - this.tokensAndSecrets.Remove(requestToken); - this.tokensAndSecrets[accessToken] = accessTokenSecret; - } - - /// <summary> - /// Gets the Token Secret given a request or access token. - /// </summary> - /// <param name="token"> - /// The request or access token. - /// </param> - /// <returns> - /// The secret associated with the given token. - /// </returns> - /// <exception cref="ArgumentException"> - /// Thrown if the secret cannot be found for the given token. - /// </exception> - public string GetTokenSecret(string token) { - return this.tokensAndSecrets[token]; - } - - /// <summary> - /// Classifies a token as a request token or an access token. - /// </summary> - /// <param name="token"> - /// The token to classify. - /// </param> - /// <returns> - /// Request or Access token, or invalid if the token is not recognized. - /// </returns> - public TokenType GetTokenType(string token) { - throw new NotImplementedException(); - } - - /// <summary> - /// Stores a newly generated unauthorized request token, secret, and optional application-specific parameters for later recall. - /// </summary> - /// <param name="request"> - /// The request message that resulted in the generation of a new unauthorized request token. - /// </param> - /// <param name="response"> - /// The response message that includes the unauthorized request token. - /// </param> - /// <exception cref="ArgumentException"> - /// Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection. - /// </exception> - /// <remarks> - /// Request tokens stored by this method SHOULD NOT associate any user account with this token. It usually opens up security holes in your application to do so. Instead, you associate a user account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/> method. - /// </remarks> - public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { - this.tokensAndSecrets[response.Token] = response.TokenSecret; - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs index 3c157f3..daf3441 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs @@ -10,11 +10,15 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; using System.Xml.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using System.Collections.Specialized; /// <summary> /// Represents LinkedIn authentication client. @@ -25,21 +29,10 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Describes the OAuth service provider endpoints for LinkedIn. /// </summary> - public static readonly ServiceProviderDescription LinkedInServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = - new MessageReceivingEndpoint( - "https://api.linkedin.com/uas/oauth/requestToken", - HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - UserAuthorizationEndpoint = - new MessageReceivingEndpoint( - "https://www.linkedin.com/uas/oauth/authenticate", - HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - AccessTokenEndpoint = - new MessageReceivingEndpoint( - "https://api.linkedin.com/uas/oauth/accessToken", - HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, - }; + public static readonly ServiceProviderDescription LinkedInServiceDescription = new ServiceProviderDescription( + "https://api.linkedin.com/uas/oauth/requestToken", + "https://www.linkedin.com/uas/oauth/authenticate", + "https://api.linkedin.com/uas/oauth/accessToken"); #endregion @@ -48,28 +41,10 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Initializes a new instance of the <see cref="LinkedInClient"/> class. /// </summary> - /// <remarks> - /// Tokens exchanged during the OAuth handshake are stored in cookies. - /// </remarks> - /// <param name="consumerKey"> - /// The LinkedIn app's consumer key. - /// </param> - /// <param name="consumerSecret"> - /// The LinkedIn app's consumer secret. - /// </param> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", - Justification = "We can't dispose the object because we still need it through the app lifetime.")] - public LinkedInClient(string consumerKey, string consumerSecret) - : this(consumerKey, consumerSecret, new CookieOAuthTokenManager()) { } - - /// <summary> - /// Initializes a new instance of the <see cref="LinkedInClient"/> class. - /// </summary> /// <param name="consumerKey">The consumer key.</param> /// <param name="consumerSecret">The consumer secret.</param> - /// <param name="tokenManager">The token manager.</param> - public LinkedInClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager) - : base("linkedIn", LinkedInServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager)) { + public LinkedInClient(string consumerKey, string consumerSecret) + : base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret) { } #endregion @@ -79,46 +54,48 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> - /// <param name="response"> - /// The response token returned from service provider - /// </param> + /// <param name="response">The response token returned from service provider</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// Authentication result. + /// Authentication result. /// </returns> [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't care if the request fails.")] - protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response) { + protected override async Task<AuthenticationResult> VerifyAuthenticationCoreAsync(AccessTokenResponse response, CancellationToken cancellationToken = default(CancellationToken)) { // See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014 const string ProfileRequestUrl = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary)"; - string accessToken = response.AccessToken; - - var profileEndpoint = new MessageReceivingEndpoint(ProfileRequestUrl, HttpDeliveryMethods.GetRequest); - HttpWebRequest request = this.WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken); - + var accessToken = response.AccessToken; + var authorizingHandler = this.WebWorker.CreateMessageHandler(accessToken); try { - using (WebResponse profileResponse = request.GetResponse()) { - using (Stream responseStream = profileResponse.GetResponseStream()) { - XDocument document = LoadXDocumentFromStream(responseStream); - string userId = document.Root.Element("id").Value; - - string firstName = document.Root.Element("first-name").Value; - string lastName = document.Root.Element("last-name").Value; - string userName = firstName + " " + lastName; - - var extraData = new Dictionary<string, string>(); - extraData.Add("accesstoken", accessToken); - extraData.Add("name", userName); - extraData.AddDataIfNotEmpty(document, "headline"); - extraData.AddDataIfNotEmpty(document, "summary"); - extraData.AddDataIfNotEmpty(document, "industry"); - - return new AuthenticationResult( - isSuccessful: true, provider: this.ProviderName, providerUserId: userId, userName: userName, extraData: extraData); + using (var httpClient = new HttpClient(authorizingHandler)) { + using (HttpResponseMessage profileResponse = await httpClient.GetAsync(ProfileRequestUrl, cancellationToken)) { + using (Stream responseStream = await profileResponse.Content.ReadAsStreamAsync()) { + XDocument document = LoadXDocumentFromStream(responseStream); + string userId = document.Root.Element("id").Value; + + string firstName = document.Root.Element("first-name").Value; + string lastName = document.Root.Element("last-name").Value; + string userName = firstName + " " + lastName; + + var extraData = new NameValueCollection(); + extraData.Add("accesstoken", accessToken.Token); + extraData.Add("accesstokensecret", accessToken.Secret); + extraData.Add("name", userName); + extraData.AddDataIfNotEmpty(document, "headline"); + extraData.AddDataIfNotEmpty(document, "summary"); + extraData.AddDataIfNotEmpty(document, "industry"); + + return new AuthenticationResult( + isSuccessful: true, + provider: this.ProviderName, + providerUserId: userId, + userName: userName, + extraData: extraData); + } } } - } - catch (Exception exception) { + } catch (Exception exception) { return new AuthenticationResult(exception); } } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs index a0afeca..1841ef3 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs @@ -9,6 +9,8 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; + using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Xml; using System.Xml.Linq; @@ -17,6 +19,7 @@ namespace DotNetOpenAuth.AspNet.Clients { using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using Validation; + using System.Collections.Specialized; /// <summary> /// Represents base class for OAuth 1.0 clients @@ -31,34 +34,14 @@ namespace DotNetOpenAuth.AspNet.Clients { /// Name of the provider. /// </param> /// <param name="serviceDescription"> - /// The service description. - /// </param> - /// <param name="consumerKey"> - /// The consumer key. - /// </param> - /// <param name="consumerSecret"> - /// The consumer secret. - /// </param> - protected OAuthClient( - string providerName, ServiceProviderDescription serviceDescription, string consumerKey, string consumerSecret) - : this(providerName, serviceDescription, new InMemoryOAuthTokenManager(consumerKey, consumerSecret)) { } - - /// <summary> - /// Initializes a new instance of the <see cref="OAuthClient"/> class. - /// </summary> - /// <param name="providerName"> - /// Name of the provider. - /// </param> - /// <param name="serviceDescription"> /// The service Description. /// </param> /// <param name="tokenManager"> /// The token Manager. /// </param> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "I don't know how to ensure this rule is followed given this API")] protected OAuthClient( - string providerName, ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) - : this(providerName, new DotNetOpenAuthWebConsumer(serviceDescription, tokenManager)) { + string providerName, ServiceProviderDescription serviceDescription, string consumerKey, string consumerSecret) + : this(providerName, new DotNetOpenAuthWebConsumer(serviceDescription, consumerKey, consumerSecret)) { } /// <summary> @@ -103,42 +86,40 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Attempts to authenticate users by forwarding them to an external website, and upon succcess or failure, redirect users back to the specified url. /// </summary> - /// <param name="context"> - /// The context. - /// </param> - /// <param name="returnUrl"> - /// The return url after users have completed authenticating against external website. - /// </param> - public virtual void RequestAuthentication(HttpContextBase context, Uri returnUrl) { + /// <param name="context">The context.</param> + /// <param name="returnUrl">The return url after users have completed authenticating against external website.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + public virtual Task RequestAuthenticationAsync(HttpContextBase context, Uri returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(returnUrl, "returnUrl"); Requires.NotNull(context, "context"); Uri callback = returnUrl.StripQueryArgumentsWithPrefix("oauth_"); - this.WebWorker.RequestAuthentication(callback); + return this.WebWorker.RequestAuthenticationAsync(callback, cancellationToken); } /// <summary> /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> - /// <param name="context"> - /// The context. - /// </param> + /// <param name="context">The context.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// An instance of <see cref="AuthenticationResult"/> containing authentication result. + /// An instance of <see cref="AuthenticationResult" /> containing authentication result. /// </returns> - public virtual AuthenticationResult VerifyAuthentication(HttpContextBase context) { - AuthorizedTokenResponse response = this.WebWorker.ProcessUserAuthorization(); + public virtual async Task<AuthenticationResult> VerifyAuthenticationAsync(HttpContextBase context, CancellationToken cancellationToken = default(CancellationToken)) { + AccessTokenResponse response = await this.WebWorker.ProcessUserAuthorizationAsync(context, cancellationToken); if (response == null) { return AuthenticationResult.Failed; } - AuthenticationResult result = this.VerifyAuthenticationCore(response); + AuthenticationResult result = await this.VerifyAuthenticationCoreAsync(response, cancellationToken); if (result.IsSuccessful && result.ExtraData != null) { // add the access token to the user data dictionary just in case page developers want to use it - var wrapExtraData = result.ExtraData.IsReadOnly - ? new Dictionary<string, string>(result.ExtraData) - : result.ExtraData; - wrapExtraData["accesstoken"] = response.AccessToken; + var wrapExtraData = new NameValueCollection(result.ExtraData); + wrapExtraData["accesstoken"] = response.AccessToken.Token; + wrapExtraData["accesstokensecret"] = response.AccessToken.Secret; AuthenticationResult wrapResult = new AuthenticationResult( result.IsSuccessful, @@ -174,12 +155,13 @@ namespace DotNetOpenAuth.AspNet.Clients { /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> /// <param name="response"> - /// The response token returned from service provider + /// The access token returned from service provider /// </param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// Authentication result /// </returns> - protected abstract AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response); + protected abstract Task<AuthenticationResult> VerifyAuthenticationCoreAsync(AccessTokenResponse response, CancellationToken cancellationToken); #endregion } } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/SimpleConsumerTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/SimpleConsumerTokenManager.cs deleted file mode 100644 index 899204c..0000000 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/SimpleConsumerTokenManager.cs +++ /dev/null @@ -1,104 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SimpleConsumerTokenManager.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet.Clients { - using System; - using DotNetOpenAuth.OAuth.ChannelElements; - using Validation; - - /// <summary> - /// Simple wrapper around IConsumerTokenManager - /// </summary> - public class SimpleConsumerTokenManager : IConsumerTokenManager { - /// <summary> - /// Store the token manager. - /// </summary> - private readonly IOAuthTokenManager tokenManager; - - /// <summary> - /// Initializes a new instance of the <see cref="SimpleConsumerTokenManager"/> class. - /// </summary> - /// <param name="consumerKey">The consumer key.</param> - /// <param name="consumerSecret">The consumer secret.</param> - /// <param name="tokenManager">The OAuth token manager.</param> - public SimpleConsumerTokenManager(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager) { - Requires.NotNullOrEmpty(consumerKey, "consumerKey"); - Requires.NotNullOrEmpty(consumerSecret, "consumerSecret"); - Requires.NotNull(tokenManager, "oAuthTokenManager"); - - this.ConsumerKey = consumerKey; - this.ConsumerSecret = consumerSecret; - this.tokenManager = tokenManager; - } - - /// <summary> - /// Gets the consumer key. - /// </summary> - /// <value> - /// The consumer key. - /// </value> - public string ConsumerKey { - get; - private set; - } - - /// <summary> - /// Gets the consumer secret. - /// </summary> - /// <value> - /// The consumer secret. - /// </value> - public string ConsumerSecret { - get; - private set; - } - - /// <summary> - /// Gets the Token Secret given a request or access token. - /// </summary> - /// <param name="token">The request or access token.</param> - /// <returns> - /// The secret associated with the given token. - /// </returns> - /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> - public string GetTokenSecret(string token) { - return this.tokenManager.GetTokenSecret(token); - } - - /// <summary> - /// Stores a newly generated unauthorized request token, secret, and optional - /// application-specific parameters for later recall. - /// </summary> - /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> - /// <param name="response">The response message that includes the unauthorized request token.</param> - /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> - public void StoreNewRequestToken(DotNetOpenAuth.OAuth.Messages.UnauthorizedTokenRequest request, DotNetOpenAuth.OAuth.Messages.ITokenSecretContainingMessage response) { - this.tokenManager.StoreRequestToken(response.Token, response.TokenSecret); - } - - /// <summary> - /// Deletes a request token and its associated secret and stores a new access token and secret. - /// </summary> - /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> - /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> - /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> - /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> - public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { - this.tokenManager.ReplaceRequestTokenWithAccessToken(requestToken, accessToken, accessTokenSecret); - } - - /// <summary> - /// Classifies a token as a request token or an access token. - /// </summary> - /// <param name="token">The token to classify.</param> - /// <returns> - /// Request or Access token, or invalid if the token is not recognized. - /// </returns> - public TokenType GetTokenType(string token) { - throw new NotSupportedException(); - } - } -}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs index 886917a..0c17ed3 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs @@ -10,11 +10,15 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; using System.Xml.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using System.Collections.Specialized; /// <summary> /// Represents a Twitter client @@ -25,51 +29,23 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature. /// </summary> - public static readonly ServiceProviderDescription TwitterServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = - new MessageReceivingEndpoint( - "https://api.twitter.com/oauth/request_token", - HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - UserAuthorizationEndpoint = - new MessageReceivingEndpoint( - "https://api.twitter.com/oauth/authenticate", - HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - AccessTokenEndpoint = - new MessageReceivingEndpoint( - "https://api.twitter.com/oauth/access_token", - HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, - }; + public static readonly ServiceProviderDescription TwitterServiceDescription = + new ServiceProviderDescription( + "https://api.twitter.com/oauth/request_token", + "https://api.twitter.com/oauth/authenticate", + "https://api.twitter.com/oauth/access_token"); #endregion #region Constructors and Destructors /// <summary> - /// Initializes a new instance of the <see cref="TwitterClient"/> class with the specified consumer key and consumer secret. - /// </summary> - /// <remarks> - /// Tokens exchanged during the OAuth handshake are stored in cookies. - /// </remarks> - /// <param name="consumerKey"> - /// The consumer key. - /// </param> - /// <param name="consumerSecret"> - /// The consumer secret. - /// </param> - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", - Justification = "We can't dispose the object because we still need it through the app lifetime.")] - public TwitterClient(string consumerKey, string consumerSecret) - : this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) { } - - /// <summary> /// Initializes a new instance of the <see cref="TwitterClient"/> class. /// </summary> /// <param name="consumerKey">The consumer key.</param> /// <param name="consumerSecret">The consumer secret.</param> - /// <param name="tokenManager">The token manager.</param> - public TwitterClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager) - : base("twitter", TwitterServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager)) { + public TwitterClient(string consumerKey, string consumerSecret) + : base("twitter", TwitterServiceDescription, consumerKey, consumerSecret) { } #endregion @@ -79,34 +55,34 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> - /// <param name="response"> - /// The response token returned from service provider - /// </param> + /// <param name="response">The response token returned from service provider</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// Authentication result + /// Authentication result /// </returns> [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't care if the request for additional data fails.")] - protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response) { - string accessToken = response.AccessToken; + protected override async Task<AuthenticationResult> VerifyAuthenticationCoreAsync(AccessTokenResponse response, CancellationToken cancellationToken) { string userId = response.ExtraData["user_id"]; string userName = response.ExtraData["screen_name"]; var profileRequestUrl = new Uri("https://api.twitter.com/1/users/show.xml?user_id=" + MessagingUtilities.EscapeUriDataStringRfc3986(userId)); - var profileEndpoint = new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest); - HttpWebRequest request = this.WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken); + var authorizingHandler = this.WebWorker.CreateMessageHandler(response.AccessToken); - var extraData = new Dictionary<string, string>(); - extraData.Add("accesstoken", accessToken); + var extraData = new NameValueCollection(); + extraData.Add("accesstoken", response.AccessToken.Token); + extraData.Add("accesstokensecret", response.AccessToken.Secret); try { - using (WebResponse profileResponse = request.GetResponse()) { - using (Stream responseStream = profileResponse.GetResponseStream()) { - XDocument document = LoadXDocumentFromStream(responseStream); - extraData.AddDataIfNotEmpty(document, "name"); - extraData.AddDataIfNotEmpty(document, "location"); - extraData.AddDataIfNotEmpty(document, "description"); - extraData.AddDataIfNotEmpty(document, "url"); + using (var httpClient = new HttpClient(authorizingHandler)) { + using (HttpResponseMessage profileResponse = await httpClient.GetAsync(profileRequestUrl, cancellationToken)) { + using (Stream responseStream = await profileResponse.Content.ReadAsStreamAsync()) { + XDocument document = LoadXDocumentFromStream(responseStream); + extraData.AddDataIfNotEmpty(document, "name"); + extraData.AddDataIfNotEmpty(document, "location"); + extraData.AddDataIfNotEmpty(document, "description"); + extraData.AddDataIfNotEmpty(document, "url"); + } } } } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs index d20e452..c06c1dc 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Web; using DotNetOpenAuth.Messaging; using Validation; + using System.Collections.Specialized; /// <summary> /// The facebook client. @@ -92,7 +93,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The access token. /// </param> /// <returns>A dictionary of profile data.</returns> - protected override IDictionary<string, string> GetUserData(string accessToken) { + protected override NameValueCollection GetUserData(string accessToken) { FacebookGraphData graphData; var request = WebRequest.Create( @@ -104,7 +105,7 @@ namespace DotNetOpenAuth.AspNet.Clients { } // this dictionary must contains - var userData = new Dictionary<string, string>(); + var userData = new NameValueCollection(); userData.AddItemIfNotEmpty("id", graphData.Id); userData.AddItemIfNotEmpty("username", graphData.Email); userData.AddItemIfNotEmpty("name", graphData.Name); diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs index 3e5f71f..b9c4941 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Net; using DotNetOpenAuth.Messaging; using Validation; + using System.Collections.Specialized; /// <summary> /// The Microsoft account client. @@ -110,7 +111,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// A dictionary contains key-value pairs of user data /// </returns> - protected override IDictionary<string, string> GetUserData(string accessToken) { + protected override NameValueCollection GetUserData(string accessToken) { MicrosoftClientUserData graph; var request = WebRequest.Create( @@ -121,7 +122,7 @@ namespace DotNetOpenAuth.AspNet.Clients { } } - var userData = new Dictionary<string, string>(); + var userData = new NameValueCollection(); userData.AddItemIfNotEmpty("id", graph.Id); userData.AddItemIfNotEmpty("username", graph.Name); userData.AddItemIfNotEmpty("name", graph.Name); diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs index 014f459..8e6b5f3 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs @@ -8,8 +8,14 @@ namespace DotNetOpenAuth.AspNet.Clients { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Threading; + using System.Threading.Tasks; using System.Web; + + using DotNetOpenAuth.Messaging; + using Validation; + using System.Collections.Specialized; /// <summary> /// Represents the base class for OAuth 2.0 clients @@ -57,30 +63,31 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Attempts to authenticate users by forwarding them to an external website, and upon succcess or failure, redirect users back to the specified url. /// </summary> - /// <param name="context"> - /// The context. - /// </param> - /// <param name="returnUrl"> - /// The return url after users have completed authenticating against external website. - /// </param> - public virtual void RequestAuthentication(HttpContextBase context, Uri returnUrl) { + /// <param name="context">The context.</param> + /// <param name="returnUrl">The return url after users have completed authenticating against external website.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + public virtual Task RequestAuthenticationAsync(HttpContextBase context, Uri returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(context, "context"); Requires.NotNull(returnUrl, "returnUrl"); string redirectUrl = this.GetServiceLoginUrl(returnUrl).AbsoluteUri; context.Response.Redirect(redirectUrl, endResponse: true); + return MessagingUtilities.CompletedTask; } /// <summary> /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> - /// <param name="context"> - /// The context. - /// </param> + /// <param name="context">The context.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// An instance of <see cref="AuthenticationResult"/> containing authentication result. + /// An instance of <see cref="AuthenticationResult" /> containing authentication result. /// </returns> - public AuthenticationResult VerifyAuthentication(HttpContextBase context) { + /// <exception cref="System.InvalidOperationException">Always thrown.</exception> + public Task<AuthenticationResult> VerifyAuthenticationAsync(HttpContextBase context, CancellationToken cancellationToken = default(CancellationToken)) { throw new InvalidOperationException(WebResources.OAuthRequireReturnUrl); } @@ -89,10 +96,11 @@ namespace DotNetOpenAuth.AspNet.Clients { /// </summary> /// <param name="context">The context.</param> /// <param name="returnPageUrl">The return URL which should match the value passed to RequestAuthentication() method.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// An instance of <see cref="AuthenticationResult"/> containing authentication result. + /// An instance of <see cref="AuthenticationResult" /> containing authentication result. /// </returns> - public virtual AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl) { + public virtual async Task<AuthenticationResult> VerifyAuthenticationAsync(HttpContextBase context, Uri returnPageUrl, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(context, "context"); string code = context.Request.QueryString["code"]; @@ -105,19 +113,15 @@ namespace DotNetOpenAuth.AspNet.Clients { return AuthenticationResult.Failed; } - IDictionary<string, string> userData = this.GetUserData(accessToken); + var userData = this.GetUserData(accessToken); if (userData == null) { return AuthenticationResult.Failed; } - string id = userData["id"]; - string name; - // Some oAuth providers do not return value for the 'username' attribute. // In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id' - if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name)) { - name = id; - } + string id = userData["id"]; + string name = userData["username"] ?? userData["name"] ?? id; // add the access token to the user data dictionary just in case page developers want to use it userData["accesstoken"] = accessToken; @@ -152,7 +156,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// A dictionary contains key-value pairs of user data /// </returns> - protected abstract IDictionary<string, string> GetUserData(string accessToken); + protected abstract NameValueCollection GetUserData(string accessToken); /// <summary> /// Queries the access token from the specified authorization code. diff --git a/src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs index 6b4061a..2a68bd8 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Collections.Generic; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.RelyingParty; + using System.Collections.Specialized; /// <summary> /// Represents Google OpenID client. @@ -32,10 +33,10 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The response message. /// </param> /// <returns>A dictionary of profile data; or null if no data is available.</returns> - protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response) { + protected override NameValueCollection GetExtraData(IAuthenticationResponse response) { FetchResponse fetchResponse = response.GetExtension<FetchResponse>(); if (fetchResponse != null) { - var extraData = new Dictionary<string, string>(); + var extraData = new NameValueCollection(); extraData.AddItemIfNotEmpty("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email)); extraData.AddItemIfNotEmpty("country", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country)); extraData.AddItemIfNotEmpty("firstName", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.First)); diff --git a/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs index a41b504..901741b 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs @@ -8,11 +8,14 @@ namespace DotNetOpenAuth.AspNet.Clients { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Threading; + using System.Threading.Tasks; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.RelyingParty; using Validation; + using System.Collections.Specialized; /// <summary> /// Base classes for OpenID clients. @@ -79,51 +82,47 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <summary> /// Attempts to authenticate users by forwarding them to an external website, and upon succcess or failure, redirect users back to the specified url. /// </summary> - /// <param name="context"> - /// The context of the current request. - /// </param> - /// <param name="returnUrl"> - /// The return url after users have completed authenticating against external website. - /// </param> + /// <param name="context">The context of the current request.</param> + /// <param name="returnUrl">The return url after users have completed authenticating against external website.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "We don't have a Uri object handy.")] - public virtual void RequestAuthentication(HttpContextBase context, Uri returnUrl) { + public virtual async Task RequestAuthenticationAsync(HttpContextBase context, Uri returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(returnUrl, "returnUrl"); var realm = new Realm(returnUrl.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped)); - IAuthenticationRequest request = RelyingParty.CreateRequest(this.providerIdentifier, realm, returnUrl); + IAuthenticationRequest request = await RelyingParty.CreateRequestAsync(this.providerIdentifier, realm, returnUrl, cancellationToken); // give subclasses a chance to modify request message, e.g. add extension attributes, etc. this.OnBeforeSendingAuthenticationRequest(request); - request.RedirectToProvider(); + await request.RedirectToProviderAsync(context); } /// <summary> /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> - /// <param name="context"> - /// The context of the current request. - /// </param> + /// <param name="context">The context of the current request.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// An instance of <see cref="AuthenticationResult"/> containing authentication result. + /// An instance of <see cref="AuthenticationResult" /> containing authentication result. /// </returns> - public virtual AuthenticationResult VerifyAuthentication(HttpContextBase context) { - IAuthenticationResponse response = RelyingParty.GetResponse(); + /// <exception cref="System.InvalidOperationException">Thrown if no OpenID response was found in the incoming HTTP request.</exception> + public virtual async Task<AuthenticationResult> VerifyAuthenticationAsync(HttpContextBase context, CancellationToken cancellationToken = default(CancellationToken)) { + IAuthenticationResponse response = await RelyingParty.GetResponseAsync(context.Request, cancellationToken); if (response == null) { throw new InvalidOperationException(WebResources.OpenIDFailedToGetResponse); } if (response.Status == AuthenticationStatus.Authenticated) { string id = response.ClaimedIdentifier; - string username; - - Dictionary<string, string> extraData = this.GetExtraData(response) ?? new Dictionary<string, string>(); + var extraData = this.GetExtraData(response) ?? new NameValueCollection(); // try to look up username from the 'username' or 'email' property. If not found, fall back to 'friendly id' - if (!extraData.TryGetValue("username", out username) && !extraData.TryGetValue("email", out username)) { - username = response.FriendlyIdentifierForDisplay; - } + string username = extraData["username"] ?? extraData["email"] ?? response.FriendlyIdentifierForDisplay; return new AuthenticationResult(true, this.ProviderName, id, username, extraData); } @@ -142,7 +141,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The response message. /// </param> /// <returns>Always null.</returns> - protected virtual Dictionary<string, string> GetExtraData(IAuthenticationResponse response) { + protected virtual NameValueCollection GetExtraData(IAuthenticationResponse response) { return null; } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs index bd420fc..d282d4f 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.AspNet.Clients { using System.Collections.Generic; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.RelyingParty; + using System.Collections.Specialized; /// <summary> /// The yahoo open id client. @@ -32,10 +33,10 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The response message. /// </param> /// <returns>A dictionary of profile data; or null if no data is available.</returns> - protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response) { + protected override NameValueCollection GetExtraData(IAuthenticationResponse response) { FetchResponse fetchResponse = response.GetExtension<FetchResponse>(); if (fetchResponse != null) { - var extraData = new Dictionary<string, string>(); + var extraData = new NameValueCollection(); extraData.AddItemIfNotEmpty("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email)); extraData.AddItemIfNotEmpty("fullName", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName)); diff --git a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj index 16229c7..b3b52d9 100644 --- a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj +++ b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj @@ -32,6 +32,8 @@ <ItemGroup> <Reference Include="System" /> <Reference Include="System.Core" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Runtime.Serialization" /> <Reference Include="System.Web" /> <Reference Include="System.Xml.Linq" /> @@ -39,21 +41,15 @@ <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> - <Reference Include="Validation"> - <HintPath>..\packages\Validation.2.0.1.12362\lib\portable-windows8+net40+sl5+windowsphone8\Validation.dll</HintPath> - <Private>True</Private> + <Reference Include="Validation, Version=2.0.0.0, Culture=neutral, PublicKeyToken=2fc06f0d701809a7, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Validation.2.0.2.13022\lib\portable-windows8+net40+sl5+windowsphone8\Validation.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> <Compile Include="AuthenticationResult.cs" /> <Compile Include="Clients\DictionaryExtensions.cs" /> <Compile Include="Clients\OAuth2\WindowsLiveClient.cs" /> - <Compile Include="Clients\OAuth\AuthenticationOnlyCookieOAuthTokenManager.cs"> - <SubType>Code</SubType> - </Compile> - <Compile Include="Clients\OAuth\CookieOAuthTokenManager.cs" /> - <Compile Include="Clients\OAuth\IOAuthTokenManager.cs" /> - <Compile Include="Clients\OAuth\SimpleConsumerTokenManager.cs" /> <Compile Include="IAuthenticationClient.cs" /> <Compile Include="Clients\OAuth2\FacebookClient.cs" /> <Compile Include="Clients\OAuth2\FacebookGraphData.cs" /> @@ -63,7 +59,6 @@ <Compile Include="Clients\OAuth2\MicrosoftClient.cs" /> <Compile Include="Clients\OAuth2\MicrosoftClientUserData.cs" /> <Compile Include="Clients\OAuth\DotNetOpenAuthWebConsumer.cs" /> - <Compile Include="Clients\OAuth\InMemoryOAuthTokenManager.cs" /> <Compile Include="Clients\OAuth\IOAuthWebWorker.cs" /> <Compile Include="Clients\OAuth\LinkedInClient.cs" /> <Compile Include="Clients\OAuth\OAuthClient.cs" /> @@ -71,7 +66,6 @@ <Compile Include="Clients\OpenID\GoogleOpenIdClient.cs" /> <Compile Include="Clients\OpenID\OpenIdClient.cs" /> <Compile Include="Clients\OpenID\YahooOpenIdClient.cs" /> - <Compile Include="MachineKeyUtil.cs" /> <Compile Include="UriHelper.cs" /> <Compile Include="IOpenAuthDataProvider.cs" /> <Compile Include="OpenAuthAuthenticationTicketHelper.cs" /> diff --git a/src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs b/src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs index 4d9acde..b13f0d1 100644 --- a/src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs +++ b/src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs @@ -6,6 +6,8 @@ namespace DotNetOpenAuth.AspNet { using System; + using System.Threading; + using System.Threading.Tasks; using System.Web; /// <summary> @@ -20,23 +22,20 @@ namespace DotNetOpenAuth.AspNet { /// <summary> /// Attempts to authenticate users by forwarding them to an external website, and upon succcess or failure, redirect users back to the specified url. /// </summary> - /// <param name="context"> - /// The context of the current request. - /// </param> - /// <param name="returnUrl"> - /// The return url after users have completed authenticating against external website. - /// </param> - void RequestAuthentication(HttpContextBase context, Uri returnUrl); + /// <param name="context">The context of the current request.</param> + /// <param name="returnUrl">The return url after users have completed authenticating against external website.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A task that completes with the async operation.</returns> + Task RequestAuthenticationAsync(HttpContextBase context, Uri returnUrl, CancellationToken cancellationToken = default(CancellationToken)); /// <summary> /// Check if authentication succeeded after user is redirected back from the service provider. /// </summary> - /// <param name="context"> - /// The context of the current request. - /// </param> + /// <param name="context">The context of the current request.</param> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> - /// An instance of <see cref="AuthenticationResult"/> containing authentication result. + /// An instance of <see cref="AuthenticationResult" /> containing authentication result. /// </returns> - AuthenticationResult VerifyAuthentication(HttpContextBase context); + Task<AuthenticationResult> VerifyAuthenticationAsync(HttpContextBase context, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs b/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs deleted file mode 100644 index eb2020b..0000000 --- a/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MachineKeyUtil.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Net; - using System.Reflection; - using System.Security.Cryptography; - using System.Text; - using System.Web; - using System.Web.Security; - - /// <summary> - /// Provides helpers that mimic the ASP.NET 4.5 MachineKey.Protect / Unprotect APIs, - /// even when running on ASP.NET 4.0. Consumers are expected to follow the same - /// conventions used by the MachineKey.Protect / Unprotect APIs (consult MSDN docs - /// for how these are meant to be used). Additionally, since this helper class - /// dynamically switches between the two based on whether the current application is - /// .NET 4.0 or 4.5, consumers should never persist output from the Protect method - /// since the implementation will change when upgrading 4.0 -> 4.5. This should be - /// used for transient data only. - /// </summary> - internal static class MachineKeyUtil { - /// <summary> - /// MachineKey implementation depending on the target .NET framework version - /// </summary> - private static readonly IMachineKey MachineKeyImpl = GetMachineKeyImpl(); - - /// <summary> - /// ProtectUnprotect delegate. - /// </summary> - /// <param name="data">The data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>Result of either Protect or Unprotect methods.</returns> - private delegate byte[] ProtectUnprotect(byte[] data, string[] purposes); - - /// <summary> - /// Abstract the MachineKey implementation in .NET 4.0 and 4.5 - /// </summary> - private interface IMachineKey { - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The protected data.</returns> - byte[] Protect(byte[] userData, string[] purposes); - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unprotected data.</returns> - byte[] Unprotect(byte[] protectedData, string[] purposes); - } - - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The encrypted data</returns> - public static byte[] Protect(byte[] userData, params string[] purposes) { - return MachineKeyImpl.Protect(userData, purposes); - } - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unencrypted data</returns> - public static byte[] Unprotect(byte[] protectedData, params string[] purposes) { - return MachineKeyImpl.Unprotect(protectedData, purposes); - } - - /// <summary> - /// Gets the machine key implementation based on the runtime framework version. - /// </summary> - /// <returns>The machine key implementation</returns> - private static IMachineKey GetMachineKeyImpl() { - // Late bind to the MachineKey.Protect / Unprotect methods only if <httpRuntime targetFramework="4.5" />. - // This helps ensure that round-tripping the payloads continues to work even if the application is - // deployed to a mixed 4.0 / 4.5 farm environment. - PropertyInfo targetFrameworkProperty = typeof(HttpRuntime).GetProperty("TargetFramework", typeof(Version)); - Version targetFramework = (targetFrameworkProperty != null) ? targetFrameworkProperty.GetValue(null, null) as Version : null; - if (targetFramework != null && targetFramework >= new Version(4, 5)) { - ProtectUnprotect protectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Protect", ignoreCase: false, throwOnBindFailure: false); - ProtectUnprotect unprotectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Unprotect", ignoreCase: false, throwOnBindFailure: false); - if (protectThunk != null && unprotectThunk != null) { - return new MachineKey45(protectThunk, unprotectThunk); // ASP.NET 4.5 - } - } - - return new MachineKey40(); // ASP.NET 4.0 - } - - /// <summary> - /// On ASP.NET 4.0, we perform some transforms which mimic the behaviors of MachineKey.Protect - /// and Unprotect. - /// </summary> - private sealed class MachineKey40 : IMachineKey { - /// <summary> - /// This is the magic header that identifies a MachineKey40 payload. - /// It helps differentiate this from other encrypted payloads.</summary> - private const uint MagicHeader = 0x8519140c; - - /// <summary> - /// The SHA-256 factory to be used. - /// </summary> - private static readonly Func<SHA256> sha256Factory = GetSHA256Factory(); - - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The protected data</returns> - public byte[] Protect(byte[] userData, string[] purposes) { - if (userData == null) { - throw new ArgumentNullException("userData"); - } - - // dataWithHeader = {magic header} .. {purposes} .. {userData} - byte[] dataWithHeader = new byte[checked(4 /* magic header */ + (256 / 8) /* purposes */ + userData.Length)]; - unchecked { - dataWithHeader[0] = (byte)(MagicHeader >> 24); - dataWithHeader[1] = (byte)(MagicHeader >> 16); - dataWithHeader[2] = (byte)(MagicHeader >> 8); - dataWithHeader[3] = (byte)MagicHeader; - } - byte[] purposeHash = ComputeSHA256(purposes); - Buffer.BlockCopy(purposeHash, 0, dataWithHeader, 4, purposeHash.Length); - Buffer.BlockCopy(userData, 0, dataWithHeader, 4 + (256 / 8), userData.Length); - - // encrypt + sign - string hexValue = MachineKey.Encode(dataWithHeader, MachineKeyProtection.All); - - // convert hex -> binary - byte[] binary = HexToBinary(hexValue); - return binary; - } - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unprotected data</returns> - public byte[] Unprotect(byte[] protectedData, string[] purposes) { - if (protectedData == null) { - throw new ArgumentNullException("protectedData"); - } - - // convert binary -> hex and calculate what the purpose should read - string hexEncodedData = BinaryToHex(protectedData); - byte[] purposeHash = ComputeSHA256(purposes); - - try { - // decrypt / verify signature - byte[] dataWithHeader = MachineKey.Decode(hexEncodedData, MachineKeyProtection.All); - - // validate magic header and purpose string - if (dataWithHeader != null - && dataWithHeader.Length >= (4 + (256 / 8)) - && (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(dataWithHeader, 0)) == MagicHeader - && AreByteArraysEqual(new ArraySegment<byte>(purposeHash), new ArraySegment<byte>(dataWithHeader, 4, 256 / 8))) { - // validation succeeded - byte[] userData = new byte[dataWithHeader.Length - 4 - (256 / 8)]; - Buffer.BlockCopy(dataWithHeader, 4 + (256 / 8), userData, 0, userData.Length); - return userData; - } - } - catch { - // swallow since will be rethrown immediately below - } - - // if we reached this point, some cryptographic operation failed - throw new CryptographicException(WebResources.Generic_CryptoFailure); - } - - /// <summary> - /// Convert bytes to hex string. - /// </summary> - /// <param name="binary">The input array.</param> - /// <returns>Hex string</returns> - internal static string BinaryToHex(byte[] binary) { - StringBuilder builder = new StringBuilder(checked(binary.Length * 2)); - for (int i = 0; i < binary.Length; i++) { - byte b = binary[i]; - builder.Append(HexDigit(b >> 4)); - builder.Append(HexDigit(b & 0x0F)); - } - string result = builder.ToString(); - return result; - } - - /// <summary> - /// This method is specially written to take the same amount of time - /// regardless of where 'a' and 'b' differ. Please do not optimize it.</summary> - /// <param name="a">first array.</param> - /// <param name="b">second array.</param> - /// <returns><c href="true" /> if equal, others <c href="false" /></returns> - private static bool AreByteArraysEqual(ArraySegment<byte> a, ArraySegment<byte> b) { - if (a.Count != b.Count) { - return false; - } - - bool areEqual = true; - for (int i = 0; i < a.Count; i++) { - areEqual &= a.Array[a.Offset + i] == b.Array[b.Offset + i]; - } - return areEqual; - } - - /// <summary> - /// Computes a SHA256 hash over all of the input parameters. - /// Each parameter is UTF8 encoded and preceded by a 7-bit encoded</summary> - /// integer describing the encoded byte length of the string. - /// <param name="parameters">The parameters.</param> - /// <returns>The output hash</returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")] - private static byte[] ComputeSHA256(IList<string> parameters) { - using (MemoryStream ms = new MemoryStream()) { - using (BinaryWriter bw = new BinaryWriter(ms)) { - if (parameters != null) { - foreach (string parameter in parameters) { - bw.Write(parameter); // also writes the length as a prefix; unambiguous - } - bw.Flush(); - } - - using (SHA256 sha256 = sha256Factory()) { - byte[] retVal = sha256.ComputeHash(ms.GetBuffer(), 0, checked((int)ms.Length)); - return retVal; - } - } - } - } - - /// <summary> - /// Gets the SHA-256 factory. - /// </summary> - /// <returns>SHA256 factory</returns> - private static Func<SHA256> GetSHA256Factory() { - // Note: ASP.NET 4.5 always prefers CNG, but the CNG algorithms are not that - // performant on 4.0 and below. The following list is optimized for speed - // given our scenarios. - if (!CryptoConfig.AllowOnlyFipsAlgorithms) { - // This provider is not FIPS-compliant, so we can't use it if FIPS compliance - // is mandatory. - return () => new SHA256Managed(); - } - - try { - using (SHA256Cng sha256 = new SHA256Cng()) { - return () => new SHA256Cng(); - } - } - catch (PlatformNotSupportedException) { - // CNG not supported (perhaps because we're not on Windows Vista or above); move on - } - - // If all else fails, fall back to CAPI. - return () => new SHA256CryptoServiceProvider(); - } - - /// <summary> - /// Convert to hex character - /// </summary> - /// <param name="value">The value to be converted.</param> - /// <returns>Hex character</returns> - private static char HexDigit(int value) { - return (char)(value > 9 ? value + '7' : value + '0'); - } - - /// <summary> - /// Convert hdex string to bytes. - /// </summary> - /// <param name="hex">Input hex string.</param> - /// <returns>The bytes</returns> - private static byte[] HexToBinary(string hex) { - int size = hex.Length / 2; - byte[] bytes = new byte[size]; - for (int idx = 0; idx < size; idx++) { - bytes[idx] = (byte)((HexValue(hex[idx * 2]) << 4) + HexValue(hex[(idx * 2) + 1])); - } - return bytes; - } - - /// <summary> - /// Convert hex digit to byte. - /// </summary> - /// <param name="digit">The hex digit.</param> - /// <returns>The byte</returns> - private static int HexValue(char digit) { - return digit > '9' ? digit - '7' : digit - '0'; - } - } - - /// <summary> - /// On ASP.NET 4.5, we can just delegate to MachineKey.Protect and MachineKey.Unprotect directly, - /// which contain optimized code paths. - /// </summary> - private sealed class MachineKey45 : IMachineKey { - /// <summary> - /// Protect thunk - /// </summary> - private readonly ProtectUnprotect protectThunk; - - /// <summary> - /// Unprotect thunk - /// </summary> - private readonly ProtectUnprotect unprotectThunk; - - /// <summary> - /// Initializes a new instance of the <see cref="MachineKey45"/> class. - /// </summary> - /// <param name="protectThunk">The protect thunk.</param> - /// <param name="unprotectThunk">The unprotect thunk.</param> - public MachineKey45(ProtectUnprotect protectThunk, ProtectUnprotect unprotectThunk) { - this.protectThunk = protectThunk; - this.unprotectThunk = unprotectThunk; - } - - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The protected data</returns> - public byte[] Protect(byte[] userData, string[] purposes) { - return this.protectThunk(userData, purposes); - } - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unprotected data</returns> - public byte[] Unprotect(byte[] protectedData, string[] purposes) { - return this.unprotectThunk(protectedData, purposes); - } - } - } -} diff --git a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs index 6736205..7669072 100644 --- a/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs +++ b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs @@ -8,6 +8,8 @@ namespace DotNetOpenAuth.AspNet { using System; using System.Diagnostics.CodeAnalysis; using System.Text; + using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Web.Security; using DotNetOpenAuth.AspNet.Clients; @@ -141,10 +143,12 @@ namespace DotNetOpenAuth.AspNet { /// <summary> /// Requests the specified provider to start the authentication by directing users to an external website /// </summary> - /// <param name="returnUrl"> - /// The return url after user is authenticated. - /// </param> - public void RequestAuthentication(string returnUrl) { + /// <param name="returnUrl">The return url after user is authenticated.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + public async Task RequestAuthenticationAsync(string returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { // convert returnUrl to an absolute path Uri uri; if (!string.IsNullOrEmpty(returnUrl)) { @@ -176,20 +180,21 @@ namespace DotNetOpenAuth.AspNet { this.requestContext.Response.Cookies.Add(xsrfCookie); // issue the redirect to the external auth provider - this.authenticationProvider.RequestAuthentication(this.requestContext, uri); + await this.authenticationProvider.RequestAuthenticationAsync(this.requestContext, uri, cancellationToken); } /// <summary> /// Checks if user is successfully authenticated when user is redirected back to this user. /// </summary> /// <param name="returnUrl">The return Url which must match exactly the Url passed into RequestAuthentication() earlier.</param> - /// <remarks> - /// This returnUrl parameter only applies to OAuth2 providers. For other providers, it ignores the returnUrl parameter. - /// </remarks> + /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The result of the authentication. /// </returns> - public AuthenticationResult VerifyAuthentication(string returnUrl) { + /// <remarks> + /// This returnUrl parameter only applies to OAuth2 providers. For other providers, it ignores the returnUrl parameter. + /// </remarks> + public async Task<AuthenticationResult> VerifyAuthenticationAsync(string returnUrl, CancellationToken cancellationToken = default(CancellationToken)) { // check for XSRF attack string sessionId; bool successful = this.ValidateRequestAgainstXsrfAttack(out sessionId); @@ -223,7 +228,7 @@ namespace DotNetOpenAuth.AspNet { uri = uri.AttachQueryStringParameter(SessionIdQueryStringName, sessionId); try { - AuthenticationResult result = oauth2Client.VerifyAuthentication(this.requestContext, uri); + AuthenticationResult result = await oauth2Client.VerifyAuthenticationAsync(this.requestContext, uri, cancellationToken); if (!result.IsSuccessful) { // if the result is a Failed result, creates a new Failed response which has providerName info. result = new AuthenticationResult( @@ -241,7 +246,7 @@ namespace DotNetOpenAuth.AspNet { } } else { - return this.authenticationProvider.VerifyAuthentication(this.requestContext); + return await this.authenticationProvider.VerifyAuthenticationAsync(this.requestContext, cancellationToken); } } diff --git a/src/DotNetOpenAuth.AspNet/WebResources.Designer.cs b/src/DotNetOpenAuth.AspNet/WebResources.Designer.cs index fd79a73..da1d1ca 100644 --- a/src/DotNetOpenAuth.AspNet/WebResources.Designer.cs +++ b/src/DotNetOpenAuth.AspNet/WebResources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.544 +// Runtime Version:4.0.30319.18033 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -79,15 +79,6 @@ namespace DotNetOpenAuth.AspNet { } /// <summary> - /// Looks up a localized string similar to The provided data could not be decrypted. If the current application is deployed in a web farm configuration, ensure that the 'decryptionKey' and 'validationKey' attributes are explicitly specified in the <machineKey> configuration section.. - /// </summary> - internal static string Generic_CryptoFailure { - get { - return ResourceManager.GetString("Generic_CryptoFailure", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to An OAuth data provider has already been registered for this application.. /// </summary> internal static string OAuthDataProviderRegistered { diff --git a/src/DotNetOpenAuth.AspNet/WebResources.resx b/src/DotNetOpenAuth.AspNet/WebResources.resx index c1552e9..a491579 100644 --- a/src/DotNetOpenAuth.AspNet/WebResources.resx +++ b/src/DotNetOpenAuth.AspNet/WebResources.resx @@ -123,9 +123,6 @@ <data name="FailedToEncryptTicket" xml:space="preserve"> <value>Unable to encrypt the authentication ticket.</value> </data> - <data name="Generic_CryptoFailure" xml:space="preserve"> - <value>The provided data could not be decrypted. If the current application is deployed in a web farm configuration, ensure that the 'decryptionKey' and 'validationKey' attributes are explicitly specified in the <machineKey> configuration section.</value> - </data> <data name="OAuthDataProviderRegistered" xml:space="preserve"> <value>An OAuth data provider has already been registered for this application.</value> </data> diff --git a/src/DotNetOpenAuth.AspNet/packages.config b/src/DotNetOpenAuth.AspNet/packages.config index 58890d8..d32d62f 100644 --- a/src/DotNetOpenAuth.AspNet/packages.config +++ b/src/DotNetOpenAuth.AspNet/packages.config @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Validation" version="2.0.1.12362" targetFramework="net45" /> + <package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" /> + <package id="Validation" version="2.0.2.13022" targetFramework="net45" /> </packages>
\ No newline at end of file |