diff options
Diffstat (limited to 'samples/DotNetOpenAuth.ApplicationBlock')
8 files changed, 335 insertions, 583 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj index 81160f2..19f26b5 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj +++ b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj @@ -33,6 +33,7 @@ <ApplicationVersion>1.0.0.%2a</ApplicationVersion> <UseApplicationTrust>false</UseApplicationTrust> <BootstrapperEnabled>true</BootstrapperEnabled> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\src\</SolutionDir> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -68,11 +69,16 @@ <DefineConstants>$(DefineConstants);SAMPLESONLY</DefineConstants> </PropertyGroup> <ItemGroup> + <Reference Include="Newtonsoft.Json"> + <HintPath>..\..\src\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.configuration" /> <Reference Include="System.Core"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Runtime.Serialization" /> <Reference Include="System.ServiceModel.Web" /> <Reference Include="System.Web" /> @@ -93,10 +99,8 @@ <Compile Include="Facebook\FacebookGraph.cs" /> <Compile Include="CustomExtensions\UIRequestAtRelyingPartyFactory.cs" /> <Compile Include="GoogleConsumer.cs" /> + <Compile Include="HttpAsyncHandlerBase.cs" /> <Compile Include="InMemoryClientAuthorizationTracker.cs" /> - <Compile Include="InMemoryTokenManager.cs"> - <SubType>Code</SubType> - </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TwitterConsumer.cs" /> <Compile Include="Util.cs" /> @@ -131,6 +135,10 @@ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> <Name>DotNetOpenAuth.Core</Name> </ProjectReference> + <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.Common\DotNetOpenAuth.OAuth.Common.csproj"> + <Project>{115217c5-22cd-415c-a292-0dd0238cdd89}</Project> + <Name>DotNetOpenAuth.OAuth.Common</Name> + </ProjectReference> <ProjectReference Include="..\..\src\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj"> <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project> <Name>DotNetOpenAuth.OAuth.Consumer</Name> @@ -168,6 +176,10 @@ <Name>DotNetOpenAuth.OpenId</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> + <Import Project="$(SolutionDir)\.nuget\nuget.targets" /> </Project>
\ No newline at end of file diff --git a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs index 1bdb04d..a7c062e 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs @@ -7,14 +7,20 @@ namespace DotNetOpenAuth.ApplicationBlock { using System; using System.Collections.Generic; + using System.Configuration; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using System.Web; using System.Xml; using System.Xml.Linq; using DotNetOpenAuth.Messaging; @@ -24,16 +30,15 @@ namespace DotNetOpenAuth.ApplicationBlock { /// <summary> /// A consumer capable of communicating with Google Data APIs. /// </summary> - public static class GoogleConsumer { + public class GoogleConsumer : Consumer { /// <summary> /// The Consumer to use for accessing Google data APIs. /// </summary> - public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, - }; + public static readonly ServiceProviderDescription ServiceDescription = + new ServiceProviderDescription( + "https://www.google.com/accounts/OAuthGetRequestToken", + "https://www.google.com/accounts/OAuthAuthorizeToken", + "https://www.google.com/accounts/OAuthGetAccessToken"); /// <summary> /// A mapping between Google's applications and their URI scope values. @@ -60,7 +65,19 @@ namespace DotNetOpenAuth.ApplicationBlock { /// <summary> /// The URI to get contacts once authorization is granted. /// </summary> - private static readonly MessageReceivingEndpoint GetContactsEndpoint = new MessageReceivingEndpoint("http://www.google.com/m8/feeds/contacts/default/full/", HttpDeliveryMethods.GetRequest); + private static readonly Uri GetContactsEndpoint = new Uri("http://www.google.com/m8/feeds/contacts/default/full/"); + + /// <summary> + /// Initializes a new instance of the <see cref="GoogleConsumer"/> class. + /// </summary> + public GoogleConsumer() { + this.ServiceProvider = ServiceDescription; + this.ConsumerKey = ConfigurationManager.AppSettings["googleConsumerKey"]; + this.ConsumerSecret = ConfigurationManager.AppSettings["googleConsumerSecret"]; + this.TemporaryCredentialStorage = HttpContext.Current != null + ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() + : new MemoryTemporaryCredentialStorage(); + } /// <summary> /// The many specific authorization scopes Google offers. @@ -149,91 +166,60 @@ namespace DotNetOpenAuth.ApplicationBlock { } /// <summary> - /// The service description to use for accessing Google data APIs using an X509 certificate. + /// Gets the scope URI in Google's format. /// </summary> - /// <param name="signingCertificate">The signing certificate.</param> - /// <returns>A service description that can be used to create an instance of - /// <see cref="DesktopConsumer"/> or <see cref="WebConsumer"/>. </returns> - public static ServiceProviderDescription CreateRsaSha1ServiceDescription(X509Certificate2 signingCertificate) { - if (signingCertificate == null) { - throw new ArgumentNullException("signingCertificate"); - } - - return new ServiceProviderDescription { - RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new RsaSha1ConsumerSigningBindingElement(signingCertificate) }, - }; + /// <param name="scope">The scope, which may include one or several Google applications.</param> + /// <returns>A space-delimited list of URIs for the requested Google applications.</returns> + public static string GetScopeUri(Applications scope) { + return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray()); } /// <summary> /// Requests authorization from Google to access data from a set of Google applications. /// </summary> - /// <param name="consumer">The Google consumer previously constructed using <see cref="CreateWebConsumer"/> or <see cref="CreateDesktopConsumer"/>.</param> /// <param name="requestedAccessScope">The requested access scope.</param> - public static void RequestAuthorization(WebConsumer consumer, Applications requestedAccessScope) { - if (consumer == null) { - throw new ArgumentNullException("consumer"); - } - + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + public Task<Uri> RequestUserAuthorizationAsync(Applications requestedAccessScope, CancellationToken cancellationToken = default(CancellationToken)) { var extraParameters = new Dictionary<string, string> { { "scope", GetScopeUri(requestedAccessScope) }, }; Uri callback = Util.GetCallbackUrlFromContext(); - var request = consumer.PrepareRequestUserAuthorization(callback, extraParameters, null); - consumer.Channel.Send(request); - } - - /// <summary> - /// Requests authorization from Google to access data from a set of Google applications. - /// </summary> - /// <param name="consumer">The Google consumer previously constructed using <see cref="CreateWebConsumer"/> or <see cref="CreateDesktopConsumer"/>.</param> - /// <param name="requestedAccessScope">The requested access scope.</param> - /// <param name="requestToken">The unauthorized request token assigned by Google.</param> - /// <returns>The request token</returns> - public static Uri RequestAuthorization(DesktopConsumer consumer, Applications requestedAccessScope, out string requestToken) { - if (consumer == null) { - throw new ArgumentNullException("consumer"); - } - - var extraParameters = new Dictionary<string, string> { - { "scope", GetScopeUri(requestedAccessScope) }, - }; - - return consumer.RequestUserAuthorization(extraParameters, null, out requestToken); + return this.RequestUserAuthorizationAsync(callback, extraParameters, cancellationToken); } /// <summary> /// Gets the Gmail address book's contents. /// </summary> - /// <param name="consumer">The Google consumer.</param> /// <param name="accessToken">The access token previously retrieved.</param> /// <param name="maxResults">The maximum number of entries to return. If you want to receive all of the contacts, rather than only the default maximum, you can specify a very large number here.</param> /// <param name="startIndex">The 1-based index of the first result to be retrieved (for paging).</param> - /// <returns>An XML document returned by Google.</returns> - public static XDocument GetContacts(ConsumerBase consumer, string accessToken, int maxResults/* = 25*/, int startIndex/* = 1*/) { - if (consumer == null) { - throw new ArgumentNullException("consumer"); - } - - var extraData = new Dictionary<string, string>() { - { "start-index", startIndex.ToString(CultureInfo.InvariantCulture) }, - { "max-results", maxResults.ToString(CultureInfo.InvariantCulture) }, - }; - var request = consumer.PrepareAuthorizedRequest(GetContactsEndpoint, accessToken, extraData); - + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// An XML document returned by Google. + /// </returns> + public async Task<XDocument> GetContactsAsync(AccessToken accessToken, int maxResults = 25, int startIndex = 1, CancellationToken cancellationToken = default(CancellationToken)) { // Enable gzip compression. Google only compresses the response for recognized user agent headers. - Mike Lim - request.AutomaticDecompression = DecompressionMethods.GZip; - request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.151 Safari/534.16"; - - var response = consumer.Channel.WebRequestHandler.GetResponse(request); - string body = response.GetResponseReader().ReadToEnd(); - XDocument result = XDocument.Parse(body); - return result; + var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }; + using (var httpClient = this.CreateHttpClient(accessToken, handler)) { + var request = new HttpRequestMessage(HttpMethod.Get, GetContactsEndpoint); + request.Content = new FormUrlEncodedContent( + new Dictionary<string, string>() { + { "start-index", startIndex.ToString(CultureInfo.InvariantCulture) }, + { "max-results", maxResults.ToString(CultureInfo.InvariantCulture) }, + }); + request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse("Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.151 Safari/534.16")); + using (var response = await httpClient.SendAsync(request, cancellationToken)) { + string body = await response.Content.ReadAsStringAsync(); + XDocument result = XDocument.Parse(body); + return result; + } + } } - public static void PostBlogEntry(ConsumerBase consumer, string accessToken, string blogUrl, string title, XElement body) { + public async Task PostBlogEntryAsync(AccessToken accessToken, string blogUrl, string title, XElement body, CancellationToken cancellationToken = default(CancellationToken)) { string feedUrl; var getBlogHome = WebRequest.Create(blogUrl); using (var blogHomeResponse = getBlogHome.GetResponse()) { @@ -258,31 +244,21 @@ namespace DotNetOpenAuth.ApplicationBlock { XmlWriter xw = XmlWriter.Create(ms, xws); entry.WriteTo(xw); xw.Flush(); - - WebRequest request = consumer.PrepareAuthorizedRequest(new MessageReceivingEndpoint(feedUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), accessToken); - request.ContentType = "application/atom+xml"; - request.Method = "POST"; - request.ContentLength = ms.Length; ms.Seek(0, SeekOrigin.Begin); - using (Stream requestStream = request.GetRequestStream()) { - ms.CopyTo(requestStream); - } - using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { - if (response.StatusCode == HttpStatusCode.Created) { - // Success - } else { - // Error! + + var request = new HttpRequestMessage(HttpMethod.Post, feedUrl); + request.Content = new StreamContent(ms); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/atom+xml"); + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (var response = await httpClient.SendAsync(request, cancellationToken)) { + if (response.StatusCode == HttpStatusCode.Created) { + // Success + } else { + // Error! + response.EnsureSuccessStatusCode(); // throw some meaningful exception. + } } } } - - /// <summary> - /// Gets the scope URI in Google's format. - /// </summary> - /// <param name="scope">The scope, which may include one or several Google applications.</param> - /// <returns>A space-delimited list of URIs for the requested Google applications.</returns> - public static string GetScopeUri(Applications scope) { - return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray()); - } } } diff --git a/samples/DotNetOpenAuth.ApplicationBlock/HttpAsyncHandlerBase.cs b/samples/DotNetOpenAuth.ApplicationBlock/HttpAsyncHandlerBase.cs new file mode 100644 index 0000000..a72a9b1 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/HttpAsyncHandlerBase.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="HttpAsyncHandlerBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + + public abstract class HttpAsyncHandlerBase : IHttpAsyncHandler { + public abstract bool IsReusable { get; } + + public IAsyncResult BeginProcessRequest(HttpContext context, System.AsyncCallback cb, object extraData) { + return ToApm(this.ProcessRequestAsync(context), cb, extraData); + } + + public void EndProcessRequest(IAsyncResult result) { + ((Task)result).Wait(); // rethrows exceptions + } + + public void ProcessRequest(HttpContext context) { + this.ProcessRequestAsync(context).GetAwaiter().GetResult(); + } + + protected abstract Task ProcessRequestAsync(HttpContext context); + + private static Task ToApm(Task task, AsyncCallback callback, object state) { + if (task == null) { + throw new ArgumentNullException("task"); + } + + var tcs = new TaskCompletionSource<object>(state); + task.ContinueWith( + t => { + if (t.IsFaulted) { + tcs.TrySetException(t.Exception.InnerExceptions); + } else if (t.IsCanceled) { + tcs.TrySetCanceled(); + } else { + tcs.TrySetResult(null); + } + + if (callback != null) { + callback(tcs.Task); + } + }, + CancellationToken.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + + return tcs.Task; + } + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs b/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs deleted file mode 100644 index 35f6c08..0000000 --- a/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs +++ /dev/null @@ -1,147 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="InMemoryTokenManager.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.ApplicationBlock { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using DotNetOpenAuth.OAuth.ChannelElements; - using DotNetOpenAuth.OAuth.Messages; - using DotNetOpenAuth.OpenId.Extensions.OAuth; - -#if SAMPLESONLY - /// <summary> - /// A token manager that only retains tokens in memory. - /// Meant for SHORT TERM USE TOKENS ONLY. - /// </summary> - /// <remarks> - /// A likely application of this class is for "Sign In With Twitter", - /// where the user only signs in without providing any authorization to access - /// Twitter APIs except to authenticate, since that access token is only useful once. - /// </remarks> - internal class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager { - private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); - - /// <summary> - /// Initializes a new instance of the <see cref="InMemoryTokenManager"/> class. - /// </summary> - /// <param name="consumerKey">The consumer key.</param> - /// <param name="consumerSecret">The consumer secret.</param> - public InMemoryTokenManager(string consumerKey, string consumerSecret) { - if (string.IsNullOrEmpty(consumerKey)) { - throw new ArgumentNullException("consumerKey"); - } - - this.ConsumerKey = consumerKey; - this.ConsumerSecret = consumerSecret; - } - - /// <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; } - - #region ITokenManager Members - - /// <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> - /// 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; - } - - /// <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> - /// 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(); - } - - #endregion - - #region IOpenIdOAuthTokenManager Members - - /// <summary> - /// Stores a new request token obtained over an OpenID request. - /// </summary> - /// <param name="consumerKey">The consumer key.</param> - /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param> - /// <remarks> - /// <para>The token secret is the empty string.</para> - /// <para>Tokens stored by this method should be short-lived to mitigate - /// possible security threats. Their lifetime should be sufficient for the - /// relying party to receive the positive authentication assertion and immediately - /// send a follow-up request for the access token.</para> - /// </remarks> - public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) { - this.tokensAndSecrets[authorization.RequestToken] = string.Empty; - } - - #endregion - } -#else -#error The InMemoryTokenManager class is only for samples as it forgets all tokens whenever the application restarts! You should implement IConsumerTokenManager in your own app that stores tokens in a persistent store (like a SQL database). -#endif -}
\ No newline at end of file diff --git a/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs index 013e66b..16f1c92 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs @@ -11,71 +11,80 @@ namespace DotNetOpenAuth.ApplicationBlock { using System.Globalization; using System.IO; using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Runtime.Serialization.Json; + using System.Threading; + using System.Threading.Tasks; using System.Web; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; + using Newtonsoft.Json.Linq; + /// <summary> /// A consumer capable of communicating with Twitter. /// </summary> - public static class TwitterConsumer { + public class TwitterConsumer : Consumer { /// <summary> /// The description of Twitter's OAuth protocol URIs for use with actually reading/writing /// a user's private Twitter data. /// </summary> - public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/authorize", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - AccessTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, - }; + public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription( + "https://api.twitter.com/oauth/request_token", + "https://api.twitter.com/oauth/authorize", + "https://api.twitter.com/oauth/access_token"); /// <summary> /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature. /// </summary> - public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/authenticate", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - AccessTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, - }; + public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = new ServiceProviderDescription( + "https://api.twitter.com/oauth/request_token", + "https://api.twitter.com/oauth/authenticate", + "https://api.twitter.com/oauth/access_token"); /// <summary> /// The URI to get a user's favorites. /// </summary> - private static readonly MessageReceivingEndpoint GetFavoritesEndpoint = new MessageReceivingEndpoint("http://twitter.com/favorites.xml", HttpDeliveryMethods.GetRequest); + private static readonly Uri GetFavoritesEndpoint = new Uri("http://twitter.com/favorites.xml"); /// <summary> /// The URI to get the data on the user's home page. /// </summary> - private static readonly MessageReceivingEndpoint GetFriendTimelineStatusEndpoint = new MessageReceivingEndpoint("http://twitter.com/statuses/friends_timeline.xml", HttpDeliveryMethods.GetRequest); + private static readonly Uri GetFriendTimelineStatusEndpoint = new Uri("https://api.twitter.com/1.1/statuses/home_timeline.json"); - private static readonly MessageReceivingEndpoint UpdateProfileBackgroundImageEndpoint = new MessageReceivingEndpoint("http://twitter.com/account/update_profile_background_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest); + private static readonly Uri UpdateProfileBackgroundImageEndpoint = new Uri("http://twitter.com/account/update_profile_background_image.xml"); - private static readonly MessageReceivingEndpoint UpdateProfileImageEndpoint = new MessageReceivingEndpoint("http://twitter.com/account/update_profile_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest); + private static readonly Uri UpdateProfileImageEndpoint = new Uri("http://twitter.com/account/update_profile_image.xml"); - private static readonly MessageReceivingEndpoint VerifyCredentialsEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/verify_credentials.xml", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest); + private static readonly Uri VerifyCredentialsEndpoint = new Uri("http://api.twitter.com/1/account/verify_credentials.xml"); /// <summary> - /// The consumer used for the Sign in to Twitter feature. + /// Initializes a new instance of the <see cref="TwitterConsumer"/> class. /// </summary> - private static WebConsumer signInConsumer; - - /// <summary> - /// The lock acquired to initialize the <see cref="signInConsumer"/> field. - /// </summary> - private static object signInConsumerInitLock = new object(); + public TwitterConsumer() { + this.ServiceProvider = ServiceDescription; + this.ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"]; + this.ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"]; + this.TemporaryCredentialStorage = HttpContext.Current != null + ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() + : new MemoryTemporaryCredentialStorage(); + } /// <summary> - /// Initializes static members of the <see cref="TwitterConsumer"/> class. + /// Initializes a new instance of the <see cref="TwitterConsumer"/> class. /// </summary> - static TwitterConsumer() { - // Twitter can't handle the Expect 100 Continue HTTP header. - ServicePointManager.FindServicePoint(GetFavoritesEndpoint.Location).Expect100Continue = false; + /// <param name="consumerKey">The consumer key.</param> + /// <param name="consumerSecret">The consumer secret.</param> + public TwitterConsumer(string consumerKey, string consumerSecret) + : this() { + this.ConsumerKey = consumerKey; + this.ConsumerSecret = consumerSecret; } /// <summary> @@ -88,137 +97,160 @@ namespace DotNetOpenAuth.ApplicationBlock { } } + public static Consumer CreateConsumer(bool forWeb = true) { + string consumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"]; + string consumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"]; + if (IsTwitterConsumerConfigured) { + ITemporaryCredentialStorage storage = forWeb ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() : new MemoryTemporaryCredentialStorage(); + return new Consumer(consumerKey, consumerSecret, ServiceDescription, storage) { + HostFactories = new TwitterHostFactories(), + }; + } else { + throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings."); + } + } + /// <summary> - /// Gets the consumer to use for the Sign in to Twitter feature. + /// Prepares a redirect that will send the user to Twitter to sign in. /// </summary> - /// <value>The twitter sign in.</value> - private static WebConsumer TwitterSignIn { - get { - if (signInConsumer == null) { - lock (signInConsumerInitLock) { - if (signInConsumer == null) { - signInConsumer = new WebConsumer(SignInWithTwitterServiceDescription, ShortTermUserSessionTokenManager); - } - } - } + /// <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The redirect message. + /// </returns> + public static async Task<Uri> StartSignInWithTwitterAsync(bool forceNewLogin = false, CancellationToken cancellationToken = default(CancellationToken)) { + var redirectParameters = new Dictionary<string, string>(); + if (forceNewLogin) { + redirectParameters["force_login"] = "true"; + } + Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_"); + + var consumer = CreateConsumer(); + consumer.ServiceProvider = SignInWithTwitterServiceDescription; + Uri redirectUrl = await consumer.RequestUserAuthorizationAsync(callback, cancellationToken: cancellationToken); + return redirectUrl; + } - return signInConsumer; + /// <summary> + /// Checks the incoming web request to see if it carries a Twitter authentication response, + /// and provides the user's Twitter screen name and unique id if available. + /// </summary> + /// <param name="completeUrl">The URL that came back from the service provider to complete the authorization.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A tuple with the screen name and Twitter unique user ID if successful; otherwise <c>null</c>. + /// </returns> + public static async Task<Tuple<string, int>> TryFinishSignInWithTwitterAsync(Uri completeUrl = null, CancellationToken cancellationToken = default(CancellationToken)) { + var consumer = CreateConsumer(); + consumer.ServiceProvider = SignInWithTwitterServiceDescription; + var response = await consumer.ProcessUserAuthorizationAsync(completeUrl ?? HttpContext.Current.Request.Url, cancellationToken: cancellationToken); + if (response == null) { + return null; } + + string screenName = response.ExtraData["screen_name"]; + int userId = int.Parse(response.ExtraData["user_id"]); + return Tuple.Create(screenName, userId); } - private static InMemoryTokenManager ShortTermUserSessionTokenManager { - get { - var store = HttpContext.Current.Session; - var tokenManager = (InMemoryTokenManager)store["TwitterShortTermUserSessionTokenManager"]; - if (tokenManager == null) { - string consumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"]; - string consumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"]; - if (IsTwitterConsumerConfigured) { - tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret); - store["TwitterShortTermUserSessionTokenManager"] = tokenManager; - } else { - throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings."); - } - } + public async Task<JArray> GetUpdatesAsync(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + if (String.IsNullOrEmpty(accessToken.Token)) { + throw new ArgumentNullException("accessToken.Token"); + } - return tokenManager; + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (var response = await httpClient.GetAsync(GetFriendTimelineStatusEndpoint, cancellationToken)) { + response.EnsureSuccessStatusCode(); + string jsonString = await response.Content.ReadAsStringAsync(); + var json = JArray.Parse(jsonString); + return json; + } } } - public static XDocument GetUpdates(ConsumerBase twitter, string accessToken) { - IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFriendTimelineStatusEndpoint, accessToken); - return XDocument.Load(XmlReader.Create(response.GetResponseReader())); - } + public async Task<XDocument> GetFavorites(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + if (String.IsNullOrEmpty(accessToken.Token)) { + throw new ArgumentNullException("accessToken.Token"); + } - public static XDocument GetFavorites(ConsumerBase twitter, string accessToken) { - IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFavoritesEndpoint, accessToken); - return XDocument.Load(XmlReader.Create(response.GetResponseReader())); + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (HttpResponseMessage response = await httpClient.GetAsync(GetFavoritesEndpoint, cancellationToken)) { + response.EnsureSuccessStatusCode(); + return XDocument.Parse(await response.Content.ReadAsStringAsync()); + } + } } - public static XDocument UpdateProfileBackgroundImage(ConsumerBase twitter, string accessToken, string image, bool tile) { - var parts = new[] { - MultipartPostPart.CreateFormFilePart("image", image, "image/" + Path.GetExtension(image).Substring(1).ToLowerInvariant()), - MultipartPostPart.CreateFormPart("tile", tile.ToString().ToLowerInvariant()), - }; - HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileBackgroundImageEndpoint, accessToken, parts); - request.ServicePoint.Expect100Continue = false; - IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request); - string responseString = response.GetResponseReader().ReadToEnd(); - return XDocument.Parse(responseString); + public async Task<XDocument> UpdateProfileBackgroundImageAsync(AccessToken accessToken, string image, bool tile, CancellationToken cancellationToken) { + var imageAttachment = new StreamContent(File.OpenRead(image)); + imageAttachment.Headers.ContentType = new MediaTypeHeaderValue("image/" + Path.GetExtension(image).Substring(1).ToLowerInvariant()); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UpdateProfileBackgroundImageEndpoint); + var content = new MultipartFormDataContent(); + content.Add(imageAttachment, "image"); + content.Add(new StringContent(tile.ToString().ToLowerInvariant()), "tile"); + request.Content = content; + request.Headers.ExpectContinue = false; + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken)) { + response.EnsureSuccessStatusCode(); + string responseString = await response.Content.ReadAsStringAsync(); + return XDocument.Parse(responseString); + } + } } - public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, string pathToImage) { + public Task<XDocument> UpdateProfileImageAsync(AccessToken accessToken, string pathToImage, CancellationToken cancellationToken = default(CancellationToken)) { string contentType = "image/" + Path.GetExtension(pathToImage).Substring(1).ToLowerInvariant(); - return UpdateProfileImage(twitter, accessToken, File.OpenRead(pathToImage), contentType); + return this.UpdateProfileImageAsync(accessToken, File.OpenRead(pathToImage), contentType, cancellationToken); } - public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, Stream image, string contentType) { - var parts = new[] { - MultipartPostPart.CreateFormFilePart("image", "twitterPhoto", contentType, image), - }; - HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileImageEndpoint, accessToken, parts); - IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request); - string responseString = response.GetResponseReader().ReadToEnd(); - return XDocument.Parse(responseString); + public async Task<XDocument> UpdateProfileImageAsync(AccessToken accessToken, Stream image, string contentType, CancellationToken cancellationToken = default(CancellationToken)) { + var imageAttachment = new StreamContent(image); + imageAttachment.Headers.ContentType = new MediaTypeHeaderValue(contentType); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UpdateProfileImageEndpoint); + var content = new MultipartFormDataContent(); + content.Add(imageAttachment, "image", "twitterPhoto"); + request.Content = content; + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken)) { + response.EnsureSuccessStatusCode(); + string responseString = await response.Content.ReadAsStringAsync(); + return XDocument.Parse(responseString); + } + } } - public static XDocument VerifyCredentials(ConsumerBase twitter, string accessToken) { - IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(VerifyCredentialsEndpoint, accessToken); - return XDocument.Load(XmlReader.Create(response.GetResponseReader())); + public async Task<XDocument> VerifyCredentialsAsync(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (var response = await httpClient.GetAsync(VerifyCredentialsEndpoint, cancellationToken)) { + response.EnsureSuccessStatusCode(); + using (var stream = await response.Content.ReadAsStreamAsync()) { + return XDocument.Load(XmlReader.Create(stream)); + } + } + } } - public static string GetUsername(ConsumerBase twitter, string accessToken) { - XDocument xml = VerifyCredentials(twitter, accessToken); + public async Task<string> GetUsername(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + XDocument xml = await this.VerifyCredentialsAsync(accessToken, cancellationToken); XPathNavigator nav = xml.CreateNavigator(); return nav.SelectSingleNode("/user/screen_name").Value; } - /// <summary> - /// Prepares a redirect that will send the user to Twitter to sign in. - /// </summary> - /// <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param> - /// <returns>The redirect message.</returns> - /// <remarks> - /// Call <see cref="OutgoingWebResponse.Send"/> or - /// <c>return StartSignInWithTwitter().<see cref="MessagingUtilities.AsActionResult">AsActionResult()</see></c> - /// to actually perform the redirect. - /// </remarks> - public static OutgoingWebResponse StartSignInWithTwitter(bool forceNewLogin) { - var redirectParameters = new Dictionary<string, string>(); - if (forceNewLogin) { - redirectParameters["force_login"] = "true"; - } - Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_"); - var request = TwitterSignIn.PrepareRequestUserAuthorization(callback, null, redirectParameters); - return TwitterSignIn.Channel.PrepareResponse(request); - } + private class TwitterHostFactories : IHostFactories { + private static readonly IHostFactories underlyingFactories = new DefaultOAuthHostFactories(); - /// <summary> - /// Checks the incoming web request to see if it carries a Twitter authentication response, - /// and provides the user's Twitter screen name and unique id if available. - /// </summary> - /// <param name="screenName">The user's Twitter screen name.</param> - /// <param name="userId">The user's Twitter unique user ID.</param> - /// <returns> - /// A value indicating whether Twitter authentication was successful; - /// otherwise <c>false</c> to indicate that no Twitter response was present. - /// </returns> - public static bool TryFinishSignInWithTwitter(out string screenName, out int userId) { - screenName = null; - userId = 0; - var response = TwitterSignIn.ProcessUserAuthorization(); - if (response == null) { - return false; + public HttpMessageHandler CreateHttpMessageHandler() { + return new WebRequestHandler(); } - screenName = response.ExtraData["screen_name"]; - userId = int.Parse(response.ExtraData["user_id"]); + public HttpClient CreateHttpClient(HttpMessageHandler handler = null) { + var client = underlyingFactories.CreateHttpClient(handler); - // If we were going to make this LOOK like OpenID even though it isn't, - // this seems like a reasonable, secure claimed id to allow the user to assume. - OpenId.Identifier fake_claimed_id = string.Format(CultureInfo.InvariantCulture, "http://twitter.com/{0}#{1}", screenName, userId); - - return true; + // Twitter can't handle the Expect 100 Continue HTTP header. + client.DefaultRequestHeaders.ExpectContinue = false; + return client; + } } } } diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Util.cs b/samples/DotNetOpenAuth.ApplicationBlock/Util.cs index 0bec372..3e0795a 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/Util.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/Util.cs @@ -13,33 +13,6 @@ internal static readonly Random NonCryptoRandomDataGenerator = new Random(); /// <summary> - /// Sets the channel's outgoing HTTP requests to use default network credentials. - /// </summary> - /// <param name="channel">The channel to modify.</param> - public static void UseDefaultNetworkCredentialsOnOutgoingHttpRequests(this Channel channel) - { - Debug.Assert(!(channel.WebRequestHandler is WrappingWebRequestHandler), "Wrapping an already wrapped web request handler. This is legal, but highly suspect of a bug as you don't want to wrap the same channel repeatedly to apply the same effect."); - AddOutgoingHttpRequestTransform(channel, http => http.Credentials = CredentialCache.DefaultNetworkCredentials); - } - - /// <summary> - /// Adds some action to any outgoing HTTP request on this channel. - /// </summary> - /// <param name="channel">The channel's whose outgoing HTTP requests should be modified.</param> - /// <param name="action">The action to perform on outgoing HTTP requests.</param> - internal static void AddOutgoingHttpRequestTransform(this Channel channel, Action<HttpWebRequest> action) { - if (channel == null) { - throw new ArgumentNullException("channel"); - } - - if (action == null) { - throw new ArgumentNullException("action"); - } - - channel.WebRequestHandler = new WrappingWebRequestHandler(channel.WebRequestHandler, action); - } - - /// <summary> /// Enumerates through the individual set bits in a flag enum. /// </summary> /// <param name="flags">The flags enum value.</param> @@ -106,154 +79,5 @@ return totalCopiedBytes; } - - /// <summary> - /// Wraps some instance of a web request handler in order to perform some extra operation on all - /// outgoing HTTP requests. - /// </summary> - private class WrappingWebRequestHandler : IDirectWebRequestHandler - { - /// <summary> - /// The handler being wrapped. - /// </summary> - private readonly IDirectWebRequestHandler wrappedHandler; - - /// <summary> - /// The action to perform on outgoing HTTP requests. - /// </summary> - private readonly Action<HttpWebRequest> action; - - /// <summary> - /// Initializes a new instance of the <see cref="WrappingWebRequestHandler"/> class. - /// </summary> - /// <param name="wrappedHandler">The HTTP handler to wrap.</param> - /// <param name="action">The action to perform on outgoing HTTP requests.</param> - internal WrappingWebRequestHandler(IDirectWebRequestHandler wrappedHandler, Action<HttpWebRequest> action) - { - if (wrappedHandler == null) { - throw new ArgumentNullException("wrappedHandler"); - } - - if (action == null) { - throw new ArgumentNullException("action"); - } - - this.wrappedHandler = wrappedHandler; - this.action = action; - } - - #region Implementation of IDirectWebRequestHandler - - /// <summary> - /// Determines whether this instance can support the specified options. - /// </summary> - /// <param name="options">The set of options that might be given in a subsequent web request.</param> - /// <returns> - /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. - /// </returns> - public bool CanSupport(DirectWebRequestOptions options) - { - return this.wrappedHandler.CanSupport(options); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request) - { - this.action(request); - return this.wrappedHandler.GetRequestStream(request); - } - - /// <summary> - /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns> - /// The stream the caller should write out the entity data to. - /// </returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> - /// and any other appropriate properties <i>before</i> calling this method. - /// Callers <i>must</i> close and dispose of the request stream when they are done - /// writing to it to avoid taking up the connection too long and causing long waits on - /// subsequent requests.</para> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch.</para> - /// </remarks> - public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) - { - this.action(request); - return this.wrappedHandler.GetRequestStream(request, options); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - public IncomingWebResponse GetResponse(HttpWebRequest request) - { - // If the request has an entity, the action would have already been processed in GetRequestStream. - if (request.Method == "GET") - { - this.action(request); - } - - return this.wrappedHandler.GetResponse(request); - } - - /// <summary> - /// Processes an <see cref="HttpWebRequest"/> and converts the - /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. - /// </summary> - /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> - /// <param name="options">The options to apply to this web request.</param> - /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns> - /// <exception cref="ProtocolException">Thrown for any network error.</exception> - /// <remarks> - /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a - /// <see cref="ProtocolException"/> to abstract away the transport and provide - /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> - /// value, if set, should be Closed before throwing.</para> - /// </remarks> - public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) - { - // If the request has an entity, the action would have already been processed in GetRequestStream. - if (request.Method == "GET") { - this.action(request); - } - - return this.wrappedHandler.GetResponse(request, options); - } - - #endregion - } } } diff --git a/samples/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs index bbeb861..1dff5b6 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs @@ -7,55 +7,45 @@ namespace DotNetOpenAuth.ApplicationBlock { using System; using System.Collections.Generic; + using System.Configuration; using System.Linq; using System.Net; using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; - public static class YammerConsumer { + public class YammerConsumer : Consumer { /// <summary> /// The Consumer to use for accessing Google data APIs. /// </summary> - public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.yammer.com/oauth/request_token", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest), - UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.yammer.com/oauth/authorize", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), - AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.yammer.com/oauth/access_token", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest), - TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new PlaintextSigningBindingElement() }, - ProtocolVersion = ProtocolVersion.V10, - }; + public static readonly ServiceProviderDescription ServiceDescription = + new ServiceProviderDescription( + "https://www.yammer.com/oauth/request_token", + "https://www.yammer.com/oauth/authorize", + "https://www.yammer.com/oauth/access_token"); - public static DesktopConsumer CreateConsumer(IConsumerTokenManager tokenManager) { - return new DesktopConsumer(ServiceDescription, tokenManager); + public YammerConsumer() { + this.ServiceProvider = ServiceDescription; + this.ConsumerKey = ConfigurationManager.AppSettings["YammerConsumerKey"]; + this.ConsumerSecret = ConfigurationManager.AppSettings["YammerConsumerSecret"]; + this.TemporaryCredentialStorage = HttpContext.Current != null + ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() + : new MemoryTemporaryCredentialStorage(); } - public static Uri PrepareRequestAuthorization(DesktopConsumer consumer, out string requestToken) { - if (consumer == null) { - throw new ArgumentNullException("consumer"); + /// <summary> + /// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file. + /// </summary> + public static bool IsConsumerConfigured { + get { + return !string.IsNullOrEmpty(ConfigurationManager.AppSettings["yammerConsumerKey"]) && + !string.IsNullOrEmpty(ConfigurationManager.AppSettings["yammerConsumerSecret"]); } - - Uri authorizationUrl = consumer.RequestUserAuthorization(null, null, out requestToken); - return authorizationUrl; - } - - public static AuthorizedTokenResponse CompleteAuthorization(DesktopConsumer consumer, string requestToken, string userCode) { - // Because Yammer has a proprietary callback_token parameter, and it's passed - // with the message that specifically bans extra arguments being passed, we have - // to cheat by adding the data to the URL itself here. - var customServiceDescription = new ServiceProviderDescription { - RequestTokenEndpoint = ServiceDescription.RequestTokenEndpoint, - UserAuthorizationEndpoint = ServiceDescription.UserAuthorizationEndpoint, - AccessTokenEndpoint = new MessageReceivingEndpoint(ServiceDescription.AccessTokenEndpoint.Location.AbsoluteUri + "?oauth_verifier=" + Uri.EscapeDataString(userCode), HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest), - TamperProtectionElements = ServiceDescription.TamperProtectionElements, - ProtocolVersion = ProtocolVersion.V10, - }; - - // To use a custom service description we also must create a new WebConsumer. - var customConsumer = new DesktopConsumer(customServiceDescription, consumer.TokenManager); - var response = customConsumer.ProcessUserAuthorization(requestToken, userCode); - return response; } } } diff --git a/samples/DotNetOpenAuth.ApplicationBlock/packages.config b/samples/DotNetOpenAuth.ApplicationBlock/packages.config new file mode 100644 index 0000000..d7219c5 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" /> +</packages>
\ No newline at end of file |