summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs1
-rw-r--r--src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs1
-rw-r--r--src/DotNetOpenAuth.Web.Test/DotNetOpenAuth.Web.Test.csproj87
-rw-r--r--src/DotNetOpenAuth.Web.Test/OAuth2ClientTest.cs142
-rw-r--r--src/DotNetOpenAuth.Web.Test/OAuthAuthenticationTickerHelperTest.cs152
-rw-r--r--src/DotNetOpenAuth.Web.Test/OAuthClientTest.cs142
-rw-r--r--src/DotNetOpenAuth.Web.Test/OAuthWebSecurityTest.cs486
-rw-r--r--src/DotNetOpenAuth.Web.Test/Properties/AssemblyInfo.cs35
-rw-r--r--src/DotNetOpenAuth.Web.Test/UriHelperTest.cs64
-rw-r--r--src/DotNetOpenAuth.Web/AuthenticationResult.cs87
-rw-r--r--src/DotNetOpenAuth.Web/BuiltInAuthenticationClient.cs16
-rw-r--r--src/DotNetOpenAuth.Web/BuiltInOpenIDClient.cs11
-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
-rw-r--r--src/DotNetOpenAuth.Web/DotNetOpenAuth.Web.csproj116
-rw-r--r--src/DotNetOpenAuth.Web/IOAuthDataProvider.cs15
-rw-r--r--src/DotNetOpenAuth.Web/OAuthAccount.cs47
-rw-r--r--src/DotNetOpenAuth.Web/OAuthAuthenticationTicketHelper.cs88
-rw-r--r--src/DotNetOpenAuth.Web/OAuthWebSecurity.cs386
-rw-r--r--src/DotNetOpenAuth.Web/Properties/AssemblyInfo.cs36
-rw-r--r--src/DotNetOpenAuth.Web/Resources/WebResources.Designer.cs171
-rw-r--r--src/DotNetOpenAuth.Web/Resources/WebResources.resx156
-rw-r--r--src/DotNetOpenAuth.WebPages/DotNetOpenAuth.WebPages.csproj92
-rw-r--r--src/DotNetOpenAuth.WebPages/PreApplicationStartCode.cs16
-rw-r--r--src/DotNetOpenAuth.WebPages/Properties/AssemblyInfo.cs27
-rw-r--r--src/DotNetOpenAuth.WebPages/WebPagesOAuthDataProvider.cs65
-rw-r--r--src/DotNetOpenAuth.sln47
46 files changed, 3920 insertions, 15 deletions
diff --git a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
index 205d331..d37e92d 100644
--- a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
@@ -65,6 +65,7 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.Web.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
#else
[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")]
diff --git a/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs
index 49ef570..dffd281 100644
--- a/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs
@@ -52,6 +52,7 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.Consumer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.Web.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
#else
[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")]
diff --git a/src/DotNetOpenAuth.Web.Test/DotNetOpenAuth.Web.Test.csproj b/src/DotNetOpenAuth.Web.Test/DotNetOpenAuth.Web.Test.csproj
new file mode 100644
index 0000000..d02c530
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/DotNetOpenAuth.Web.Test.csproj
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " />
+ <PropertyGroup>
+ <StyleCopEnabled>False</StyleCopEnabled>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth.Web.Test</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.Web.Test</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" />
+ <PropertyGroup>
+ <DelaySign>true</DelaySign>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Moq">
+ <HintPath>..\..\lib\Moq.dll</HintPath>
+ </Reference>
+ <Reference Include="nunit.framework">
+ <HintPath>..\..\lib\nunit.framework.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="OAuth2ClientTest.cs" />
+ <Compile Include="OAuthAuthenticationTickerHelperTest.cs" />
+ <Compile Include="OAuthClientTest.cs" />
+ <Compile Include="OAuthWebSecurityTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="UriHelperTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\DotNetOpenAuth.Messaging\DotNetOpenAuth.Messaging.csproj">
+ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project>
+ <Name>DotNetOpenAuth.Messaging</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj">
+ <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project>
+ <Name>DotNetOpenAuth.OAuth</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.WebPages\DotNetOpenAuth.WebPages.csproj">
+ <Project>{AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}</Project>
+ <Name>DotNetOpenAuth.WebPages</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.Web\DotNetOpenAuth.Web.csproj">
+ <Project>{51835086-9611-4C53-819B-F2D5C9320873}</Project>
+ <Name>DotNetOpenAuth.Web</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="official-build-key.pfx" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " />
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web.Test/OAuth2ClientTest.cs b/src/DotNetOpenAuth.Web.Test/OAuth2ClientTest.cs
new file mode 100644
index 0000000..b090346
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/OAuth2ClientTest.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenAuth.Web.Clients;
+using Moq;
+using System.Web;
+using System.Collections.Specialized;
+
+namespace DotNetOpenAuth.Web.Test
+{
+ [TestFixture]
+ public class OAuth2ClientTest
+ {
+ [TestCase]
+ public void TestProviderName()
+ {
+ // Arrange
+ var client = new MockOAuth2Client();
+
+ // Act
+ string providerName = client.ProviderName;
+
+ // Assert
+ Assert.AreEqual("mockprovider", providerName);
+ }
+
+ [TestCase]
+ public void RequestAuthenticationIssueCorrectRedirect()
+ {
+ // Arrange
+ var client = new MockOAuth2Client();
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ context.Setup(c => c.Response.Redirect("http://live.com/?q=http://return.to.me/", true)).Verifiable();
+
+ // Act
+ client.RequestAuthentication(context.Object, new Uri("http://return.to.me"));
+
+ // Assert
+ context.Verify();
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationThrowsIfContextIsNull()
+ {
+ // Arrange
+ var client = new MockOAuth2Client();
+
+ // Act && Assert
+ Assert.Throws<ArgumentNullException>(() => client.VerifyAuthentication(null));
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationFailsIfCodeIsNotPresent()
+ {
+ // Arrange
+ var client = new MockOAuth2Client();
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var queryStrings = new NameValueCollection();
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ // Act
+ AuthenticationResult result = client.VerifyAuthentication(context.Object);
+
+ // Assert
+ Assert.IsFalse(result.IsSuccessful);
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationFailsIfAccessTokenIsNull()
+ {
+ // Arrange
+ var client = new MockOAuth2Client();
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var queryStrings = new NameValueCollection();
+ queryStrings.Add("code", "random");
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ // Act
+ AuthenticationResult result = client.VerifyAuthentication(context.Object);
+
+ // Assert
+ Assert.IsFalse(result.IsSuccessful);
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationSucceeds()
+ {
+ // Arrange
+ var client = new MockOAuth2Client();
+ var context = new Mock<HttpContextBase>(MockBehavior.Strict);
+ var queryStrings = new NameValueCollection();
+ queryStrings.Add("code", "secret");
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ // Act
+ AuthenticationResult result = client.VerifyAuthentication(context.Object);
+
+ // Assert
+ Assert.True(result.IsSuccessful);
+ Assert.AreEqual("mockprovider", result.Provider);
+ Assert.AreEqual("12345", result.ProviderUserId);
+ Assert.AreEqual("John Doe", result.UserName);
+ Assert.NotNull(result.ExtraData);
+ Assert.AreEqual("abcde", result.ExtraData["token"]);
+ }
+
+ private class MockOAuth2Client : OAuth2Client
+ {
+ public MockOAuth2Client() : base("mockprovider")
+ {
+ }
+
+ protected override Uri GetServiceLoginUrl(Uri returnUrl)
+ {
+ string url = "http://live.com/?q=" + returnUrl.ToString();
+ return new Uri(url);
+ }
+
+ protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
+ {
+ return (authorizationCode == "secret") ? "abcde" : null;
+ }
+
+ protected override IDictionary<string, string> GetUserData(string accessToken)
+ {
+ if (accessToken == "abcde")
+ {
+ return new Dictionary<string, string>
+ {
+ {"id", "12345"},
+ {"token", accessToken},
+ {"name", "John Doe"}
+ };
+ }
+
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web.Test/OAuthAuthenticationTickerHelperTest.cs b/src/DotNetOpenAuth.Web.Test/OAuthAuthenticationTickerHelperTest.cs
new file mode 100644
index 0000000..2545bb3
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/OAuthAuthenticationTickerHelperTest.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Web;
+using System.Web.Security;
+using DotNetOpenAuth.Web;
+using Moq;
+using NUnit.Framework;
+
+namespace DotNetOpenAuth.Test.Web
+{
+ [TestFixture]
+ public class OAuthAuthenticationTickerHelperTest
+ {
+ [TestCase]
+ public void SetAuthenticationTicketSetCookieOnHttpResponseWithPersistentSet()
+ {
+ SetAuthenticationTicketSetCookieOnHttpResponse(isPersistent: true);
+ }
+
+ [TestCase]
+ public void SetAuthenticationTicketSetCookieOnHttpResponseWithPersistentNotSet()
+ {
+ SetAuthenticationTicketSetCookieOnHttpResponse(isPersistent: false);
+ }
+
+ [TestCase]
+ public void IsOAuthAuthenticationTicketReturnsTrueIfCookieIsPresent()
+ {
+ // Arrange
+ var ticket = new FormsAuthenticationTicket(
+ 2,
+ "username",
+ DateTime.Now,
+ DateTime.Now.Add(FormsAuthentication.Timeout),
+ false,
+ "OAuth",
+ FormsAuthentication.FormsCookiePath);
+
+ var cookie = new HttpCookie(name: FormsAuthentication.FormsCookieName,
+ value: FormsAuthentication.Encrypt(ticket));
+ var cookies = new HttpCookieCollection {cookie};
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.Cookies).Returns(cookies);
+
+ // Act
+ bool result = OAuthAuthenticationTicketHelper.IsOAuthAuthenticationTicket(context.Object);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestCase]
+ public void IsOAuthAuthenticationTicketReturnsFalseIfCookieIsNotPresent()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
+
+ // Act
+ bool result = OAuthAuthenticationTicketHelper.IsOAuthAuthenticationTicket(context.Object);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestCase]
+ public void IsOAuthAuthenticationTicketReturnsFalseIfCookieIsPresentButDoesNotHaveOAuthData()
+ {
+ // Arrange
+ var ticket = new FormsAuthenticationTicket(
+ 2,
+ "username",
+ DateTime.Now,
+ DateTime.Now.Add(FormsAuthentication.Timeout),
+ false,
+ null,
+ FormsAuthentication.FormsCookiePath);
+
+ var cookie = new HttpCookie(name: FormsAuthentication.FormsCookieName,
+ value: FormsAuthentication.Encrypt(ticket));
+ var cookies = new HttpCookieCollection { cookie };
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.Cookies).Returns(cookies);
+
+ // Act
+ bool result = OAuthAuthenticationTicketHelper.IsOAuthAuthenticationTicket(context.Object);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestCase]
+ public void IsOAuthAuthenticationTicketReturnsFalseIfCookieIsPresentButDoesNotHaveCorrectName()
+ {
+ // Arrange
+ var response = new Mock<HttpResponseBase>();
+
+ var ticket = new FormsAuthenticationTicket(
+ 2,
+ "username",
+ DateTime.Now,
+ DateTime.Now.Add(FormsAuthentication.Timeout),
+ false,
+ "OAuth",
+ FormsAuthentication.FormsCookiePath);
+
+ var cookie = new HttpCookie(name: "random cookie name",
+ value: FormsAuthentication.Encrypt(ticket));
+ var cookies = new HttpCookieCollection { cookie };
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.Cookies).Returns(cookies);
+
+ // Act
+ bool result = OAuthAuthenticationTicketHelper.IsOAuthAuthenticationTicket(context.Object);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ private void SetAuthenticationTicketSetCookieOnHttpResponse(bool isPersistent)
+ {
+ // Arrange
+ var cookies = new HttpCookieCollection();
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.IsSecureConnection).Returns(true);
+ context.Setup(c => c.Response.Cookies).Returns(cookies);
+
+ // Act
+ OAuthAuthenticationTicketHelper.SetAuthenticationTicket(context.Object, "user", isPersistent);
+
+ // Assert
+ Assert.AreEqual(1, cookies.Count);
+ HttpCookie addedCookie = cookies[0];
+
+ Assert.AreEqual(FormsAuthentication.FormsCookieName, addedCookie.Name);
+ Assert.IsTrue(addedCookie.HttpOnly);
+ Assert.AreEqual("/", addedCookie.Path);
+ Assert.IsFalse(addedCookie.Secure);
+ Assert.IsNotNullOrEmpty(addedCookie.Value);
+
+ FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(addedCookie.Value);
+ Assert.NotNull(ticket);
+ Assert.AreEqual(2, ticket.Version);
+ Assert.AreEqual("user", ticket.Name);
+ Assert.AreEqual("OAuth", ticket.UserData);
+ Assert.AreEqual(isPersistent, ticket.IsPersistent);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web.Test/OAuthClientTest.cs b/src/DotNetOpenAuth.Web.Test/OAuthClientTest.cs
new file mode 100644
index 0000000..8e8cf1e
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/OAuthClientTest.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Web;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.OAuth.Messages;
+using DotNetOpenAuth.Web.Clients;
+using Moq;
+using NUnit.Framework;
+
+namespace DotNetOpenAuth.Web.Test
+{
+ [TestFixture]
+ public class OAuthClientTest
+ {
+ [TestCase]
+ public void TestProviderNamePropertyIsCorrect()
+ {
+ // Arrange
+ var client = new MockOAuthClient();
+
+ // Act
+ var provider = client.ProviderName;
+
+ // Assert
+ Assert.AreEqual("mockoauth", provider);
+ }
+
+ [TestCase]
+ public void RequestAuthenticationInvokeMethodOnWebWorker()
+ {
+ // Arrange
+ var webWorker = new Mock<IOAuthWebWorker>(MockBehavior.Strict);
+ webWorker.Setup(
+ w => w.RequestAuthentication(
+ It.Is<Uri>(u => u.ToString().Equals("http://live.com/my/path.cshtml?q=one"))))
+ .Verifiable();
+
+ var client = new MockOAuthClient(webWorker.Object);
+ var returnUri = new Uri("http://live.com/my/path.cshtml?q=one");
+ var context = new Mock<HttpContextBase>();
+
+ // Act
+ client.RequestAuthentication(context.Object, returnUri);
+
+ // Assert
+ webWorker.Verify();
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationFailsIfResponseTokenIsNull()
+ {
+ // Arrange
+ var webWorker = new Mock<IOAuthWebWorker>(MockBehavior.Strict);
+ webWorker.Setup(w => w.ProcessUserAuthorization()).Returns((AuthorizedTokenResponse)null);
+
+ var client = new MockOAuthClient(webWorker.Object);
+ var context = new Mock<HttpContextBase>();
+
+ // Act
+ client.VerifyAuthentication(context.Object);
+
+ // Assert
+ webWorker.Verify();
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationFailsIfAccessTokenIsInvalid()
+ {
+ // Arrange
+ var endpoint = new MessageReceivingEndpoint("http://live.com/path/?a=b", HttpDeliveryMethods.GetRequest);
+ var request = new AuthorizedTokenRequest(endpoint, new Version("1.0"));
+ var response = new AuthorizedTokenResponse(request)
+ {
+ AccessToken = "invalid token"
+ };
+
+ var webWorker = new Mock<IOAuthWebWorker>(MockBehavior.Strict);
+ webWorker.Setup(w => w.ProcessUserAuthorization()).Returns(response).Verifiable();
+
+ var client = new MockOAuthClient(webWorker.Object);
+ var context = new Mock<HttpContextBase>();
+
+ // Act
+ AuthenticationResult result = client.VerifyAuthentication(context.Object);
+
+ // Assert
+ webWorker.Verify();
+
+ Assert.False(result.IsSuccessful);
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationSucceeds()
+ {
+ // Arrange
+ var endpoint = new MessageReceivingEndpoint("http://live.com/path/?a=b", HttpDeliveryMethods.GetRequest);
+ var request = new AuthorizedTokenRequest(endpoint, new Version("1.0"));
+ var response = new AuthorizedTokenResponse(request)
+ {
+ AccessToken = "ok"
+ };
+
+ var webWorker = new Mock<IOAuthWebWorker>(MockBehavior.Strict);
+ webWorker.Setup(w => w.ProcessUserAuthorization()).Returns(response).Verifiable();
+
+ var client = new MockOAuthClient(webWorker.Object);
+ var context = new Mock<HttpContextBase>();
+
+ // Act
+ AuthenticationResult result = client.VerifyAuthentication(context.Object);
+
+ // Assert
+ webWorker.Verify();
+
+ Assert.True(result.IsSuccessful);
+ Assert.AreEqual("mockoauth", result.Provider);
+ Assert.AreEqual("12345", result.ProviderUserId);
+ Assert.AreEqual("super", result.UserName);
+ }
+
+ private class MockOAuthClient : OAuthClient
+ {
+ public MockOAuthClient()
+ : this(new Mock<IOAuthWebWorker>().Object)
+ {
+ }
+
+ public MockOAuthClient(IOAuthWebWorker worker) : base("mockoauth", worker)
+ {
+ }
+
+ protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
+ {
+ if (response.AccessToken == "ok")
+ {
+ return new AuthenticationResult(true, "mockoauth", "12345", "super", response.ExtraData);
+ }
+
+ return AuthenticationResult.Failed;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web.Test/OAuthWebSecurityTest.cs b/src/DotNetOpenAuth.Web.Test/OAuthWebSecurityTest.cs
new file mode 100644
index 0000000..2465f0f
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/OAuthWebSecurityTest.cs
@@ -0,0 +1,486 @@
+using System;
+using System.Collections.Specialized;
+using System.Web;
+using System.Web.Security;
+using DotNetOpenAuth.Web.Clients;
+using Moq;
+using NUnit.Framework;
+
+namespace DotNetOpenAuth.Web.Test
+{
+ [TestFixture]
+ public class OAuthWebSecurityTest
+ {
+ [TestCase]
+ public void RegisterDataProviderThrowsOnNullValue()
+ {
+ Assert.Throws(typeof(ArgumentNullException), () => OAuthWebSecurity.RegisterDataProvider(null));
+ }
+
+ [TestCase]
+ public void IsOAuthDataProviderIsFalseIfNotYetRegistered()
+ {
+ Assert.IsFalse(OAuthWebSecurity.IsOAuthDataProviderRegistered);
+ }
+
+ [TestCase]
+ public void IsOAuthDataProviderIsTrueIfRegistered()
+ {
+ // Arrange
+ OAuthWebSecurity.RegisterDataProvider(new Mock<IOAuthDataProvider>().Object);
+
+ // Act & Assert
+ Assert.IsTrue(OAuthWebSecurity.IsOAuthDataProviderRegistered);
+ }
+
+ [TestCase]
+ public void RegisterDataProviderThrowsIfRegisterMoreThanOnce()
+ {
+ // Arrange
+ OAuthWebSecurity.RegisterDataProvider(new Mock<IOAuthDataProvider>().Object);
+
+ // Act & Assert
+ Assert.Throws(
+ typeof(InvalidOperationException),
+ () => OAuthWebSecurity.RegisterDataProvider(new Mock<IOAuthDataProvider>().Object));
+ }
+
+ [TestCase]
+ public void RegisterClientThrowsOnNullValue()
+ {
+ Assert.Throws(typeof(ArgumentNullException), () => OAuthWebSecurity.RegisterClient(null));
+ }
+
+ [TestCase]
+ public void RegisterClientThrowsIfProviderNameIsEmpty()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns((string)null);
+
+ // Act & Assert
+ Assert.Throws(typeof(ArgumentException), () => OAuthWebSecurity.RegisterClient(client.Object), "Invalid provider name.");
+
+ client.Setup(c => c.ProviderName).Returns("");
+
+ // Act & Assert
+ Assert.Throws(typeof(ArgumentException), () => OAuthWebSecurity.RegisterClient(client.Object), "Invalid provider name.");
+ }
+
+ [TestCase]
+ public void RegisterClientThrowsRegisterMoreThanOneClientWithTheSameName()
+ {
+ // Arrange
+ var client1 = new Mock<IAuthenticationClient>();
+ client1.Setup(c => c.ProviderName).Returns("provider");
+
+ var client2 = new Mock<IAuthenticationClient>();
+ client2.Setup(c => c.ProviderName).Returns("provider");
+
+ OAuthWebSecurity.RegisterClient(client1.Object);
+
+ // Act & Assert
+ Assert.Throws(
+ typeof(ArgumentException),
+ () => OAuthWebSecurity.RegisterClient(client2.Object),
+ "Another service provider with the same name has already been registered.");
+ }
+
+ [TestCase]
+ public void RegisterOAuthClient()
+ {
+ // Arrange
+ var clients = new BuiltInOAuthClient[]
+ {
+ BuiltInOAuthClient.Facebook,
+ BuiltInOAuthClient.Twitter,
+ BuiltInOAuthClient.LinkedIn,
+ BuiltInOAuthClient.WindowsLive
+ };
+ var clientNames = new string[]
+ {
+ "Facebook",
+ "Twitter",
+ "LinkedIn",
+ "WindowsLive"
+ };
+
+ for (int i = 0; i < clients.Length; i++)
+ {
+ // Act
+ OAuthWebSecurity.RegisterOAuthClient(clients[i], "key", "secret");
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns(clientNames[i]);
+
+ // Assert
+ Assert.Throws(
+ typeof(ArgumentException),
+ () => OAuthWebSecurity.RegisterClient(client.Object),
+ "Another service provider with the same name has already been registered.");
+ }
+ }
+
+ [TestCase]
+ public void RegisterOpenIDClient()
+ {
+ // Arrange
+ var clients = new BuiltInOpenIDClient[]
+ {
+ BuiltInOpenIDClient.Google,
+ BuiltInOpenIDClient.Yahoo
+ };
+ var clientNames = new string[]
+ {
+ "Google",
+ "Yahoo"
+ };
+
+ for (int i = 0; i < clients.Length; i++)
+ {
+ // Act
+ OAuthWebSecurity.RegisterOpenIDClient(clients[i]);
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns(clientNames[i]);
+
+ // Assert
+ Assert.Throws(
+ typeof(ArgumentException),
+ () => OAuthWebSecurity.RegisterClient(client.Object),
+ "Another service provider with the same name has already been registered.");
+ }
+ }
+
+ [TestCase]
+ public void RequestAuthenticationRedirectsToProviderWithNullReturnUrl()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.ServerVariables).Returns(
+ new NameValueCollection());
+ context.Setup(c => c.Request.Url).Returns(new Uri("http://live.com/login.aspx"));
+ context.Setup(c => c.Request.RawUrl).Returns("/login.aspx");
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("windowslive");
+ client.Setup(c => c.RequestAuthentication(
+ context.Object,
+ It.Is<Uri>(u => u.AbsoluteUri.Equals("http://live.com/login.aspx?__provider__=windowslive", StringComparison.OrdinalIgnoreCase))))
+ .Verifiable();
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ // Act
+ OAuthWebSecurity.RequestAuthenticationCore(context.Object, "windowslive", null);
+
+ // Assert
+ client.Verify();
+ }
+
+ [TestCase]
+ public void RequestAuthenticationRedirectsToProviderWithReturnUrl()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.ServerVariables).Returns(
+ new NameValueCollection());
+ context.Setup(c => c.Request.Url).Returns(new Uri("http://live.com/login.aspx"));
+ context.Setup(c => c.Request.RawUrl).Returns("/login.aspx");
+
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("yahoo");
+ client.Setup(c => c.RequestAuthentication(
+ context.Object,
+ It.Is<Uri>(u => u.AbsoluteUri.Equals("http://yahoo.com/?__provider__=yahoo", StringComparison.OrdinalIgnoreCase))))
+ .Verifiable();
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ // Act
+ OAuthWebSecurity.RequestAuthenticationCore(context.Object, "yahoo", "http://yahoo.com");
+
+ // Assert
+ client.Verify();
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationSucceed()
+ {
+ // Arrange
+ var queryStrings = new NameValueCollection();
+ queryStrings.Add("__provider__", "facebook");
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ var client = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ client.Setup(c => c.VerifyAuthentication(context.Object)).Returns(new AuthenticationResult(true, "facebook", "123",
+ "super", null));
+
+ var anotherClient = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ anotherClient.Setup(c => c.ProviderName).Returns("twitter");
+ anotherClient.Setup(c => c.VerifyAuthentication(context.Object)).Returns(AuthenticationResult.Failed);
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ AuthenticationResult result = OAuthWebSecurity.VerifyAuthenticationCore(context.Object);
+
+ // Assert
+ Assert.True(result.IsSuccessful);
+ Assert.AreEqual("facebook", result.Provider);
+ Assert.AreEqual("123", result.ProviderUserId);
+ Assert.AreEqual("super", result.UserName);
+ Assert.IsNull(result.Error);
+ Assert.IsNull(result.ExtraData);
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationFail()
+ {
+ // Arrange
+ var queryStrings = new NameValueCollection();
+ queryStrings.Add("__provider__", "twitter");
+
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.QueryString).Returns(queryStrings);
+
+ var client = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ client.Setup(c => c.VerifyAuthentication(context.Object)).Returns(new AuthenticationResult(true, "facebook", "123",
+ "super", null));
+
+ var anotherClient = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ anotherClient.Setup(c => c.ProviderName).Returns("twitter");
+ anotherClient.Setup(c => c.VerifyAuthentication(context.Object)).Returns(AuthenticationResult.Failed);
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ AuthenticationResult result = OAuthWebSecurity.VerifyAuthenticationCore(context.Object);
+
+ // Assert
+ Assert.False(result.IsSuccessful);
+ Assert.AreEqual("twitter", result.Provider);
+ }
+
+ [TestCase]
+ public void VerifyAuthenticationFailIfNoProviderInQueryString()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.QueryString).Returns(new NameValueCollection());
+
+ var client = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ client.Setup(c => c.ProviderName).Returns("facebook");
+
+ var anotherClient = new Mock<IAuthenticationClient>(MockBehavior.Strict);
+ anotherClient.Setup(c => c.ProviderName).Returns("twitter");
+
+ OAuthWebSecurity.RegisterClient(client.Object);
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ AuthenticationResult result = OAuthWebSecurity.VerifyAuthenticationCore(context.Object);
+
+ // Assert
+ Assert.False(result.IsSuccessful);
+ Assert.IsNull(result.Provider);
+ }
+
+ [TestCase]
+ public void LoginSetAuthenticationTicketIfSuccessful()
+ {
+ // Arrange
+ var cookies = new HttpCookieCollection();
+ var context = new Mock<HttpContextBase>();
+ context.Setup(c => c.Request.IsSecureConnection).Returns(true);
+ context.Setup(c => c.Response.Cookies).Returns(cookies);
+
+ var dataProvider = new Mock<IOAuthDataProvider>(MockBehavior.Strict);
+ dataProvider.Setup(p => p.GetUserNameFromOAuth("twitter", "12345")).Returns("hola");
+ OAuthWebSecurity.RegisterDataProvider(dataProvider.Object);
+
+ // Act
+ bool successful = OAuthWebSecurity.LoginCore(context.Object, "twitter", "12345", createPersistentCookie: false);
+
+ // Assert
+ Assert.IsTrue(successful);
+
+ Assert.AreEqual(1, cookies.Count);
+ HttpCookie addedCookie = cookies[0];
+
+ Assert.AreEqual(FormsAuthentication.FormsCookieName, addedCookie.Name);
+ Assert.IsTrue(addedCookie.HttpOnly);
+ Assert.AreEqual("/", addedCookie.Path);
+ Assert.IsFalse(addedCookie.Secure);
+ Assert.IsNotNullOrEmpty(addedCookie.Value);
+
+ FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(addedCookie.Value);
+ Assert.NotNull(ticket);
+ Assert.AreEqual(2, ticket.Version);
+ Assert.AreEqual("hola", ticket.Name);
+ Assert.AreEqual("OAuth", ticket.UserData);
+ Assert.IsFalse(ticket.IsPersistent);
+ }
+
+ [TestCase]
+ public void LoginFailIfUserIsNotFound()
+ {
+ // Arrange
+ var context = new Mock<HttpContextBase>();
+
+ var dataProvider = new Mock<IOAuthDataProvider>();
+ OAuthWebSecurity.RegisterDataProvider(dataProvider.Object);
+
+ // Act
+ bool successful = OAuthWebSecurity.LoginCore(context.Object, "twitter", "12345", createPersistentCookie: false);
+
+ // Assert
+ Assert.IsFalse(successful);
+ }
+
+ [TestCase]
+ public void CreateOrUpdateAccountCallsOAuthDataProviderMethod()
+ {
+ // Arrange
+ var dataProvider = new Mock<IOAuthDataProvider>(MockBehavior.Strict);
+ OAuthWebSecurity.RegisterDataProvider(dataProvider.Object);
+ dataProvider.Setup(p => p.CreateOrUpdateOAuthAccount("twitter", "12345", "super")).Verifiable();
+
+ // Act
+ OAuthWebSecurity.CreateOrUpdateAccount("twitter", "12345", "super");
+
+ // Assert
+ dataProvider.Verify();
+ }
+
+ [TestCase]
+ public void GetAccountsFromUserNameCallsOAuthDataProviderMethod()
+ {
+ // Arrange
+ var accounts = new OAuthAccount[]
+ {
+ new OAuthAccount("twitter", "123"),
+ new OAuthAccount("facebook", "abc"),
+ new OAuthAccount("live", "xyz")
+ };
+
+ var dataProvider = new Mock<IOAuthDataProvider>(MockBehavior.Strict);
+ dataProvider.Setup(p => p.GetOAuthAccountsFromUserName("dotnetjunky")).Returns(accounts).Verifiable();
+
+ OAuthWebSecurity.RegisterDataProvider(dataProvider.Object);
+
+ // Act
+ var retrievedAccounts = OAuthWebSecurity.GetAccountsFromUserName("dotnetjunky");
+
+ // Assert
+ CollectionAssert.AreEqual(retrievedAccounts, accounts);
+ }
+
+ [TestCase]
+ public void DeleteAccountCallsOAuthDataProviderMethod()
+ {
+ // Arrange
+ var dataProvider = new Mock<IOAuthDataProvider>(MockBehavior.Strict);
+ OAuthWebSecurity.RegisterDataProvider(dataProvider.Object);
+ dataProvider.Setup(p => p.DeleteOAuthAccount("linkedin", "423432")).Returns(true).Verifiable();
+
+ // Act
+ OAuthWebSecurity.DeleteAccount("linkedin", "423432");
+
+ // Assert
+ dataProvider.Verify();
+ }
+
+ [TestCase]
+ public void GetOAuthClientReturnsTheCorrectClient()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ var expectedClient = OAuthWebSecurity.GetOAuthClient("facebook");
+
+ // Assert
+ Assert.AreSame(expectedClient, client.Object);
+ }
+
+ [TestCase]
+ public void GetOAuthClientThrowsIfClientIsNotFound()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act & Assert
+ Assert.Throws<ArgumentException>(
+ () => OAuthWebSecurity.GetOAuthClient("live"),
+ "A service provider could not be found by the specified name.");
+ }
+
+ [TestCase]
+ public void TryGetOAuthClientSucceeds()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ IAuthenticationClient expectedClient;
+ bool result = OAuthWebSecurity.TryGetOAuthClient("facebook", out expectedClient);
+
+ // Assert
+ Assert.AreSame(expectedClient, client.Object);
+ Assert.IsTrue(result);
+ }
+
+ [TestCase]
+ public void TryGetOAuthClientFail()
+ {
+ // Arrange
+ var client = new Mock<IAuthenticationClient>();
+ client.Setup(c => c.ProviderName).Returns("facebook");
+ OAuthWebSecurity.RegisterClient(client.Object);
+
+ var anotherClient = new Mock<IAuthenticationClient>();
+ anotherClient.Setup(c => c.ProviderName).Returns("hulu");
+ OAuthWebSecurity.RegisterClient(anotherClient.Object);
+
+ // Act
+ IAuthenticationClient expectedClient;
+ bool result = OAuthWebSecurity.TryGetOAuthClient("live", out expectedClient);
+
+ // Assert
+ Assert.IsNull(expectedClient);
+ Assert.IsFalse(result);
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ OAuthWebSecurity.ClearDataProvider();
+ OAuthWebSecurity.ClearProviders();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web.Test/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Web.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2ea59e1
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DotNetOpenAuth.Web.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft IT")]
+[assembly: AssemblyProduct("DotNetOpenAuth.Web.Test")]
+[assembly: AssemblyCopyright("Copyright © Microsoft IT 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("6c32d8f7-1394-40ef-9ec0-b8953adc0a4f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/DotNetOpenAuth.Web.Test/UriHelperTest.cs b/src/DotNetOpenAuth.Web.Test/UriHelperTest.cs
new file mode 100644
index 0000000..557efae
--- /dev/null
+++ b/src/DotNetOpenAuth.Web.Test/UriHelperTest.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using DotNetOpenAuth.Web.Clients;
+
+namespace DotNetOpenAuth.Web.Test
+{
+ [TestFixture]
+ public class UriHelperTest
+ {
+ [TestCase]
+ public void TestAttachQueryStringParameterMethod()
+ {
+ // Arrange
+ string[] input = new string[]
+ {
+ "http://x.com",
+ "https://xxx.com/one?s=123",
+ "https://yyy.com/?s=6&u=a",
+ "https://zzz.com/default.aspx?name=sd"
+ };
+
+ string[] expectedOutput = new string[]
+ {
+ "http://x.com/?s=awesome",
+ "https://xxx.com/one?s=awesome",
+ "https://yyy.com/?s=awesome&u=a",
+ "https://zzz.com/default.aspx?name=sd&s=awesome"
+ };
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ // Act
+ var inputUrl = new Uri(input[i]);
+ var outputUri = UriHelper.AttachQueryStringParameter(inputUrl, "s", "awesome");
+
+ // Assert
+ Assert.AreEqual(expectedOutput[i], outputUri.ToString());
+ }
+ }
+
+ [TestCase]
+ public void TestAppendQueryArguments()
+ {
+ // Arrange
+ var builder = new UriBuilder("http://www.microsoft.com");
+
+ // Act
+ builder.AppendQueryArguments(
+ new Dictionary<string, string> {{"one", "xxx"}, {"two", "yyy"}});
+
+ // Assert
+ if (builder.Port == 80)
+ {
+ // set port = -1 so that the display string doesn't contain port number
+ builder.Port = -1;
+ }
+ string s = builder.ToString();
+ Assert.AreEqual("http://www.microsoft.com/?one=xxx&two=yyy", s);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/AuthenticationResult.cs b/src/DotNetOpenAuth.Web/AuthenticationResult.cs
new file mode 100644
index 0000000..b32267d
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/AuthenticationResult.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace DotNetOpenAuth.Web
+{
+ /// <summary>
+ /// Represents the result of OAuth & OpenId authentication
+ /// </summary>
+ public class AuthenticationResult
+ {
+ /// <summary>
+ /// Returns an instance which indicates failed authentication.
+ /// </summary>
+ [SuppressMessage(
+ "Microsoft.Security",
+ "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
+ Justification = "This type is immutable.")]
+ public static readonly AuthenticationResult Failed = new AuthenticationResult(isSuccessful: false);
+
+ /// <summary>
+ /// Gets a value indicating whether the authentication step is successful.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if authentication is successful; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSuccessful { get; private set; }
+
+ /// <summary>
+ /// Gets the provider's name.
+ /// </summary>
+ public string Provider { get; private set; }
+
+ /// <summary>
+ /// Gets the unique user id that is returned from the provider.
+ /// </summary>
+ public string ProviderUserId { get; private set; }
+
+ /// <summary>
+ /// Gets the user name that is returned from the provider.
+ /// </summary>
+ public string UserName { get; private set; }
+
+ /// <summary>
+ /// Gets the optional extra data that may be returned from the provider
+ /// </summary>
+ public IDictionary<string, string> ExtraData { get; private set; }
+
+ /// <summary>
+ /// Gets the error that may have occured during the authentication process
+ /// </summary>
+ public Exception Error { get; private set; }
+
+ public AuthenticationResult(bool isSuccessful) :
+ this(isSuccessful,
+ provider: null,
+ providerUserId: null,
+ userName: null,
+ extraData: null)
+ {
+ }
+
+ public AuthenticationResult(Exception exception) : this(isSuccessful: false)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException("exception");
+ }
+
+ Error = exception;
+ }
+
+ public AuthenticationResult(
+ bool isSuccessful,
+ string provider,
+ string providerUserId,
+ string userName,
+ IDictionary<string, string> extraData)
+ {
+ IsSuccessful = isSuccessful;
+ Provider = provider;
+ ProviderUserId = providerUserId;
+ UserName = userName;
+ ExtraData = extraData;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/BuiltInAuthenticationClient.cs b/src/DotNetOpenAuth.Web/BuiltInAuthenticationClient.cs
new file mode 100644
index 0000000..29aea59
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/BuiltInAuthenticationClient.cs
@@ -0,0 +1,16 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace DotNetOpenAuth.Web
+{
+ /// <summary>
+ /// Represents built in OAuth clients.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OAuth")]
+ public enum BuiltInOAuthClient
+ {
+ Twitter,
+ Facebook,
+ LinkedIn,
+ WindowsLive
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/BuiltInOpenIDClient.cs b/src/DotNetOpenAuth.Web/BuiltInOpenIDClient.cs
new file mode 100644
index 0000000..65de6e3
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/BuiltInOpenIDClient.cs
@@ -0,0 +1,11 @@
+namespace DotNetOpenAuth.Web
+{
+ /// <summary>
+ /// Represents built in OpenID clients.
+ /// </summary>
+ public enum BuiltInOpenIDClient
+ {
+ Google,
+ Yahoo
+ }
+}
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
diff --git a/src/DotNetOpenAuth.Web/DotNetOpenAuth.Web.csproj b/src/DotNetOpenAuth.Web/DotNetOpenAuth.Web.csproj
new file mode 100644
index 0000000..5f5aef8
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/DotNetOpenAuth.Web.csproj
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " />
+ <PropertyGroup>
+ <StyleCopEnabled>False</StyleCopEnabled>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{51835086-9611-4C53-819B-F2D5C9320873}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth.Web</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.Web</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\..\bin\v4.0\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <CodeAnalysisRuleSet>ExtendedDesignGuidelineRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\bin\v4.0\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" />
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" />
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AuthenticationResult.cs" />
+ <Compile Include="BuiltInAuthenticationClient.cs" />
+ <Compile Include="BuiltInOpenIDClient.cs" />
+ <Compile Include="Clients\AuthenticationClientCollection.cs" />
+ <Compile Include="Clients\DictionaryExtensions.cs" />
+ <Compile Include="Clients\IAuthenticationClient.cs" />
+ <Compile Include="Clients\OAuth2\FacebookClient.cs" />
+ <Compile Include="Clients\OAuth2\FacebookGraph.cs" />
+ <Compile Include="Clients\OAuth2\JsonHelper.cs" />
+ <Compile Include="Clients\OAuth2\OAuth2AccessTokenData.cs" />
+ <Compile Include="Clients\OAuth2\OAuth2Client.cs" />
+ <Compile Include="Clients\OAuth2\WindowsLiveClient.cs" />
+ <Compile Include="Clients\OAuth2\WindowsLiveUserData.cs" />
+ <Compile Include="Clients\OAuth\DotNetOpenAuthWebConsumer.cs" />
+ <Compile Include="Clients\OAuth\InMemoryOAuthTokenManager.cs" />
+ <Compile Include="Clients\OAuth\IOAuthWebWorker.cs" />
+ <Compile Include="Clients\OAuth\LinkedInClient.cs" />
+ <Compile Include="Clients\OAuth\OAuthClient.cs" />
+ <Compile Include="Clients\OAuth\TwitterClient.cs" />
+ <Compile Include="Clients\OpenID\AxKnownAttributes.cs" />
+ <Compile Include="Clients\OpenID\GoogleOpenIdClient.cs" />
+ <Compile Include="Clients\OpenID\OpenIDClient.cs" />
+ <Compile Include="Clients\OpenID\YahooOpenIdClient.cs" />
+ <Compile Include="Clients\UriHelper.cs" />
+ <Compile Include="IOAuthDataProvider.cs" />
+ <Compile Include="OAuthAccount.cs" />
+ <Compile Include="OAuthAuthenticationTicketHelper.cs" />
+ <Compile Include="OAuthWebSecurity.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Resources\WebResources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>WebResources.resx</DependentUpon>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Resources\WebResources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>WebResources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\DotNetOpenAuth.Messaging\DotNetOpenAuth.Messaging.csproj">
+ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project>
+ <Name>DotNetOpenAuth.Messaging</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj">
+ <Project>{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}</Project>
+ <Name>DotNetOpenAuth.OAuth.Consumer</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj">
+ <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project>
+ <Name>DotNetOpenAuth.OAuth</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OpenId.RelyingParty\DotNetOpenAuth.OpenId.RelyingParty.csproj">
+ <Project>{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}</Project>
+ <Name>DotNetOpenAuth.OpenId.RelyingParty</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OpenId\DotNetOpenAuth.OpenId.csproj">
+ <Project>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</Project>
+ <Name>DotNetOpenAuth.OpenId</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " />
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/IOAuthDataProvider.cs b/src/DotNetOpenAuth.Web/IOAuthDataProvider.cs
new file mode 100644
index 0000000..cf2d576
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/IOAuthDataProvider.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace DotNetOpenAuth.Web
+{
+ public interface IOAuthDataProvider
+ {
+ string GetUserNameFromOAuth(string oAuthProvider, string oAuthId);
+
+ void CreateOrUpdateOAuthAccount(string oAuthProvider, string oAuthId, string userName);
+
+ bool DeleteOAuthAccount(string oAuthProvider, string oAuthId);
+
+ ICollection<OAuthAccount> GetOAuthAccountsFromUserName(string userName);
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/OAuthAccount.cs b/src/DotNetOpenAuth.Web/OAuthAccount.cs
new file mode 100644
index 0000000..a580cb3
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/OAuthAccount.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Globalization;
+using DotNetOpenAuth.Web.Resources;
+
+namespace DotNetOpenAuth.Web
+{
+ /// <summary>
+ /// Represents an OpenAuth & OpenID account.
+ /// </summary>
+ public class OAuthAccount
+ {
+ /// <summary>
+ /// Gets the provider name.
+ /// </summary>
+ public string Provider { get; private set; }
+
+ /// <summary>
+ /// Gets the provider user id.
+ /// </summary>
+ public string ProviderUserId { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthAccountData"/> class.
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ /// <param name="providerUserId">The provider user id.</param>
+ public OAuthAccount(string provider, string providerUserId)
+ {
+ if (string.IsNullOrEmpty(provider))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "provider"),
+ "provider");
+ }
+
+ if (string.IsNullOrEmpty(providerUserId))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "providerUserId"),
+ "providerUserId");
+ }
+
+ Provider = provider;
+ ProviderUserId = providerUserId;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/OAuthAuthenticationTicketHelper.cs b/src/DotNetOpenAuth.Web/OAuthAuthenticationTicketHelper.cs
new file mode 100644
index 0000000..ba351d0
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/OAuthAuthenticationTicketHelper.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Diagnostics;
+using System.Web;
+using System.Web.Security;
+using DotNetOpenAuth.Web.Resources;
+
+namespace DotNetOpenAuth.Web
+{
+ internal static class OAuthAuthenticationTicketHelper
+ {
+ private const string OAuthCookieToken = "OAuth";
+
+ public static void SetAuthenticationTicket(HttpContextBase context, string userName, bool createPersistentCookie)
+ {
+ if (!context.Request.IsSecureConnection && FormsAuthentication.RequireSSL)
+ {
+ throw new HttpException(WebResources.ConnectionNotSecure);
+ }
+
+ HttpCookie cookie = GetAuthCookie(userName, createPersistentCookie);
+ context.Response.Cookies.Add(cookie);
+ }
+
+ public static bool IsOAuthAuthenticationTicket(HttpContextBase context)
+ {
+ HttpCookie cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
+ if (cookie == null)
+ {
+ return false;
+ }
+
+ string encryptedCookieData = cookie.Value;
+ if (String.IsNullOrEmpty(encryptedCookieData))
+ {
+ return false;
+ }
+
+ try
+ {
+ FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(encryptedCookieData);
+ return authTicket != null && !authTicket.Expired && authTicket.UserData == OAuthCookieToken;
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+ }
+
+ private static HttpCookie GetAuthCookie(string userName, bool createPersistentCookie)
+ {
+ Debug.Assert(!String.IsNullOrEmpty(userName));
+
+ var ticket = new FormsAuthenticationTicket(
+ /* version */ 2,
+ userName,
+ DateTime.Now,
+ DateTime.Now.Add(FormsAuthentication.Timeout),
+ createPersistentCookie,
+ OAuthCookieToken,
+ FormsAuthentication.FormsCookiePath);
+
+ string encryptedTicket = FormsAuthentication.Encrypt(ticket);
+ if (encryptedTicket == null || encryptedTicket.Length < 1)
+ {
+ throw new HttpException(WebResources.FailedToEncryptTicket);
+ }
+
+ var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
+ {
+ HttpOnly = true,
+ Path = FormsAuthentication.FormsCookiePath,
+ Secure = FormsAuthentication.RequireSSL
+ };
+
+ if (FormsAuthentication.CookieDomain != null)
+ {
+ cookie.Domain = FormsAuthentication.CookieDomain;
+ }
+
+ if (ticket.IsPersistent)
+ {
+ cookie.Expires = ticket.Expiration;
+ }
+
+ return cookie;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/OAuthWebSecurity.cs b/src/DotNetOpenAuth.Web/OAuthWebSecurity.cs
new file mode 100644
index 0000000..7a3f864
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/OAuthWebSecurity.cs
@@ -0,0 +1,386 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Threading;
+using System.Web;
+using DotNetOpenAuth.Messaging;
+using DotNetOpenAuth.Web.Clients;
+using DotNetOpenAuth.Web.Resources;
+
+namespace DotNetOpenAuth.Web
+{
+ /// <summary>
+ /// Contains APIs to manage authentication against OAuth & OpenID service providers
+ /// </summary>
+ public static class OAuthWebSecurity
+ {
+ private const string ProviderQueryStringName = "__provider__";
+
+ private static IOAuthDataProvider _oAuthDataProvider;
+ private static IOAuthDataProvider OAuthDataProvider
+ {
+ get
+ {
+ return _oAuthDataProvider;
+ }
+ }
+
+ // contains all registered authentication clients
+ private static readonly AuthenticationClientCollection _authenticationClients = new AuthenticationClientCollection();
+
+ public static void RegisterDataProvider(IOAuthDataProvider dataProvider)
+ {
+ if (dataProvider == null)
+ {
+ throw new ArgumentNullException("dataProvider");
+ }
+
+ var originalValue = Interlocked.CompareExchange(ref _oAuthDataProvider, dataProvider, null);
+ if (originalValue != null)
+ {
+ throw new InvalidOperationException(WebResources.OAuthDataProviderRegistered);
+ }
+ }
+
+ public static bool IsOAuthDataProviderRegistered
+ {
+ get
+ {
+ return OAuthDataProvider != null;
+ }
+ }
+
+ private static void EnsureDataProvider()
+ {
+ if (!IsOAuthDataProviderRegistered)
+ {
+ throw new InvalidOperationException(WebResources.OAuthDataProviderNotRegistered);
+ }
+ }
+
+ /// <summary>
+ /// Registers a supported OAuth client with the specified consumer key and consumer secret.
+ /// </summary>
+ /// <param name="client">One of the supported OAuth clients.</param>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="consumerSecret">The consumer secret.</param>
+ public static void RegisterOAuthClient(BuiltInOAuthClient client, string consumerKey, string consumerSecret)
+ {
+ IAuthenticationClient authenticationClient;
+ switch (client)
+ {
+ case BuiltInOAuthClient.LinkedIn:
+ authenticationClient = new LinkedInClient(consumerKey, consumerSecret);
+ break;
+
+ case BuiltInOAuthClient.Twitter:
+ authenticationClient = new TwitterClient(consumerKey, consumerSecret);
+ break;
+
+ case BuiltInOAuthClient.Facebook:
+ authenticationClient = new FacebookClient(consumerKey, consumerSecret);
+ break;
+
+ case BuiltInOAuthClient.WindowsLive:
+ authenticationClient = new WindowsLiveClient(consumerKey, consumerSecret);
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException("client");
+ }
+ RegisterClient(authenticationClient);
+ }
+
+ /// <summary>
+ /// Registers a supported OpenID client
+ /// </summary>
+ public static void RegisterOpenIDClient(BuiltInOpenIDClient openIDClient)
+ {
+ IAuthenticationClient client;
+ switch (openIDClient)
+ {
+ case BuiltInOpenIDClient.Google:
+ client = new GoogleOpenIdClient();
+ break;
+
+ case BuiltInOpenIDClient.Yahoo:
+ client = new YahooOpenIdClient();
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException("openIDClient");
+ }
+
+ RegisterClient(client);
+ }
+
+ /// <summary>
+ /// Registers an authentication client.
+ /// </summary>
+ public static void RegisterClient(IAuthenticationClient client)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException("client");
+ }
+
+ if (String.IsNullOrEmpty(client.ProviderName))
+ {
+ throw new ArgumentException(WebResources.InvalidServiceProviderName, "client");
+ }
+
+ if (_authenticationClients.Contains(client))
+ {
+ throw new ArgumentException(WebResources.ServiceProviderNameExists, "client");
+ }
+
+ _authenticationClients.Add(client);
+ }
+
+ /// <summary>
+ /// Requests the specified provider to start the authentication by directing users to an external website
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ public static void RequestAuthentication(string provider)
+ {
+ RequestAuthentication(provider, returnUrl: null);
+ }
+
+ /// <summary>
+ /// Requests the specified provider to start the authentication by directing users to an external website
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ /// <param name="returnUrl">The return url after user is authenticated.</param>
+ [SuppressMessage(
+ "Microsoft.Design",
+ "CA1054:UriParametersShouldNotBeStrings",
+ MessageId = "1#",
+ Justification = "We want to allow relative app path, and support ~/")]
+ public static void RequestAuthentication(string provider, string returnUrl)
+ {
+ if (HttpContext.Current == null)
+ {
+ throw new InvalidOperationException(WebResources.HttpContextNotAvailable);
+ }
+
+ RequestAuthenticationCore(new HttpContextWrapper(HttpContext.Current), provider, returnUrl);
+ }
+
+ internal static void RequestAuthenticationCore(HttpContextBase context, string provider, string returnUrl)
+ {
+ if (String.IsNullOrEmpty(provider))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "provider"),
+ "provider");
+ }
+
+ IAuthenticationClient client = GetOAuthClient(provider);
+
+ // convert returnUrl to an absolute path
+ Uri uri;
+ if (!String.IsNullOrEmpty(returnUrl))
+ {
+ uri = UriHelper.ConvertToAbsoluteUri(returnUrl);
+ }
+ else
+ {
+ uri = UriHelper.GetPublicFacingUrl(context.Request);
+ }
+ // attach the provider parameter so that we know which provider initiated
+ // the login when user is redirected back to this page
+ uri = uri.AttachQueryStringParameter(ProviderQueryStringName, provider);
+ client.RequestAuthentication(context, uri);
+ }
+
+ /// <summary>
+ /// Checks if user is successfully authenticated when user is redirected back to this user.
+ /// </summary>
+ /// <returns></returns>
+ public static AuthenticationResult VerifyAuthentication()
+ {
+ if (HttpContext.Current == null)
+ {
+ throw new InvalidOperationException(WebResources.HttpContextNotAvailable);
+ }
+
+ return VerifyAuthenticationCore(new HttpContextWrapper(HttpContext.Current));
+ }
+
+ internal static AuthenticationResult VerifyAuthenticationCore(HttpContextBase context)
+ {
+ string providerName = context.Request.QueryString[ProviderQueryStringName];
+ if (String.IsNullOrEmpty(providerName))
+ {
+ return AuthenticationResult.Failed;
+ }
+
+ IAuthenticationClient client;
+ if (TryGetOAuthClient(providerName, out client))
+ {
+ AuthenticationResult result = client.VerifyAuthentication(context);
+ if (!result.IsSuccessful)
+ {
+ // if the result is a Failed result, creates a new Failed response which has providerName info.
+ result = new AuthenticationResult(isSuccessful: false, provider: providerName, providerUserId: null,
+ userName: null, extraData: null);
+ }
+
+ return result;
+ }
+ else
+ {
+ throw new InvalidOperationException(WebResources.InvalidServiceProviderName);
+ }
+ }
+
+ /// <summary>
+ /// Checks if the specified provider user id represents a valid account.
+ /// If it does, log user in.
+ /// </summary>
+ /// <param name="providerName">Name of the provider.</param>
+ /// <param name="providerUserId">The provider user id.</param>
+ /// <returns><c>true</c> if the login is successful.</returns>
+ [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "Login is used more consistently in ASP.Net")]
+ public static bool Login(string providerName, string providerUserId, bool createPersistentCookie)
+ {
+ if (HttpContext.Current == null)
+ {
+ throw new InvalidOperationException(WebResources.HttpContextNotAvailable);
+ }
+
+ return LoginCore(new HttpContextWrapper(HttpContext.Current), providerName, providerUserId, createPersistentCookie);
+ }
+
+ internal static bool LoginCore(HttpContextBase context, string providerName, string providerUserId, bool createPersistentCookie)
+ {
+ EnsureDataProvider();
+
+ string userName = OAuthDataProvider.GetUserNameFromOAuth(providerName, providerUserId);
+ if (String.IsNullOrEmpty(userName))
+ {
+ return false;
+ }
+
+ OAuthAuthenticationTicketHelper.SetAuthenticationTicket(
+ context,
+ userName,
+ createPersistentCookie);
+ return true;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the current user is authenticated by an OAuth provider.
+ /// </summary>
+ public static bool IsAuthenticatedViaOAuth
+ {
+ get
+ {
+ if (HttpContext.Current == null)
+ {
+ throw new InvalidOperationException(WebResources.HttpContextNotAvailable);
+ }
+
+ return GetIsAuthenticatedViaOAuthCore(new HttpContextWrapper(HttpContext.Current));
+ }
+ }
+
+ internal static bool GetIsAuthenticatedViaOAuthCore(HttpContextBase context)
+ {
+ if (!context.Request.IsAuthenticated)
+ {
+ return false;
+ }
+ return OAuthAuthenticationTicketHelper.IsOAuthAuthenticationTicket(context);
+ }
+
+ /// <summary>
+ /// Creates or update the account with the specified provider, provider user id and associate it with the specified user name.
+ /// </summary>
+ /// <param name="providerName">Name of the provider.</param>
+ /// <param name="providerUserId">The provider user id.</param>
+ /// <param name="userName">The user name.</param>
+ public static void CreateOrUpdateAccount(string providerName, string providerUserId, string userName)
+ {
+ EnsureDataProvider();
+ OAuthDataProvider.CreateOrUpdateOAuthAccount(providerName, providerUserId, userName);
+ }
+
+ public static string GetUsername(string providerName, string providerUserId)
+ {
+ EnsureDataProvider();
+ return OAuthDataProvider.GetUserNameFromOAuth(providerName, providerUserId);
+ }
+
+ /// <summary>
+ /// Gets all OAuth & OpenID accounts which are associted with the specified user name.
+ /// </summary>
+ /// <param name="userName">The user name.</param>
+ public static ICollection<OAuthAccount> GetAccountsFromUserName(string userName)
+ {
+ if (String.IsNullOrEmpty(userName))
+ {
+ throw new ArgumentException(
+ String.Format(CultureInfo.CurrentCulture, WebResources.Argument_Cannot_Be_Null_Or_Empty, "userName"),
+ "userName");
+ }
+
+ EnsureDataProvider();
+
+ return OAuthDataProvider.GetOAuthAccountsFromUserName(userName);
+ }
+
+ /// <summary>
+ /// Delete the specified OAuth & OpenID account
+ /// </summary>
+ /// <param name="providerName">Name of the provider.</param>
+ /// <param name="providerUserId">The provider user id.</param>
+ public static void DeleteAccount(string providerName, string providerUserId)
+ {
+ EnsureDataProvider();
+
+ OAuthDataProvider.DeleteOAuthAccount(providerName, providerUserId);
+ }
+
+ internal static IAuthenticationClient GetOAuthClient(string providerName)
+ {
+ if (!_authenticationClients.Contains(providerName))
+ {
+ throw new ArgumentException(WebResources.ServiceProviderNotFound, "providerName");
+ }
+
+ return _authenticationClients[providerName];
+ }
+
+ internal static bool TryGetOAuthClient(string provider, out IAuthenticationClient client)
+ {
+ if (_authenticationClients.Contains(provider))
+ {
+ client = _authenticationClients[provider];
+ return true;
+ }
+ else
+ {
+ client = null;
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// for unit tests
+ /// </summary>
+ internal static void ClearProviders()
+ {
+ _authenticationClients.Clear();
+ }
+
+ /// <summary>
+ /// for unit tests
+ /// </summary>
+ internal static void ClearDataProvider()
+ {
+ _oAuthDataProvider = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Web/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..039cc1e
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DotNetOpenAuth.Web")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("DotNetOpenAuth.Web")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c89b7e57-2735-4407-bcb9-dfe9bb9493a2")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: InternalsVisibleTo("DotnetOpenAuth.Web.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Web/Resources/WebResources.Designer.cs b/src/DotNetOpenAuth.Web/Resources/WebResources.Designer.cs
new file mode 100644
index 0000000..e35278d
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Resources/WebResources.Designer.cs
@@ -0,0 +1,171 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.488
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Web.Resources {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class WebResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal WebResources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.Web.Resources.WebResources", typeof(WebResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to {0} cannot be null or an empty string..
+ /// </summary>
+ internal static string Argument_Cannot_Be_Null_Or_Empty {
+ get {
+ return ResourceManager.GetString("Argument_Cannot_Be_Null_Or_Empty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A setting in web.config requires a secure connection for this request but the current connection is not secured..
+ /// </summary>
+ internal static string ConnectionNotSecure {
+ get {
+ return ResourceManager.GetString("ConnectionNotSecure", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unable to encrypt the authentication ticket..
+ /// </summary>
+ internal static string FailedToEncryptTicket {
+ get {
+ return ResourceManager.GetString("FailedToEncryptTicket", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to HttpContext is not available in the current thread..
+ /// </summary>
+ internal static string HttpContextNotAvailable {
+ get {
+ return ResourceManager.GetString("HttpContextNotAvailable", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Invalid provider name..
+ /// </summary>
+ internal static string InvalidServiceProviderName {
+ get {
+ return ResourceManager.GetString("InvalidServiceProviderName", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An OAuth data provider has not been registered for this application..
+ /// </summary>
+ internal static string OAuthDataProviderNotRegistered {
+ get {
+ return ResourceManager.GetString("OAuthDataProviderNotRegistered", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An OAuth data provider has already been registered for this application..
+ /// </summary>
+ internal static string OAuthDataProviderRegistered {
+ get {
+ return ResourceManager.GetString("OAuthDataProviderRegistered", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Failed to obtain the authentication response from service provider..
+ /// </summary>
+ internal static string OpenIDFailedToGetResponse {
+ get {
+ return ResourceManager.GetString("OpenIDFailedToGetResponse", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Invalid identifier.
+ /// </summary>
+ internal static string OpenIDInvalidIdentifier {
+ get {
+ return ResourceManager.GetString("OpenIDInvalidIdentifier", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Another service provider with the same name has already been registered..
+ /// </summary>
+ internal static string ServiceProviderNameExists {
+ get {
+ return ResourceManager.GetString("ServiceProviderNameExists", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A service provider could not be found by the specified name..
+ /// </summary>
+ internal static string ServiceProviderNotFound {
+ get {
+ return ResourceManager.GetString("ServiceProviderNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Couldn&apos;t find a username with the provided credentials..
+ /// </summary>
+ internal static string UsernameNotFoundFromId {
+ get {
+ return ResourceManager.GetString("UsernameNotFoundFromId", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Web/Resources/WebResources.resx b/src/DotNetOpenAuth.Web/Resources/WebResources.resx
new file mode 100644
index 0000000..a4c0d5d
--- /dev/null
+++ b/src/DotNetOpenAuth.Web/Resources/WebResources.resx
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Argument_Cannot_Be_Null_Or_Empty" xml:space="preserve">
+ <value>{0} cannot be null or an empty string.</value>
+ </data>
+ <data name="ConnectionNotSecure" xml:space="preserve">
+ <value>A setting in web.config requires a secure connection for this request but the current connection is not secured.</value>
+ </data>
+ <data name="FailedToEncryptTicket" xml:space="preserve">
+ <value>Unable to encrypt the authentication ticket.</value>
+ </data>
+ <data name="HttpContextNotAvailable" xml:space="preserve">
+ <value>HttpContext is not available in the current thread.</value>
+ </data>
+ <data name="InvalidServiceProviderName" xml:space="preserve">
+ <value>Invalid provider name.</value>
+ </data>
+ <data name="OAuthDataProviderNotRegistered" xml:space="preserve">
+ <value>An OAuth data provider has not been registered for this application.</value>
+ </data>
+ <data name="OAuthDataProviderRegistered" xml:space="preserve">
+ <value>An OAuth data provider has already been registered for this application.</value>
+ </data>
+ <data name="OpenIDFailedToGetResponse" xml:space="preserve">
+ <value>Failed to obtain the authentication response from service provider.</value>
+ </data>
+ <data name="OpenIDInvalidIdentifier" xml:space="preserve">
+ <value>Invalid identifier</value>
+ </data>
+ <data name="ServiceProviderNameExists" xml:space="preserve">
+ <value>Another service provider with the same name has already been registered.</value>
+ </data>
+ <data name="ServiceProviderNotFound" xml:space="preserve">
+ <value>A service provider could not be found by the specified name.</value>
+ </data>
+ <data name="UsernameNotFoundFromId" xml:space="preserve">
+ <value>Couldn't find a username with the provided credentials.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.WebPages/DotNetOpenAuth.WebPages.csproj b/src/DotNetOpenAuth.WebPages/DotNetOpenAuth.WebPages.csproj
new file mode 100644
index 0000000..01d6166
--- /dev/null
+++ b/src/DotNetOpenAuth.WebPages/DotNetOpenAuth.WebPages.csproj
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " />
+ <PropertyGroup>
+ <StyleCopEnabled>False</StyleCopEnabled>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth.WebPages</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.WebPages</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\..\bin\v4.0\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <CodeAnalysisRuleSet>ExtendedDesignGuidelineRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\bin\v4.0\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" />
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" />
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.ApplicationServices" />
+ <Reference Include="System.Web.Razor, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\System.Web.Razor.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Web.WebPages, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\System.Web.WebPages.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Web.WebPages.Administration, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\System.Web.WebPages.Administration.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Web.WebPages.Deployment, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\System.Web.WebPages.Deployment.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Web.WebPages.Razor, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\System.Web.WebPages.Razor.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ <Reference Include="WebMatrix.Data, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\WebMatrix.Data.dll</HintPath>
+ </Reference>
+ <Reference Include="WebMatrix.WebData, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\WPF\WebStackRuntime\Runtime\bin\Release\WebMatrix.WebData.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="PreApplicationStartCode.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="WebPagesOAuthDataProvider.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\DotNetOpenAuth.Web\DotNetOpenAuth.Web.csproj">
+ <Project>{51835086-9611-4C53-819B-F2D5C9320873}</Project>
+ <Name>DotNetOpenAuth.Web</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " />
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.WebPages/PreApplicationStartCode.cs b/src/DotNetOpenAuth.WebPages/PreApplicationStartCode.cs
new file mode 100644
index 0000000..c96173a
--- /dev/null
+++ b/src/DotNetOpenAuth.WebPages/PreApplicationStartCode.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel;
+using System.Web.WebPages.Razor;
+using DotNetOpenAuth.Web;
+
+namespace DotNetOpenAuth.WebPages
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class PreApplicationStartCode
+ {
+ public static void Start()
+ {
+ WebPageRazorHost.AddGlobalImport("DotNetOpenAuth.Web");
+ OAuthWebSecurity.RegisterDataProvider(new WebPagesOAuthDataProvider());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.WebPages/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.WebPages/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..96b3016
--- /dev/null
+++ b/src/DotNetOpenAuth.WebPages/Properties/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Web;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DotNetOpenAuth.WebPages")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("DotNetOpenAuth.WebPages")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c814e5db-c38b-4a8b-9a99-3b851c356ce5")]
+
+[assembly: AssemblyFileVersion("1.0.0.0")]
+
+[assembly: PreApplicationStartMethod(typeof(DotNetOpenAuth.WebPages.PreApplicationStartCode), "Start")] \ No newline at end of file
diff --git a/src/DotNetOpenAuth.WebPages/WebPagesOAuthDataProvider.cs b/src/DotNetOpenAuth.WebPages/WebPagesOAuthDataProvider.cs
new file mode 100644
index 0000000..b8895c0
--- /dev/null
+++ b/src/DotNetOpenAuth.WebPages/WebPagesOAuthDataProvider.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Security;
+using DotNetOpenAuth.Web;
+using WebMatrix.WebData;
+
+namespace DotNetOpenAuth.WebPages
+{
+ internal class WebPagesOAuthDataProvider : IOAuthDataProvider
+ {
+ public WebPagesOAuthDataProvider()
+ {
+ }
+
+ private static ExtendedMembershipProvider VerifyProvider()
+ {
+ var provider = Membership.Provider as ExtendedMembershipProvider;
+ if (provider == null)
+ {
+ throw new InvalidOperationException();
+ }
+ return provider;
+ }
+
+ public string GetUserNameFromOAuth(string oAuthProvider, string oAuthId)
+ {
+ ExtendedMembershipProvider provider = VerifyProvider();
+
+ int userId = provider.GetUserIdFromOAuth(oAuthProvider, oAuthId);
+ if (userId == -1) {
+ return null;
+ }
+
+ return provider.GetUserNameFromId(userId);
+ }
+
+ public void CreateOrUpdateOAuthAccount(string oAuthProvider, string oAuthId, string username)
+ {
+ ExtendedMembershipProvider provider = VerifyProvider();
+ provider.CreateOrUpdateOAuthAccount(oAuthProvider, oAuthId, username);
+ }
+
+ public bool DeleteOAuthAccount(string oAuthProvider, string oAuthId)
+ {
+ ExtendedMembershipProvider provider = VerifyProvider();
+
+ string username = GetUserNameFromOAuth(oAuthProvider, oAuthId);
+ if (String.IsNullOrEmpty(username))
+ {
+ // account doesn't exist
+ return false;
+ }
+
+ provider.DeleteOAuthAccount(oAuthProvider, oAuthId);
+ return true;
+ }
+
+ public ICollection<OAuthAccount> GetOAuthAccountsFromUserName(string userName)
+ {
+ ExtendedMembershipProvider provider = VerifyProvider();
+ return provider.GetAccountsForUser(userName).Select(p => new OAuthAccount(p.Provider, p.ProviderUserId)).ToList();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln
index 505ce7e..eae5e98 100644
--- a/src/DotNetOpenAuth.sln
+++ b/src/DotNetOpenAuth.sln
@@ -202,6 +202,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Combinations", "Combination
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenIdInfoCard.UI", "DotNetOpenAuth.OpenIdInfoCard.UI\DotNetOpenAuth.OpenIdInfoCard.UI.csproj", "{3A8347E8-59A5-4092-8842-95C75D7D2F36}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Web", "DotNetOpenAuth.Web\DotNetOpenAuth.Web.csproj", "{51835086-9611-4C53-819B-F2D5C9320873}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.WebPages", "DotNetOpenAuth.WebPages\DotNetOpenAuth.WebPages.csproj", "{AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Web.Test", "DotNetOpenAuth.Web.Test\DotNetOpenAuth.Web.Test.csproj", "{C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeAnalysis|Any CPU = CodeAnalysis|Any CPU
@@ -224,13 +230,6 @@ Global
{AA78D112-D889-414B-A7D4-467B34C7B663}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA78D112-D889-414B-A7D4-467B34C7B663}.Release|Any CPU.Build.0 = Release|Any CPU
{AA78D112-D889-414B-A7D4-467B34C7B663}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Release|Any CPU.ActiveCfg = Debug|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Release|Any CPU.Build.0 = Debug|Any CPU
- {47A84EF7-68C3-4D47-926A-9CCEA6518531}.ReleaseNoUI|Any CPU.ActiveCfg = Debug|Any CPU
{2A59DE0A-B76A-4B42-9A33-04D34548353D}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
{2A59DE0A-B76A-4B42-9A33-04D34548353D}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
{2A59DE0A-B76A-4B42-9A33-04D34548353D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -245,13 +244,6 @@ Global
{AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.Build.0 = Release|Any CPU
{AEA29D4D-396F-47F6-BC81-B58D4B855245}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.ActiveCfg = Debug|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.Build.0 = Debug|Any CPU
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.ReleaseNoUI|Any CPU.ActiveCfg = Debug|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
{07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -529,6 +521,30 @@ Global
{3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.Build.0 = Release|Any CPU
{3A8347E8-59A5-4092-8842-95C75D7D2F36}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU
{3A8347E8-59A5-4092-8842-95C75D7D2F36}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.Release|Any CPU.Build.0 = Release|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU
+ {51835086-9611-4C53-819B-F2D5C9320873}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.ReleaseNoUI|Any CPU.ActiveCfg = Release|Any CPU
+ {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.ReleaseNoUI|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -551,7 +567,6 @@ Global
{9529606E-AF76-4387-BFB7-3D10A5B399AA} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
{E135F455-0669-49F8-9207-07FCA8C8FC79} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
{C78E8235-1D46-43EB-A912-80B522C4E9AE} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6}
- {6EB90284-BD15-461C-BBF2-131CF55F7C8B} = {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277}
{5C65603B-235F-47E6-B536-06385C60DE7F} = {E9ED920D-1F83-48C0-9A4B-09CCE505FE6D}
{A78F8FC6-7B03-4230-BE41-761E400D6810} = {B9EB8729-4B54-4453-B089-FE6761BA3057}
{17932639-1F50-48AF-B0A5-E2BF832F82CC} = {B9EB8729-4B54-4453-B089-FE6761BA3057}
@@ -564,6 +579,8 @@ Global
{173E7B8D-E751-46E2-A133-F72297C0D2F4} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
{60426312-6AE5-4835-8667-37EDEA670222} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
{57A7DD35-666C-4FA3-9A1B-38961E50CA27} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
+ {51835086-9611-4C53-819B-F2D5C9320873} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
+ {AE031B0C-710B-4D5B-BDF5-F21DFC9092D8} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
{F8284738-3B5D-4733-A511-38C23F4A763F} = {C7EF1823-3AA7-477E-8476-28929F5C05D2}
{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B} = {C7EF1823-3AA7-477E-8476-28929F5C05D2}
{F4CD3C04-6037-4946-B7A5-34BFC96A75D2} = {C7EF1823-3AA7-477E-8476-28929F5C05D2}