summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Web/Clients
diff options
context:
space:
mode:
authorMicrosoft <aspnet@microsoft.com>2011-12-08 15:50:14 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2012-03-01 19:35:54 -0800
commit67e1a42ffe2ed7ac2bf99c703f17e4406cc35921 (patch)
treeb117701274fea4bb5cfb1342c7ba20605fbaf13d /src/DotNetOpenAuth.Web/Clients
parent8f4165ee515728aca3faaa26e8354a40612e85e4 (diff)
downloadDotNetOpenAuth-67e1a42ffe2ed7ac2bf99c703f17e4406cc35921.zip
DotNetOpenAuth-67e1a42ffe2ed7ac2bf99c703f17e4406cc35921.tar.gz
DotNetOpenAuth-67e1a42ffe2ed7ac2bf99c703f17e4406cc35921.tar.bz2
Add DotNetOpenAuth.Web and DotNetOpenAut.WebPages projects. Add commands to build nuget packages for DNOA.
Diffstat (limited to 'src/DotNetOpenAuth.Web/Clients')
-rw-r--r--src/DotNetOpenAuth.Web/Clients/AuthenticationClientCollection.cs21
-rw-r--r--src/DotNetOpenAuth.Web/Clients/DictionaryExtensions.cs46
-rw-r--r--src/DotNetOpenAuth.Web/Clients/IAuthenticationClient.cs33
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth/DotNetOpenAuthWebConsumer.cs47
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth/IOAuthWebWorker.cs14
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth/InMemoryOAuthTokenManager.cs128
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth/LinkedInClient.cs95
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth/OAuthClient.cs116
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth/TwitterClient.cs94
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookClient.cs103
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookGraph.cs34
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/JsonHelper.cs20
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2AccessTokenData.cs20
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2Client.cs133
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveClient.cs107
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveUserData.cs34
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OpenID/AxKnownAttributes.cs12
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OpenID/GoogleOpenIdClient.cs55
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OpenID/OpenIDClient.cs141
-rw-r--r--src/DotNetOpenAuth.Web/Clients/OpenID/YahooOpenIdClient.cs48
-rw-r--r--src/DotNetOpenAuth.Web/Clients/UriHelper.cs148
21 files changed, 1449 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Web/Clients/AuthenticationClientCollection.cs b/src/DotNetOpenAuth.Web/Clients/AuthenticationClientCollection.cs
new file mode 100644
index 0000000..8f730e1
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/AuthenticationClientCollection.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.ObjectModel;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// A collection to store instances of IAuthenticationClient by keying off ProviderName.
+ /// </summary>
+ internal sealed class AuthenticationClientCollection : KeyedCollection<string, IAuthenticationClient>
+ {
+ public AuthenticationClientCollection()
+ : base(StringComparer.OrdinalIgnoreCase)
+ {
+ }
+
+ protected override string GetKeyForItem(IAuthenticationClient item)
+ {
+ return item.ProviderName;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/DictionaryExtensions.cs b/src/DotNetOpenAuth.Web/Clients/DictionaryExtensions.cs
new file mode 100644
index 0000000..517fc47
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/DictionaryExtensions.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Xml.Linq;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ internal static class DictionaryExtensions
+ {
+ /// <summary>
+ /// Adds a key/value pair to the specified dictionary if the value is not null or empty.
+ /// </summary>
+ /// <param name="dictionary">The dictionary.</param>
+ /// <param name="key">The key.</param>
+ /// <param name="value">The value.</param>
+ public static void AddItemIfNotEmpty(this IDictionary<string, string> dictionary, string key, string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ if (!String.IsNullOrEmpty(value))
+ {
+ dictionary[key] = value;
+ }
+ }
+
+ /// <summary>
+ /// Adds the value from an XDocument with the specified element name if it's not empty.
+ /// </summary>
+ /// <param name="dictionary">The dictionary.</param>
+ /// <param name="document">The document.</param>
+ /// <param name="elementName">Name of the element.</param>
+ public static void AddDataIfNotEmpty(
+ this Dictionary<string, string> dictionary,
+ XDocument document,
+ string elementName)
+ {
+ var element = document.Root.Element(elementName);
+ if (element != null)
+ {
+ dictionary.AddItemIfNotEmpty(elementName, element.Value);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/IAuthenticationClient.cs b/src/DotNetOpenAuth.Web/Clients/IAuthenticationClient.cs
new file mode 100644
index 0000000..526d775
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/IAuthenticationClient.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Web;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Represents a client which can authenticate users via an external website/provider.
+ /// </summary>
+ public interface IAuthenticationClient
+ {
+ /// <summary>
+ /// Gets the name of the provider which provides authentication service.
+ /// </summary>
+ string ProviderName { get; }
+
+ /// <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);
+
+ /// <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>
+ /// <returns>
+ /// An instance of <see cref="AuthenticationResult"/> containing authentication result.
+ /// </returns>
+ AuthenticationResult VerifyAuthentication(HttpContextBase context);
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth/DotNetOpenAuthWebConsumer.cs b/src/DotNetOpenAuth.Web/Clients/OAuth/DotNetOpenAuthWebConsumer.cs
new file mode 100644
index 0000000..b66f0b1
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth/DotNetOpenAuthWebConsumer.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.OAuth;
+using DotNetOpenAuth.OAuth.ChannelElements;
+using DotNetOpenAuth.OAuth.Messages;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ public class DotNetOpenAuthWebConsumer : IOAuthWebWorker
+ {
+ private readonly WebConsumer _webConsumer;
+
+ public DotNetOpenAuthWebConsumer(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager)
+ {
+ if (serviceDescription == null)
+ {
+ throw new ArgumentNullException("consumer");
+ }
+
+ if (tokenManager == null)
+ {
+ throw new ArgumentNullException("tokenManager");
+ }
+
+ _webConsumer = new WebConsumer(serviceDescription, tokenManager);
+ }
+
+ public void RequestAuthentication(Uri callback)
+ {
+ var redirectParameters = new Dictionary<string, string>() { { "force_login", "false" } };
+ UserAuthorizationRequest request = _webConsumer.PrepareRequestUserAuthorization(callback, null, redirectParameters);
+ _webConsumer.Channel.PrepareResponse(request).Send();
+ }
+
+ public AuthorizedTokenResponse ProcessUserAuthorization()
+ {
+ return _webConsumer.ProcessUserAuthorization();
+ }
+
+ public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint profileEndpoint, string accessToken)
+ {
+ return _webConsumer.PrepareAuthorizedRequest(profileEndpoint, accessToken);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth/IOAuthWebWorker.cs b/src/DotNetOpenAuth.Web/Clients/OAuth/IOAuthWebWorker.cs
new file mode 100644
index 0000000..7ffc6b4
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth/IOAuthWebWorker.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Net;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.OAuth.Messages;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ public interface IOAuthWebWorker
+ {
+ void RequestAuthentication(Uri callback);
+ AuthorizedTokenResponse ProcessUserAuthorization();
+ HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint profileEndpoint, string accessToken);
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth/InMemoryOAuthTokenManager.cs b/src/DotNetOpenAuth.Web/Clients/OAuth/InMemoryOAuthTokenManager.cs
new file mode 100644
index 0000000..0cc1fba
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth/InMemoryOAuthTokenManager.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using DotNetOpenAuth.OAuth.ChannelElements;
+using DotNetOpenAuth.OAuth.Messages;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// An implementation of IOAuthTokenManager which stores keys in memory.
+ /// </summary>
+ public sealed class InMemoryOAuthTokenManager : IConsumerTokenManager
+ {
+ private readonly Dictionary<string, string> _tokensAndSecrets = new Dictionary<string, string>();
+
+ /// <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)
+ {
+ if (consumerKey == null)
+ {
+ throw new ArgumentNullException("consumerKey");
+ }
+
+ if (consumerSecret == null)
+ {
+ throw new ArgumentNullException("consumerSecret");
+ }
+
+ ConsumerKey = consumerKey;
+ ConsumerSecret = consumerSecret;
+ }
+
+ /// <summary>
+ /// Gets the consumer key.
+ /// </summary>
+ public string ConsumerKey
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Gets the consumer secret.
+ /// </summary>
+ 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 _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)
+ {
+ _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)
+ {
+ _tokensAndSecrets.Remove(requestToken);
+ _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/src/DotNetOpenAuth.Web/Clients/OAuth/LinkedInClient.cs b/src/DotNetOpenAuth.Web/Clients/OAuth/LinkedInClient.cs
new file mode 100644
index 0000000..ddbc39f
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth/LinkedInClient.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Xml.Linq;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.OAuth;
+using DotNetOpenAuth.OAuth.ChannelElements;
+using DotNetOpenAuth.OAuth.Messages;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Represents LinkedIn authentication client.
+ /// </summary>
+ internal sealed class LinkedInClient : OAuthClient
+ {
+ 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() },
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LinkedInClient"/> class.
+ /// </summary>
+ /// <param name="consumerKey">The LinkedIn app's consumer key.</param>
+ /// <param name="consumerSecret">The LinkedIn app's consumer secret.</param>
+ [System.Diagnostics.CodeAnalysis.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) :
+ base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret)
+ {
+ }
+
+ /// <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>
+ /// <returns>
+ /// Authentication result.
+ /// </returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Microsoft.Design",
+ "CA1031:DoNotCatchGeneralExceptionTypes",
+ Justification = "We don't care if the request fails.")]
+ protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
+ {
+ // See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
+ const string profileRequestUrl = "http://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 = WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
+
+ try
+ {
+ using (WebResponse profileResponse = request.GetResponse())
+ {
+ using (Stream responseStream = profileResponse.GetResponseStream())
+ {
+ XDocument document = XDocument.Load(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("name", userName);
+ extraData.AddDataIfNotEmpty(document, "headline");
+ extraData.AddDataIfNotEmpty(document, "summary");
+ extraData.AddDataIfNotEmpty(document, "industry");
+
+ return new AuthenticationResult(
+ isSuccessful: true,
+ provider: ProviderName,
+ providerUserId: userId,
+ userName: userName,
+ extraData: extraData);
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ return new AuthenticationResult(exception);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth/OAuthClient.cs b/src/DotNetOpenAuth.Web/Clients/OAuth/OAuthClient.cs
new file mode 100644
index 0000000..d0d7751
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth/OAuthClient.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Web;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.OAuth;
+using DotNetOpenAuth.OAuth.ChannelElements;
+using DotNetOpenAuth.OAuth.Messages;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Represents base class for OAuth 1.0 clients
+ /// </summary>
+ public abstract class OAuthClient : IAuthenticationClient
+ {
+ /// <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="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>
+ protected OAuthClient(string providerName, ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) :
+ this(providerName, new DotNetOpenAuthWebConsumer(serviceDescription, tokenManager))
+ {
+ }
+
+ protected OAuthClient(string providerName, IOAuthWebWorker webWorker)
+ {
+ if (providerName == null)
+ {
+ throw new ArgumentNullException("providerName");
+ }
+
+ if (webWorker == null)
+ {
+ throw new ArgumentNullException("webWorker");
+ }
+
+ ProviderName = providerName;
+ WebWorker = webWorker;
+ }
+
+ /// <summary>
+ /// Gets the name of the provider which provides authentication service.
+ /// </summary>
+ public string ProviderName
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Gets the <see cref="OAuthWebConsumer"/> instance which handles constructing requests
+ /// to the OAuth providers.
+ /// </summary>
+ protected IOAuthWebWorker WebWorker
+ {
+ get;
+ private set;
+ }
+
+ /// <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="returnUrl">The return url after users have completed authenticating against external website.</param>
+ public virtual void RequestAuthentication(HttpContextBase context, Uri returnUrl)
+ {
+ if (returnUrl == null)
+ {
+ throw new ArgumentNullException("returnUrl");
+ }
+
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ Uri callback = returnUrl.StripQueryArgumentsWithPrefix("oauth_");
+ WebWorker.RequestAuthentication(callback);
+ }
+
+ /// <summary>
+ /// Check if authentication succeeded after user is redirected back from the service provider.
+ /// </summary>
+ /// <returns>
+ /// An instance of <see cref="AuthenticationResult"/> containing authentication result.
+ /// </returns>
+ public virtual AuthenticationResult VerifyAuthentication(HttpContextBase context)
+ {
+ AuthorizedTokenResponse response = WebWorker.ProcessUserAuthorization();
+ if (response == null)
+ {
+ return AuthenticationResult.Failed;
+ }
+
+ return VerifyAuthenticationCore(response);
+ }
+
+ /// <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>
+ /// <returns>Authentication result</returns>
+ protected abstract AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response);
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth/TwitterClient.cs b/src/DotNetOpenAuth.Web/Clients/OAuth/TwitterClient.cs
new file mode 100644
index 0000000..6d82607
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth/TwitterClient.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Xml.Linq;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.OAuth;
+using DotNetOpenAuth.OAuth.ChannelElements;
+using DotNetOpenAuth.OAuth.Messages;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Represents a Twitter client
+ /// </summary>
+ internal class TwitterClient : OAuthClient
+ {
+ /// <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("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>
+ /// Initializes a new instance of the <see cref="TwitterClient"/> class with the specified consumer key and consumer secret.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="consumerSecret">The consumer secret.</param>
+ [System.Diagnostics.CodeAnalysis.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) :
+ base("twitter", TwitterServiceDescription, consumerKey, consumerSecret)
+ {
+ }
+
+ /// <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>
+ /// <returns>
+ /// Authentication result
+ /// </returns>
+ [System.Diagnostics.CodeAnalysis.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;
+ string userId = response.ExtraData["user_id"];
+ string userName = response.ExtraData["screen_name"];
+
+ string profileRequestUrl = "http://api.twitter.com/1/users/show.xml?user_id=" + Uri.EscapeDataString(userId);
+ var profileEndpoint = new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest);
+ HttpWebRequest request = WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
+
+ var extraData = new Dictionary<string, string>();
+ try
+ {
+ using (WebResponse profileResponse = request.GetResponse())
+ {
+ using (Stream responseStream = profileResponse.GetResponseStream())
+ {
+ XDocument document = XDocument.Load(responseStream);
+ extraData.AddDataIfNotEmpty(document, "name");
+ extraData.AddDataIfNotEmpty(document, "location");
+ extraData.AddDataIfNotEmpty(document, "description");
+ extraData.AddDataIfNotEmpty(document, "url");
+ }
+ }
+ }
+ catch (Exception)
+ {
+ // At this point, the authentication is already successful.
+ // Here we are just trying to get additional data if we can.
+ // If it fails, no problem.
+ }
+
+ return new AuthenticationResult(
+ isSuccessful: true,
+ provider: ProviderName,
+ providerUserId: userId,
+ userName: userName,
+ extraData: extraData);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookClient.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookClient.cs
new file mode 100644
index 0000000..e9e0b80
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookClient.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Web;
+using DotNetOpenAuth.Web.Resources;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ internal sealed class FacebookClient : OAuth2Client
+ {
+ private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
+ private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
+
+ private readonly string _appId;
+ private readonly string _appSecret;
+
+ public FacebookClient(string appId, string appSecret)
+ : base("facebook")
+ {
+ if (String.IsNullOrEmpty(appId))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "appId"),
+ "appId");
+ }
+
+ if (String.IsNullOrEmpty("appSecret"))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "appSecret"),
+ "appSecret");
+ }
+
+ _appId = appId;
+ _appSecret = appSecret;
+ }
+
+ protected override Uri GetServiceLoginUrl(Uri returnUrl)
+ {
+ // Note: Facebook doesn't like us to url-encode the redirect_uri value
+ var builder = new UriBuilder(AuthorizationEndpoint);
+ builder.AppendQueryArguments(new Dictionary<string, string>
+ {
+ { "client_id", _appId },
+ { "redirect_uri", returnUrl.ToString() }
+ });
+ return builder.Uri;
+ }
+
+ protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
+ {
+ // Note: Facebook doesn't like us to url-encode the redirect_uri value
+ var builder = new UriBuilder(TokenEndpoint);
+ builder.AppendQueryArguments(new Dictionary<string, string>
+ {
+ { "client_id", _appId },
+ { "redirect_uri", returnUrl.ToString() },
+ { "client_secret", _appSecret },
+ { "code", authorizationCode }
+ });
+
+ using (WebClient client = new WebClient())
+ {
+ string data = client.DownloadString(builder.Uri);
+ if (String.IsNullOrEmpty(data))
+ {
+ return null;
+ }
+
+ var parsedQueryString = HttpUtility.ParseQueryString(data);
+ if (parsedQueryString != null)
+ {
+ return parsedQueryString["access_token"];
+ }
+ }
+ return null;
+ }
+
+ protected override IDictionary<string, string> GetUserData(string accessToken)
+ {
+ FacebookGraph graph;
+ var request = WebRequest.Create("https://graph.facebook.com/me?access_token=" + Uri.EscapeDataString(accessToken));
+ using (var response = request.GetResponse())
+ {
+ using (var responseStream = response.GetResponseStream())
+ {
+ graph = JsonHelper.Deserialize<FacebookGraph>(responseStream);
+ }
+ }
+
+ // this dictionary must contains
+ var userData = new Dictionary<string, string>();
+ userData.AddItemIfNotEmpty("id", graph.Id);
+ userData.AddItemIfNotEmpty("username", graph.Email);
+ userData.AddItemIfNotEmpty("name", graph.Name);
+ userData.AddItemIfNotEmpty("link", graph.Link == null ? null : graph.Link.ToString());
+ userData.AddItemIfNotEmpty("gender", graph.Gender);
+ userData.AddItemIfNotEmpty("birthday", graph.Birthday);
+ return userData;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookGraph.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookGraph.cs
new file mode 100644
index 0000000..43f31eb
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/FacebookGraph.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Contains data of a Facebook user.
+ /// </summary>
+ /// <remarks>
+ /// Technically, this class doesn't need to be public, but because we want to make it serializable
+ /// in medium trust, it has to be public.
+ /// </remarks>
+ [DataContract]
+ public class FacebookGraph
+ {
+ [DataMember(Name = "id")]
+ public string Id { get; set; }
+
+ [DataMember(Name = "email")]
+ public string Email { get; set; }
+
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ [DataMember(Name = "link")]
+ public Uri Link { get; set; }
+
+ [DataMember(Name = "gender")]
+ public string Gender { get; set; }
+
+ [DataMember(Name = "birthday")]
+ public string Birthday { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/JsonHelper.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/JsonHelper.cs
new file mode 100644
index 0000000..26cc61a
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/JsonHelper.cs
@@ -0,0 +1,20 @@
+using System;
+using System.IO;
+using System.Runtime.Serialization.Json;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ internal static class JsonHelper
+ {
+ public static T Deserialize<T>(Stream stream) where T : class
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException("stream");
+ }
+
+ var serializer = new DataContractJsonSerializer(typeof(T));
+ return (T)serializer.ReadObject(stream);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2AccessTokenData.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2AccessTokenData.cs
new file mode 100644
index 0000000..9649ad5
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2AccessTokenData.cs
@@ -0,0 +1,20 @@
+using System.Runtime.Serialization;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ [DataContract]
+ public class OAuth2AccessTokenData
+ {
+ [DataMember(Name = "access_token")]
+ public string AccessToken { get; set; }
+
+ [DataMember(Name = "refresh_token")]
+ public string RefreshToken { get; set; }
+
+ [DataMember(Name = "scope")]
+ public string Scope { get; set; }
+
+ [DataMember(Name = "token_type")]
+ public string TokenType { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2Client.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2Client.cs
new file mode 100644
index 0000000..9920065
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/OAuth2Client.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Web;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Represents the base class for OAuth 2.0 clients
+ /// </summary>
+ public abstract class OAuth2Client : IAuthenticationClient
+ {
+ private readonly string _providerName;
+ private Uri _returnUrl;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2Client"/> class with the specified provider name.
+ /// </summary>
+ /// <param name="providerName">Name of the provider.</param>
+ protected OAuth2Client(string providerName)
+ {
+ if (providerName == null)
+ {
+ throw new ArgumentNullException("providerName");
+ }
+
+ _providerName = providerName;
+ }
+
+ /// <summary>
+ /// Gets the name of the provider which provides authentication service.
+ /// </summary>
+ public string ProviderName
+ {
+ get { return _providerName; }
+ }
+
+ /// <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="returnUrl">The return url after users have completed authenticating against external website.</param>
+ public virtual void RequestAuthentication(HttpContextBase context, Uri returnUrl)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ if (returnUrl == null)
+ {
+ throw new ArgumentNullException("returnUrl");
+ }
+
+ _returnUrl = returnUrl;
+
+ string redirectUrl = GetServiceLoginUrl(returnUrl).ToString();
+ context.Response.Redirect(redirectUrl, endResponse: true);
+ }
+
+ /// <summary>
+ /// Check if authentication succeeded after user is redirected back from the service provider.
+ /// </summary>
+ /// <returns>
+ /// An instance of <see cref="AuthenticationResult"/> containing authentication result.
+ /// </returns>
+ public virtual AuthenticationResult VerifyAuthentication(HttpContextBase context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ string code = context.Request.QueryString["code"];
+ if (String.IsNullOrEmpty(code))
+ {
+ return AuthenticationResult.Failed;
+ }
+
+ string accessToken = QueryAccessToken(_returnUrl, code);
+ if (accessToken == null)
+ {
+ return AuthenticationResult.Failed;
+ }
+
+ IDictionary<string, string> userData = 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;
+ }
+
+ return new AuthenticationResult(
+ isSuccessful: true,
+ provider: ProviderName,
+ providerUserId: id,
+ userName: name,
+ extraData: userData);
+ }
+
+ /// <summary>
+ /// Gets the full url pointing to the login page for this client. The url should include the
+ /// specified return url so that when the login completes, user is redirected back to that url.
+ /// </summary>
+ /// <param name="returnUrl">The return URL.</param>
+ /// <returns></returns>
+ [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "Login is used more consistently in ASP.Net")]
+ protected abstract Uri GetServiceLoginUrl(Uri returnUrl);
+
+ /// <summary>
+ /// Queries the access token from the specified authorization code.
+ /// </summary>
+ /// <param name="returnUrl">The return URL.</param>
+ /// <param name="authorizationCode">The authorization code.</param>
+ /// <returns></returns>
+ protected abstract string QueryAccessToken(Uri returnUrl, string authorizationCode);
+
+ /// <summary>
+ /// Given the access token, gets the logged-in user's data. The returned dictionary must include
+ /// two keys 'id', and 'username'.
+ /// </summary>
+ /// <param name="accessToken">The access token of the current user.</param>
+ /// <returns>A dictionary contains key-value pairs of user data</returns>
+ protected abstract IDictionary<string, string> GetUserData(string accessToken);
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveClient.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveClient.cs
new file mode 100644
index 0000000..9184be6
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveClient.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ internal sealed class WindowsLiveClient : OAuth2Client
+ {
+ private const string TokenEndpoint = "https://oauth.live.com/token";
+ private const string AuthorizationEndpoint = "https://oauth.live.com/authorize";
+ private readonly string _appId;
+ private readonly string _appSecret;
+
+ public WindowsLiveClient(string appId, string appSecret)
+ : base("windowslive")
+ {
+ if (String.IsNullOrEmpty(appId))
+ {
+ throw new ArgumentNullException("appId");
+ }
+
+ if (String.IsNullOrEmpty("appSecret"))
+ {
+ throw new ArgumentNullException("appSecret");
+ }
+
+ _appId = appId;
+ _appSecret = appSecret;
+ }
+
+ protected override Uri GetServiceLoginUrl(Uri returnUrl)
+ {
+ var builder = new UriBuilder(AuthorizationEndpoint);
+ builder.AppendQueryArguments(new Dictionary<string, string>
+ {
+ { "client_id", _appId },
+ { "scope", "wl.basic" },
+ { "response_type", "code" },
+ { "redirect_uri", returnUrl.ToString() }
+ });
+
+ return builder.Uri;
+ }
+
+ protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
+ {
+ var builder = new StringBuilder();
+ builder.AppendFormat("client_id={0}", _appId);
+ builder.AppendFormat("&redirect_uri={0}", Uri.EscapeDataString(returnUrl.ToString()));
+ builder.AppendFormat("&client_secret={0}", _appSecret);
+ builder.AppendFormat("&code={0}", authorizationCode);
+ builder.Append("&grant_type=authorization_code");
+
+ WebRequest tokenRequest = WebRequest.Create(TokenEndpoint);
+ tokenRequest.ContentType = "application/x-www-form-urlencoded";
+ tokenRequest.ContentLength = builder.Length;
+ tokenRequest.Method = "POST";
+
+ using (Stream requestStream = tokenRequest.GetRequestStream())
+ {
+ var writer = new StreamWriter(requestStream);
+ writer.Write(builder.ToString());
+ writer.Flush();
+ }
+
+ HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();
+ if (tokenResponse.StatusCode == HttpStatusCode.OK)
+ {
+ using (Stream responseStream = tokenResponse.GetResponseStream())
+ {
+ var tokenData = JsonHelper.Deserialize<OAuth2AccessTokenData>(responseStream);
+ if (tokenData != null)
+ {
+ return tokenData.AccessToken;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected override IDictionary<string, string> GetUserData(string accessToken)
+ {
+ WindowsLiveUserData graph;
+ var request = WebRequest.Create("https://apis.live.net/v5.0/me?access_token=" + Uri.EscapeDataString(accessToken));
+ using (var response = request.GetResponse())
+ {
+ using (var responseStream = response.GetResponseStream())
+ {
+ graph = JsonHelper.Deserialize<WindowsLiveUserData>(responseStream);
+ }
+ }
+
+ var userData = new Dictionary<string, string>();
+ userData.AddItemIfNotEmpty("id", graph.Id);
+ userData.AddItemIfNotEmpty("username", graph.Name);
+ userData.AddItemIfNotEmpty("name", graph.Name);
+ userData.AddItemIfNotEmpty("link", graph.Link == null ? null : graph.Link.ToString());
+ userData.AddItemIfNotEmpty("gender", graph.Gender);
+ userData.AddItemIfNotEmpty("firstname", graph.FirstName);
+ userData.AddItemIfNotEmpty("lastname", graph.LastName);
+ return userData;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveUserData.cs b/src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveUserData.cs
new file mode 100644
index 0000000..cc6fa27
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OAuth2/WindowsLiveUserData.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Contains data of a Windows Live user.
+ /// </summary>
+ /// <remarks>
+ /// Technically, this class doesn't need to be public, but because we want to make it serializable
+ /// in medium trust, it has to be public.
+ /// </remarks>
+ [DataContract]
+ public class WindowsLiveUserData
+ {
+ [DataMember(Name = "id")]
+ public string Id { get; set; }
+
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ [DataMember(Name = "link")]
+ public Uri Link { get; set; }
+
+ [DataMember(Name = "gender")]
+ public string Gender { get; set; }
+
+ [DataMember(Name = "first_name")]
+ public string FirstName { get; set; }
+
+ [DataMember(Name = "last_name")]
+ public string LastName { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OpenID/AxKnownAttributes.cs b/src/DotNetOpenAuth.Web/Clients/OpenID/AxKnownAttributes.cs
new file mode 100644
index 0000000..1afcc65
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OpenID/AxKnownAttributes.cs
@@ -0,0 +1,12 @@
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Contains namespace values of common attributes used for Attribute Exchange extensions
+ /// </summary>
+ internal static class AxKnownAttributes
+ {
+ public const string FirstName = "http://axschema.org/namePerson/first";
+ public const string LastName = "http://axschema.org/namePerson/last";
+ public const string FullName = "http://axschema.org/namePerson";
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/OpenID/GoogleOpenIdClient.cs b/src/DotNetOpenAuth.Web/Clients/OpenID/GoogleOpenIdClient.cs
new file mode 100644
index 0000000..61b88ee
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OpenID/GoogleOpenIdClient.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+using DotNetOpenAuth.OpenId.RelyingParty;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Represents Google OpenID client.
+ /// </summary>
+ internal sealed class GoogleOpenIdClient : OpenIDClient
+ {
+ public GoogleOpenIdClient() :
+ base("google", "https://www.google.com/accounts/o8/id")
+ {
+ }
+
+ /// <summary>
+ /// Called just before the authentication request is sent to service provider.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
+ {
+ // Attribute Exchange extensions
+ var fetchRequest = new FetchRequest();
+ fetchRequest.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email, isRequired: true));
+ fetchRequest.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.HomeAddress.Country, isRequired: false));
+ fetchRequest.Attributes.Add(new AttributeRequest(AxKnownAttributes.FirstName, isRequired: false));
+ fetchRequest.Attributes.Add(new AttributeRequest(AxKnownAttributes.LastName, isRequired: false));
+
+ request.AddExtension(fetchRequest);
+ }
+
+ /// <summary>
+ /// Gets the extra data obtained from the response message when authentication is successful.
+ /// </summary>
+ /// <param name="response">The response message.</param>
+ /// <returns></returns>
+ protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
+ {
+ FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
+ if (fetchResponse != null)
+ {
+ var extraData = new Dictionary<string, string>();
+ extraData.AddItemIfNotEmpty("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
+ extraData.AddItemIfNotEmpty("country", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country));
+ extraData.AddItemIfNotEmpty("firstName", fetchResponse.GetAttributeValue(AxKnownAttributes.FirstName));
+ extraData.AddItemIfNotEmpty("lastName", fetchResponse.GetAttributeValue(AxKnownAttributes.LastName));
+
+ return extraData;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OpenID/OpenIDClient.cs b/src/DotNetOpenAuth.Web/Clients/OpenID/OpenIDClient.cs
new file mode 100644
index 0000000..f0f938e
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OpenID/OpenIDClient.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web;
+using DotNetOpenAuth.OpenId;
+using DotNetOpenAuth.OpenId.RelyingParty;
+using DotNetOpenAuth.Web.Resources;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ /// <summary>
+ /// Base classes for OpenID clients.
+ /// </summary>
+ internal class OpenIDClient : IAuthenticationClient
+ {
+ private readonly Identifier _providerIdentifier;
+ private readonly string _providerName;
+
+ private static OpenIdRelyingParty _openidRelayingParty =
+ new OpenIdRelyingParty(new StandardRelyingPartyApplicationStore());
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIDClient"/> class.
+ /// </summary>
+ /// <param name="providerName">Name of the provider.</param>
+ /// <param name="providerIdentifier">The provider identifier, which is the usually the login url of the specified provider.</param>
+ public OpenIDClient(string providerName, string providerIdentifier)
+ {
+ if (String.IsNullOrEmpty(providerIdentifier))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "providerIdentifier"),
+ "providerIdentifier");
+ }
+
+ if (String.IsNullOrEmpty(providerName))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "providerName"),
+ "providerName");
+ }
+
+ _providerName = providerName;
+ if (!Identifier.TryParse(providerIdentifier, out _providerIdentifier) || _providerIdentifier == null)
+ {
+ throw new ArgumentException(WebResources.OpenIDInvalidIdentifier, "providerIdentifier");
+ }
+ }
+
+ /// <summary>
+ /// Gets the name of the provider which provides authentication service.
+ /// </summary>
+ public string ProviderName
+ {
+ get
+ {
+ return _providerName;
+ }
+ }
+
+ /// <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="returnUrl">The return url after users have completed authenticating against external website.</param>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage(
+ "Microsoft.Usage",
+ "CA2234:PassSystemUriObjectsInsteadOfStrings",
+ Justification = "We don't have a Uri object handy.")]
+ public virtual void RequestAuthentication(HttpContextBase context, Uri returnUrl)
+ {
+ if (returnUrl == null)
+ {
+ throw new ArgumentNullException("returnUrl");
+ }
+
+ var realm = new Realm(returnUrl.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped));
+ IAuthenticationRequest request = _openidRelayingParty.CreateRequest(_providerIdentifier, realm, returnUrl);
+
+ // give subclasses a chance to modify request message, e.g. add extension attributes, etc.
+ OnBeforeSendingAuthenticationRequest(request);
+
+ request.RedirectToProvider();
+ }
+
+ /// <summary>
+ /// Called just before the authentication request is sent to service provider.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ protected virtual void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
+ {
+ }
+
+ /// <summary>
+ /// Check if authentication succeeded after user is redirected back from the service provider.
+ /// </summary>
+ /// <returns>
+ /// An instance of <see cref="AuthenticationResult"/> containing authentication result.
+ /// </returns>
+ public virtual AuthenticationResult VerifyAuthentication(HttpContextBase context)
+ {
+ IAuthenticationResponse response = _openidRelayingParty.GetResponse();
+ if (response == null)
+ {
+ throw new InvalidOperationException(WebResources.OpenIDFailedToGetResponse);
+ }
+
+ if (response.Status == AuthenticationStatus.Authenticated)
+ {
+ string id = response.ClaimedIdentifier;
+ string username;
+
+ Dictionary<string, string> extraData = GetExtraData(response) ?? new Dictionary<string, string>();
+ // 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;
+ }
+
+ return new AuthenticationResult(
+ true,
+ ProviderName,
+ id,
+ username,
+ extraData);
+ }
+
+ return AuthenticationResult.Failed;
+ }
+
+ /// <summary>
+ /// Gets the extra data obtained from the response message when authentication is successful.
+ /// </summary>
+ /// <param name="response">The response message.</param>
+ /// <returns></returns>
+ protected virtual Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
+ {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Clients/OpenID/YahooOpenIdClient.cs b/src/DotNetOpenAuth.Web/Clients/OpenID/YahooOpenIdClient.cs
new file mode 100644
index 0000000..2235a2b
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/OpenID/YahooOpenIdClient.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+using DotNetOpenAuth.OpenId.RelyingParty;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ internal sealed class YahooOpenIdClient : OpenIDClient
+ {
+ public YahooOpenIdClient() :
+ base("yahoo", "http://me.yahoo.com")
+ {
+ }
+
+ /// <summary>
+ /// Called just before the authentication request is sent to service provider.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
+ {
+ // Attribute Exchange extensions
+ var fetchRequest = new FetchRequest();
+ fetchRequest.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email, isRequired: true));
+ fetchRequest.Attributes.Add(new AttributeRequest(AxKnownAttributes.FullName, isRequired: false));
+
+ request.AddExtension(fetchRequest);
+ }
+
+ /// <summary>
+ /// Gets the extra data obtained from the response message when authentication is successful.
+ /// </summary>
+ /// <param name="response">The response message.</param>
+ /// <returns></returns>
+ protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
+ {
+ FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
+ if (fetchResponse != null)
+ {
+ var extraData = new Dictionary<string, string>();
+ extraData.AddItemIfNotEmpty("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
+ extraData.AddItemIfNotEmpty("fullName", fetchResponse.GetAttributeValue(AxKnownAttributes.FullName));
+
+ return extraData;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Clients/UriHelper.cs b/src/DotNetOpenAuth.Web/Clients/UriHelper.cs
new file mode 100644
index 0000000..f8f9723
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Clients/UriHelper.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Web;
+using DotNetOpenAuth.Messaging;
+
+namespace DotNetOpenAuth.Web.Clients
+{
+ internal static class UriHelper
+ {
+ /// <summary>
+ /// Attaches the query string '__provider' to an existing url. If the url already
+ /// contains the __provider query string, it overrides it with the specified provider name.
+ /// </summary>
+ /// <param name="url">The original url.</param>
+ /// <param name="providerName">Name of the provider.</param>
+ /// <returns>The new url with the provider name query string attached</returns>
+ /// <example>
+ /// If the url is: http://contoso.com, and providerName='facebook', the returned value is: http://contoso.com?__provider=facebook
+ /// If the url is: http://contoso.com?a=1, and providerName='twitter', the returned value is: http://contoso.com?a=1&__provider=twitter
+ /// If the url is: http://contoso.com?a=1&__provider=twitter, and providerName='linkedin', the returned value is: http://contoso.com?a=1&__provider=linkedin
+ /// </example>
+ /// <remarks>
+ /// The reason we have to do this is so that when the external service provider forwards user
+ /// back to our site, we know which provider it comes back from.
+ /// </remarks>
+ public static Uri AttachQueryStringParameter(this Uri url, string parameterName, string parameterValue)
+ {
+ UriBuilder builder = new UriBuilder(url);
+ string query = builder.Query;
+ if (query.Length > 1)
+ {
+ // remove the '?' character in front of the query string
+ query = query.Substring(1);
+ }
+
+ string parameterPrefix = parameterName + "=";
+
+ string encodedParameterValue = Uri.EscapeDataString(parameterValue);
+
+ string newQuery = Regex.Replace(query, parameterPrefix + "[^\\&]*", parameterPrefix + encodedParameterValue);
+ if (newQuery == query)
+ {
+ if (newQuery.Length > 0)
+ {
+ newQuery += "&";
+ }
+ newQuery = newQuery + parameterPrefix + encodedParameterValue;
+ }
+ builder.Query = newQuery;
+
+ return builder.Uri;
+ }
+
+ /// <summary>
+ /// Appends the specified key/value pairs as query string parameters to the builder.
+ /// </summary>
+ /// <param name="builder">The builder.</param>
+ /// <param name="pairs">The pairs.</param>
+ /// <returns></returns>
+ public static void AppendQueryArguments(this UriBuilder builder, IDictionary<string, string> pairs)
+ {
+ if (pairs == null)
+ {
+ throw new ArgumentNullException("pairs");
+ }
+
+ if (!pairs.Any())
+ {
+ return;
+ }
+
+ string query = builder.Query;
+ if (query.Length > 1)
+ {
+ // remove the '?' character in front of the query string and append the '&'
+ query = query.Substring(1);
+ }
+
+ var sb = new StringBuilder(query);
+ foreach (KeyValuePair<string, string> pair in pairs)
+ {
+ if (sb.Length > 0)
+ {
+ sb.Append('&');
+ }
+ sb.AppendFormat("{0}={1}", pair.Key, pair.Value);
+ }
+
+ builder.Query = sb.ToString();
+ }
+
+ /// <summary>
+ /// Converts an app-relative url, e.g. ~/Content/Return.cshtml, to a full-blown url, e.g. http://mysite.com/Content/Return.cshtml
+ /// </summary>
+ /// <param name="returnUrl">The return URL.</param>
+ /// <returns></returns>
+ public static Uri ConvertToAbsoluteUri(string returnUrl)
+ {
+ if (Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute))
+ {
+ return new Uri(returnUrl, UriKind.Absolute);
+ }
+
+ if (HttpContext.Current == null)
+ {
+ return null;
+ }
+
+ if (!VirtualPathUtility.IsAbsolute(returnUrl))
+ {
+ returnUrl = VirtualPathUtility.ToAbsolute(returnUrl);
+ }
+
+ return new Uri(GetPublicFacingUrl(new HttpRequestWrapper(HttpContext.Current.Request)), returnUrl);
+ }
+
+ /// <summary>
+ /// Gets the public facing URL of this request as what clients see it.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ public static Uri GetPublicFacingUrl(HttpRequestBase request)
+ {
+ NameValueCollection serverVariables = request.ServerVariables;
+ if (serverVariables["HTTP_HOST"] != null)
+ {
+ string forwardProto = serverVariables["HTTP_X_FORWARDED_PROTO"];
+ if (forwardProto == null)
+ {
+ string scheme = request.Url.Scheme;
+ var hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
+ var publicRequestUri = new UriBuilder(request.Url)
+ {
+ Scheme = scheme,
+ Host = hostAndPort.Host,
+ Port = hostAndPort.Port
+ };
+
+ return publicRequestUri.Uri;
+ }
+ }
+ return new Uri(request.Url, request.RawUrl);
+ }
+ }
+} \ No newline at end of file