diff options
-rw-r--r-- | samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj | 3 | ||||
-rw-r--r-- | samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs | 122 | ||||
-rw-r--r-- | samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs | 118 | ||||
-rw-r--r-- | samples/OAuthConsumer/App_Code/InMemoryTokenManager.cs | 54 | ||||
-rw-r--r-- | samples/OAuthConsumer/Default.aspx | 1 | ||||
-rw-r--r-- | samples/OAuthConsumer/SampleWcf.aspx.cs | 1 | ||||
-rw-r--r-- | samples/OAuthConsumer/SignInWithTwitter.aspx | 38 | ||||
-rw-r--r-- | samples/OAuthConsumer/SignInWithTwitter.aspx.cs | 37 | ||||
-rw-r--r-- | samples/OAuthConsumer/images/Sign-in-with-Twitter-darker.png | bin | 0 -> 2370 bytes |
9 files changed, 319 insertions, 55 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj index 28c4ff4..eab27db 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj +++ b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj @@ -65,9 +65,11 @@ </PropertyGroup> <ItemGroup> <Reference Include="System" /> + <Reference Include="System.configuration" /> <Reference Include="System.Core"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> + <Reference Include="System.Web" /> <Reference Include="System.Xml.Linq"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> @@ -82,6 +84,7 @@ <Compile Include="CustomExtensions\AcmeRequest.cs" /> <Compile Include="CustomExtensions\AcmeResponse.cs" /> <Compile Include="GoogleConsumer.cs" /> + <Compile Include="InMemoryTokenManager.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TwitterConsumer.cs" /> <Compile Include="Util.cs" /> diff --git a/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs b/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs new file mode 100644 index 0000000..e83817a --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------- +// <copyright file="InMemoryTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + /// <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> + public class InMemoryTokenManager : IConsumerTokenManager { + 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 + } +}
\ No newline at end of file diff --git a/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs index 29973c2..2e160f4 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs @@ -7,10 +7,13 @@ namespace DotNetOpenAuth.ApplicationBlock { using System; using System.Collections.Generic; + using System.Configuration; using System.IO; using System.Net; + using System.Web; using System.Xml; using System.Xml.Linq; + using System.Xml.XPath; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; @@ -20,7 +23,8 @@ namespace DotNetOpenAuth.ApplicationBlock { /// </summary> public static class TwitterConsumer { /// <summary> - /// The description of Twitter's OAuth protocol URIs. + /// 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), @@ -30,6 +34,16 @@ namespace DotNetOpenAuth.ApplicationBlock { }; /// <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() }, + }; + + /// <summary> /// The URI to get a user's favorites. /// </summary> private static readonly MessageReceivingEndpoint GetFavoritesEndpoint = new MessageReceivingEndpoint("http://twitter.com/favorites.xml", HttpDeliveryMethods.GetRequest); @@ -43,6 +57,45 @@ namespace DotNetOpenAuth.ApplicationBlock { private static readonly MessageReceivingEndpoint UpdateProfileImageEndpoint = new MessageReceivingEndpoint("http://twitter.com/account/update_profile_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest); + private static readonly MessageReceivingEndpoint VerifyCredentialsEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/verify_credentials.xml", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest); + + 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."); + } + } + + return tokenManager; + } + } + + private static WebConsumer signInConsumer; + + private static object signInConsumerInitLock = new object(); + + private static WebConsumer TwitterSignIn { + get { + if (signInConsumer == null) { + lock (signInConsumerInitLock) { + if (signInConsumer == null) { + signInConsumer = new WebConsumer(SignInWithTwitterServiceDescription, ShortTermUserSessionTokenManager); + } + } + } + + return signInConsumer; + } + } + /// <summary> /// Initializes static members of the <see cref="TwitterConsumer"/> class. /// </summary> @@ -51,6 +104,13 @@ namespace DotNetOpenAuth.ApplicationBlock { ServicePointManager.FindServicePoint(GetFavoritesEndpoint.Location).Expect100Continue = false; } + public static bool IsTwitterConsumerConfigured { + get { + return !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerKey"]) && + !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerSecret"]); + } + } + public static XDocument GetUpdates(ConsumerBase twitter, string accessToken) { IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFriendTimelineStatusEndpoint, accessToken); return XDocument.Load(XmlReader.Create(response.GetResponseReader())); @@ -87,5 +147,61 @@ namespace DotNetOpenAuth.ApplicationBlock { string responseString = response.GetResponseReader().ReadToEnd(); 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 static string GetUsername(ConsumerBase twitter, string accessToken) { + XDocument xml = VerifyCredentials(twitter, accessToken); + 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); + } + + /// <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; + } + + XDocument xml = VerifyCredentials(TwitterSignIn, response.AccessToken); + XPathNavigator nav = xml.CreateNavigator(); + screenName = nav.SelectSingleNode("/user/screen_name").Value; + userId = int.Parse(nav.SelectSingleNode("/user/id").Value); + return true; + } } } diff --git a/samples/OAuthConsumer/App_Code/InMemoryTokenManager.cs b/samples/OAuthConsumer/App_Code/InMemoryTokenManager.cs deleted file mode 100644 index 120f00a..0000000 --- a/samples/OAuthConsumer/App_Code/InMemoryTokenManager.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="InMemoryTokenManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using DotNetOpenAuth.OAuth.ChannelElements; -using DotNetOpenAuth.OAuth.Messages; - -public class InMemoryTokenManager : IConsumerTokenManager { - private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); - - public InMemoryTokenManager(string consumerKey, string consumerSecret) { - if (String.IsNullOrEmpty(consumerKey)) { - throw new ArgumentNullException("consumerKey"); - } - - this.ConsumerKey = consumerKey; - this.ConsumerSecret = consumerSecret; - } - - public string ConsumerKey { get; private set; } - - public string ConsumerSecret { get; private set; } - - #region ITokenManager Members - - public string GetTokenSecret(string token) { - return this.tokensAndSecrets[token]; - } - - public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { - this.tokensAndSecrets[response.Token] = response.TokenSecret; - } - - 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 -} diff --git a/samples/OAuthConsumer/Default.aspx b/samples/OAuthConsumer/Default.aspx index aa4ef79..c952877 100644 --- a/samples/OAuthConsumer/Default.aspx +++ b/samples/OAuthConsumer/Default.aspx @@ -8,6 +8,7 @@ <ul> <li><a href="GoogleAddressBook.aspx">Download your Gmail address book</a></li> <li><a href="Twitter.aspx">Get your Twitter updates</a></li> + <li><a href="SignInWithTwitter.aspx">Sign In With Twitter</a></li> <li><a href="SampleWcf.aspx">Interop with Service Provider sample using WCF w/ OAuth</a></li> </ul> </asp:Content> diff --git a/samples/OAuthConsumer/SampleWcf.aspx.cs b/samples/OAuthConsumer/SampleWcf.aspx.cs index 7572dd8..d1af6a1 100644 --- a/samples/OAuthConsumer/SampleWcf.aspx.cs +++ b/samples/OAuthConsumer/SampleWcf.aspx.cs @@ -8,6 +8,7 @@ using System.ServiceModel.Channels; using System.ServiceModel.Security; using System.Web.UI.WebControls; using DotNetOpenAuth; +using DotNetOpenAuth.ApplicationBlock; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; diff --git a/samples/OAuthConsumer/SignInWithTwitter.aspx b/samples/OAuthConsumer/SignInWithTwitter.aspx new file mode 100644 index 0000000..b1dd606 --- /dev/null +++ b/samples/OAuthConsumer/SignInWithTwitter.aspx @@ -0,0 +1,38 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SignInWithTwitter.aspx.cs" + Inherits="SignInWithTwitter" %> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head runat="server"> + <title>Sign-in with Twitter</title> +</head> +<body> + <form id="form1" runat="server"> + <div> + <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> + <asp:View ID="View1" runat="server"> + <h2> + Twitter setup</h2> + <p> + A Twitter client app must be endorsed by a Twitter user. + </p> + <ol> + <li><a target="_blank" href="https://twitter.com/oauth_clients">Visit Twitter and create + a client app</a>. </li> + <li>Modify your web.config file to include your consumer key and consumer secret.</li> + </ol> + </asp:View> + <asp:View ID="View2" runat="server"> + <asp:ImageButton ImageUrl="~/images/Sign-in-with-Twitter-darker.png" runat="server" + AlternateText="Sign In With Twitter" ID="signInButton" OnClick="signInButton_Click" /> + <asp:CheckBox Text="force re-login" runat="server" ID="forceLoginCheckbox" /> + <br /> + <asp:Panel runat="server" ID="loggedInPanel" Visible="false"> + Now logged in as + <asp:Label Text="[name]" runat="server" ID="loggedInName" /> + </asp:Panel> + </asp:View> + </asp:MultiView> + </form> +</body> +</html> diff --git a/samples/OAuthConsumer/SignInWithTwitter.aspx.cs b/samples/OAuthConsumer/SignInWithTwitter.aspx.cs new file mode 100644 index 0000000..688471a --- /dev/null +++ b/samples/OAuthConsumer/SignInWithTwitter.aspx.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Xml.Linq; +using System.Xml.XPath; +using DotNetOpenAuth.ApplicationBlock; +using DotNetOpenAuth.OAuth; + +public partial class SignInWithTwitter : System.Web.UI.Page { + protected void Page_Load(object sender, EventArgs e) { + if (TwitterConsumer.IsTwitterConsumerConfigured) { + MultiView1.ActiveViewIndex = 1; + + if (!IsPostBack) { + string screenName; + int userId; + if (TwitterConsumer.TryFinishSignInWithTwitter(out screenName, out userId)) { + loggedInPanel.Visible = true; + loggedInName.Text = screenName; + + // In a real app, the Twitter username would likely be used + // to log the user into the application. + ////FormsAuthentication.RedirectFromLoginPage(screenName, false); + } + } + } + } + + protected void signInButton_Click(object sender, ImageClickEventArgs e) { + TwitterConsumer.StartSignInWithTwitter(forceLoginCheckbox.Checked).Send(); + } +}
\ No newline at end of file diff --git a/samples/OAuthConsumer/images/Sign-in-with-Twitter-darker.png b/samples/OAuthConsumer/images/Sign-in-with-Twitter-darker.png Binary files differnew file mode 100644 index 0000000..746b6b9 --- /dev/null +++ b/samples/OAuthConsumer/images/Sign-in-with-Twitter-darker.png |