summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-03-01 19:47:05 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2012-03-01 19:47:05 -0800
commit6d18e14045cc9a8c0926e037ede82fc2d7818079 (patch)
tree07a9ba5991e2bd09b6cdf4a3e53dfd295c017a75
parent3ed1a19d2ba373869e5d1aa285726f3b5b99d0c2 (diff)
parent2137afe38f2c403892d479bf1761d40f444ff141 (diff)
downloadDotNetOpenAuth-6d18e14045cc9a8c0926e037ede82fc2d7818079.zip
DotNetOpenAuth-6d18e14045cc9a8c0926e037ede82fc2d7818079.tar.gz
DotNetOpenAuth-6d18e14045cc9a8c0926e037ede82fc2d7818079.tar.bz2
Merge branch 'webpages'
-rw-r--r--nuget/DotNetOpenAuth.AspNet.nuspec26
-rw-r--r--nuget/nuget.proj9
-rw-r--r--src/DotNetOpenAuth.AspNet.Test/DotNetOpenAuth.AspNet.Test.csproj79
-rw-r--r--src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs127
-rw-r--r--src/DotNetOpenAuth.AspNet.Test/OAuthAuthenticationTickerHelperTest.cs143
-rw-r--r--src/DotNetOpenAuth.AspNet.Test/OAuthClientTest.cs130
-rw-r--r--src/DotNetOpenAuth.AspNet.Test/Properties/AssemblyInfo.cs35
-rw-r--r--src/DotNetOpenAuth.AspNet.Test/UriHelperTest.cs37
-rw-r--r--src/DotNetOpenAuth.AspNet/AuthenticationResult.cs102
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs39
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs39
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs12
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs117
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs85
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs109
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs84
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs91
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookGraphData.cs34
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/JsonHelper.cs16
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2AccessTokenData.cs18
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs122
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/WindowsLiveClient.cs95
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/WindowsLiveUserData.cs34
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs49
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs127
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs42
-rw-r--r--src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj105
-rw-r--r--src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs31
-rw-r--r--src/DotNetOpenAuth.AspNet/IOpenAuthDataProvider.cs5
-rw-r--r--src/DotNetOpenAuth.AspNet/OpenAuthAuthenticationTicketHelper.cs73
-rw-r--r--src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs112
-rw-r--r--src/DotNetOpenAuth.AspNet/Properties/AssemblyInfo.cs29
-rw-r--r--src/DotNetOpenAuth.AspNet/Resources/WebResources.Designer.cs117
-rw-r--r--src/DotNetOpenAuth.AspNet/Resources/WebResources.resx138
-rw-r--r--src/DotNetOpenAuth.AspNet/UriHelper.cs66
-rw-r--r--src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln1
-rw-r--r--src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj1
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs33
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/ReadOnlyDictionary.cs224
-rw-r--r--src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs4
-rw-r--r--src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs2
-rw-r--r--src/DotNetOpenAuth.sln5
42 files changed, 2742 insertions, 5 deletions
diff --git a/nuget/DotNetOpenAuth.AspNet.nuspec b/nuget/DotNetOpenAuth.AspNet.nuspec
new file mode 100644
index 0000000..c518176
--- /dev/null
+++ b/nuget/DotNetOpenAuth.AspNet.nuspec
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
+ <metadata>
+ <id>DotNetOpenAuth.AspNet</id>
+ <version>$version$</version>
+ <title>DotNetOpenAuth extensions for ASP.NET (WebPages)</title>
+ <authors>Luan Nguyen</authors>
+ <owners>Outercurve Foundation</owners>
+ <projectUrl>http://www.dotnetopenauth.net/</projectUrl>
+ <iconUrl>https://github.com/AArnott/dotnetopenid/raw/v3.4/doc/logo/dnoa-logo_32x32.png</iconUrl>
+ <licenseUrl>http://www.opensource.org/licenses/ms-pl.html</licenseUrl>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <description>Allows adding OAuth and OpenID authentication to ASP.NET web projects using DotNetOpenAuth implementation.</description>
+ <dependencies>
+ <dependency id="DotNetOpenAuth.OpenId.RelyingParty" version="[$version$]" />
+ <dependency id="DotNetOpenAuth.OAuth.Consumer" version="[$version$]" />
+ </dependencies>
+ </metadata>
+ <files>
+ <file src="$OutputPath40$signed\DotNetOpenAuth.AspNet.dll" target="lib\net40-full\DotNetOpenAuth.AspNet.dll" />
+ <file src="$OutputPath40$DotNetOpenAuth.AspNet.pdb" target="lib\net40-full\DotNetOpenAuth.AspNet.pdb" />
+ <file src="$OutputPath40$DotNetOpenAuth.AspNet.xml" target="lib\net40-full\DotNetOpenAuth.AspNet.xml" />
+
+ <file src="..\src\DotNetOpenAuth.AspNet\**\*.cs" target="src" />
+ </files>
+</package> \ No newline at end of file
diff --git a/nuget/nuget.proj b/nuget/nuget.proj
index bf86b1c..faf4bae 100644
--- a/nuget/nuget.proj
+++ b/nuget/nuget.proj
@@ -7,6 +7,8 @@
<ItemGroup>
<ProductTargets Include="BuildUnifiedProduct;ReSignDelaySignedAssemblies" Condition=" '$(SkipNugetDependenciesBuild)' != 'true' " />
<ProductTargets Include="GetOutputPath" />
+ <AspNetTargets Include="Build;Sign" Condition=" '$(SkipNugetDependenciesBuild)' != 'true' " />
+ <AspNetTargets Include="GetOutputPath" />
</ItemGroup>
<!-- We build the entire unified, signed product targeting both CLRs, since NuGet supports packages that contain both,
@@ -25,6 +27,13 @@
BuildInParallel="$(BuildInParallel)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputs40"/>
</MSBuild>
+ <MSBuild
+ Projects="$(ProjectRoot)src\DotNetOpenAuth.AspNet\DotNetOpenAuth.AspNet.csproj"
+ Targets="@(AspNetTargets)"
+ Properties="TargetFrameworkVersion=v4.0"
+ BuildInParallel="$(BuildInParallel)">
+ <Output TaskParameter="TargetOutputs" ItemName="TargetOutputs40"/>
+ </MSBuild>
<ItemGroup>
<ResignedAssembliesOutputs Include="@(TargetOutputs35)" Condition=" '%(MSBuildSourceTargetName)' == 'Sign' ">
diff --git a/src/DotNetOpenAuth.AspNet.Test/DotNetOpenAuth.AspNet.Test.csproj b/src/DotNetOpenAuth.AspNet.Test/DotNetOpenAuth.AspNet.Test.csproj
new file mode 100644
index 0000000..dd7228f
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet.Test/DotNetOpenAuth.AspNet.Test.csproj
@@ -0,0 +1,79 @@
+<?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.AspNet.Test</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.AspNet.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="Properties\AssemblyInfo.cs" />
+ <Compile Include="UriHelperTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj">
+ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project>
+ <Name>DotNetOpenAuth.Core</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.OAuth\DotNetOpenAuth.OAuth.csproj">
+ <Project>{A288FCC8-6FCF-46DA-A45E-5F9281556361}</Project>
+ <Name>DotNetOpenAuth.OAuth</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.AspNet\DotNetOpenAuth.AspNet.csproj">
+ <Project>{51835086-9611-4C53-819B-F2D5C9320873}</Project>
+ <Name>DotNetOpenAuth.AspNet</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.AspNet.Test/OAuth2ClientTest.cs b/src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs
new file mode 100644
index 0000000..d24cf77
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet.Test/OAuth2ClientTest.cs
@@ -0,0 +1,127 @@
+namespace DotNetOpenAuth.AspNet.Test {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Web;
+ using DotNetOpenAuth.AspNet.Clients;
+ using Moq;
+ using NUnit.Framework;
+
+ [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.AspNet.Test/OAuthAuthenticationTickerHelperTest.cs b/src/DotNetOpenAuth.AspNet.Test/OAuthAuthenticationTickerHelperTest.cs
new file mode 100644
index 0000000..78b8ca6
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet.Test/OAuthAuthenticationTickerHelperTest.cs
@@ -0,0 +1,143 @@
+namespace DotNetOpenAuth.Test.Web {
+ using System;
+ using System.Web;
+ using System.Web.Security;
+ using DotNetOpenAuth.AspNet;
+ using Moq;
+ using NUnit.Framework;
+
+ [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 = OpenAuthAuthenticationTicketHelper.IsValidAuthenticationTicket(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 = OpenAuthAuthenticationTicketHelper.IsValidAuthenticationTicket(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 = OpenAuthAuthenticationTicketHelper.IsValidAuthenticationTicket(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 = OpenAuthAuthenticationTicketHelper.IsValidAuthenticationTicket(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
+ OpenAuthAuthenticationTicketHelper.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.AspNet.Test/OAuthClientTest.cs b/src/DotNetOpenAuth.AspNet.Test/OAuthClientTest.cs
new file mode 100644
index 0000000..bec7f8a
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet.Test/OAuthClientTest.cs
@@ -0,0 +1,130 @@
+namespace DotNetOpenAuth.AspNet.Test {
+ using System;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.Messages;
+ using DotNetOpenAuth.AspNet.Clients;
+ using Moq;
+ using NUnit.Framework;
+ using DotNetOpenAuth.AspNet;
+
+ [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.AspNet.Test/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.AspNet.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..3c9f4f6
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet.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.AspNet.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft IT")]
+[assembly: AssemblyProduct("DotNetOpenAuth.AspNet.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.AspNet.Test/UriHelperTest.cs b/src/DotNetOpenAuth.AspNet.Test/UriHelperTest.cs
new file mode 100644
index 0000000..ea78a76
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet.Test/UriHelperTest.cs
@@ -0,0 +1,37 @@
+namespace DotNetOpenAuth.AspNet.Test {
+ using System;
+ using DotNetOpenAuth.AspNet.Clients;
+ using NUnit.Framework;
+
+ [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());
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs b/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs
new file mode 100644
index 0000000..9656a56
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/AuthenticationResult.cs
@@ -0,0 +1,102 @@
+namespace DotNetOpenAuth.AspNet {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOpenAuth.Messaging;
+
+ /// <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; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationResult"/> class.
+ /// </summary>
+ /// <param name="isSuccessful">if set to <c>true</c> [is successful].</param>
+ public AuthenticationResult(bool isSuccessful) :
+ this(isSuccessful,
+ provider: null,
+ providerUserId: null,
+ userName: null,
+ extraData: null) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationResult"/> class.
+ /// </summary>
+ /// <param name="exception">The exception.</param>
+ public AuthenticationResult(Exception exception)
+ : this(isSuccessful: false) {
+ if (exception == null) {
+ throw new ArgumentNullException("exception");
+ }
+
+ Error = exception;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationResult"/> class.
+ /// </summary>
+ /// <param name="isSuccessful">if set to <c>true</c> [is successful].</param>
+ /// <param name="provider">The provider.</param>
+ /// <param name="providerUserId">The provider user id.</param>
+ /// <param name="userName">Name of the user.</param>
+ /// <param name="extraData">The extra data.</param>
+ public AuthenticationResult(
+ bool isSuccessful,
+ string provider,
+ string providerUserId,
+ string userName,
+ IDictionary<string, string> extraData) {
+ IsSuccessful = isSuccessful;
+ Provider = provider;
+ ProviderUserId = providerUserId;
+ UserName = userName;
+ if (extraData != null) {
+ // wrap extraData in a read-only dictionary
+ ExtraData = new ReadOnlyDictionary<string, string>(extraData);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs b/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs
new file mode 100644
index 0000000..ce7891d
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/DictionaryExtensions.cs
@@ -0,0 +1,39 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.Xml.Linq;
+
+ 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.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs
new file mode 100644
index 0000000..5ad709c
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/DotNetOpenAuthWebConsumer.cs
@@ -0,0 +1,39 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.Net;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ 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.AspNet/Clients/OAuth/IOAuthWebWorker.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs
new file mode 100644
index 0000000..cca8298
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/IOAuthWebWorker.cs
@@ -0,0 +1,12 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Net;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ 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.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs
new file mode 100644
index 0000000..9dbf3d8
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/InMemoryOAuthTokenManager.cs
@@ -0,0 +1,117 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ /// <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.AspNet/Clients/OAuth/LinkedInClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs
new file mode 100644
index 0000000..53578ab
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/LinkedInClient.cs
@@ -0,0 +1,85 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ 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;
+
+ /// <summary>
+ /// Represents LinkedIn authentication client.
+ /// </summary>
+ public 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.AspNet/Clients/OAuth/OAuthClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs
new file mode 100644
index 0000000..5e57f6e
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/OAuthClient.cs
@@ -0,0 +1,109 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ /// <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;
+ }
+
+ // add the access token to the user data dictionary just in case page developers want to use it
+ AuthenticationResult result = VerifyAuthenticationCore(response);
+ if (result.IsSuccessful && result.ExtraData != null)
+ {
+ result.ExtraData["accesstoken"] = response.AccessToken;
+ }
+
+ return result;
+ }
+
+ /// <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.AspNet/Clients/OAuth/TwitterClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs
new file mode 100644
index 0000000..c73deed
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth/TwitterClient.cs
@@ -0,0 +1,84 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ 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;
+
+ /// <summary>
+ /// Represents a Twitter client
+ /// </summary>
+ public 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.AspNet/Clients/OAuth2/FacebookClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs
new file mode 100644
index 0000000..623d595
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs
@@ -0,0 +1,91 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Net;
+ using System.Web;
+ using DotNetOpenAuth.AspNet.Resources;
+ using DotNetOpenAuth.Messaging;
+
+ public 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);
+ MessagingUtilities.AppendQueryArgs(builder,
+ new KeyValuePair<string, string>[] {
+ new KeyValuePair<string, string>("client_id", _appId),
+ new KeyValuePair<string, string>("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);
+ MessagingUtilities.AppendQueryArgs(builder,
+ new KeyValuePair<string, string>[] {
+ new KeyValuePair<string, string>("client_id", _appId),
+ new KeyValuePair<string, string>("redirect_uri", returnUrl.ToString()),
+ new KeyValuePair<string, string>("client_secret", _appSecret),
+ new KeyValuePair<string, string>("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) {
+ FacebookGraphData graphData;
+ var request = WebRequest.Create("https://graph.facebook.com/me?access_token=" + Uri.EscapeDataString(accessToken));
+ using (var response = request.GetResponse()) {
+ using (var responseStream = response.GetResponseStream()) {
+ graphData = JsonHelper.Deserialize<FacebookGraphData>(responseStream);
+ }
+ }
+
+ // this dictionary must contains
+ var userData = new Dictionary<string, string>();
+ userData.AddItemIfNotEmpty("id", graphData.Id);
+ userData.AddItemIfNotEmpty("username", graphData.Email);
+ userData.AddItemIfNotEmpty("name", graphData.Name);
+ userData.AddItemIfNotEmpty("link", graphData.Link == null ? null : graphData.Link.ToString());
+ userData.AddItemIfNotEmpty("gender", graphData.Gender);
+ userData.AddItemIfNotEmpty("birthday", graphData.Birthday);
+ return userData;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookGraphData.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookGraphData.cs
new file mode 100644
index 0000000..a2605bf
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookGraphData.cs
@@ -0,0 +1,34 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Runtime.Serialization;
+ using System.ComponentModel;
+
+ /// <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]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class FacebookGraphData {
+ [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.AspNet/Clients/OAuth2/JsonHelper.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/JsonHelper.cs
new file mode 100644
index 0000000..bc8af46
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/JsonHelper.cs
@@ -0,0 +1,16 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.IO;
+ using System.Runtime.Serialization.Json;
+
+ 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.AspNet/Clients/OAuth2/OAuth2AccessTokenData.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2AccessTokenData.cs
new file mode 100644
index 0000000..7cb902e
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2AccessTokenData.cs
@@ -0,0 +1,18 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System.Runtime.Serialization;
+
+ [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.AspNet/Clients/OAuth2/OAuth2Client.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs
new file mode 100644
index 0000000..ff50521
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/OAuth2Client.cs
@@ -0,0 +1,122 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Web;
+
+ /// <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;
+ }
+
+ // add the access token to the user data dictionary just in case page developers want to use it
+ userData["accesstoken"] = accessToken;
+
+ 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.AspNet/Clients/OAuth2/WindowsLiveClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/WindowsLiveClient.cs
new file mode 100644
index 0000000..bddb801
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/WindowsLiveClient.cs
@@ -0,0 +1,95 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ public 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);
+ MessagingUtilities.AppendQueryArgs(builder,
+ new KeyValuePair<string, string>[]
+ {
+ new KeyValuePair<string, string>("client_id", _appId),
+ new KeyValuePair<string, string>("scope", "wl.basic"),
+ new KeyValuePair<string, string>("response_type", "code"),
+ new KeyValuePair<string, string>("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.AspNet/Clients/OAuth2/WindowsLiveUserData.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/WindowsLiveUserData.cs
new file mode 100644
index 0000000..8147e2f
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/WindowsLiveUserData.cs
@@ -0,0 +1,34 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Runtime.Serialization;
+ using System.ComponentModel;
+
+ /// <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]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ 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.AspNet/Clients/OpenID/GoogleOpenIdClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs
new file mode 100644
index 0000000..654fff6
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OpenID/GoogleOpenIdClient.cs
@@ -0,0 +1,49 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System.Collections.Generic;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Represents Google OpenID client.
+ /// </summary>
+ public 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(WellKnownAttributes.Name.First, isRequired: false));
+ fetchRequest.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.Last, 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(WellKnownAttributes.Name.First));
+ extraData.AddItemIfNotEmpty("lastName", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Last));
+
+ return extraData;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs
new file mode 100644
index 0000000..4bfb87e
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OpenID/OpenIDClient.cs
@@ -0,0 +1,127 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Web;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.AspNet.Resources;
+
+ /// <summary>
+ /// Base classes for OpenID clients.
+ /// </summary>
+ public 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="context">The context of the current request.</param>
+ /// <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>
+ /// <param name="context">The context of the current request.</param>
+ /// <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.AspNet/Clients/OpenID/YahooOpenIdClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs
new file mode 100644
index 0000000..dcf6a13
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OpenID/YahooOpenIdClient.cs
@@ -0,0 +1,42 @@
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System.Collections.Generic;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ public 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(WellKnownAttributes.Name.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(WellKnownAttributes.Name.FullName));
+
+ return extraData;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj
new file mode 100644
index 0000000..e27febc
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj
@@ -0,0 +1,105 @@
+<?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>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{51835086-9611-4C53-819B-F2D5C9320873}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth.AspNet</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.AspNet</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <CodeAnalysisRuleSet>ExtendedDesignGuidelineRules.ruleset</CodeAnalysisRuleSet>
+ </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>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\bin\v4.0\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ </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="Clients\DictionaryExtensions.cs" />
+ <Compile Include="IAuthenticationClient.cs" />
+ <Compile Include="Clients\OAuth2\FacebookClient.cs" />
+ <Compile Include="Clients\OAuth2\FacebookGraphData.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\GoogleOpenIdClient.cs" />
+ <Compile Include="Clients\OpenID\OpenIDClient.cs" />
+ <Compile Include="Clients\OpenID\YahooOpenIdClient.cs" />
+ <Compile Include="UriHelper.cs" />
+ <Compile Include="IOpenAuthDataProvider.cs" />
+ <Compile Include="OpenAuthAuthenticationTicketHelper.cs" />
+ <Compile Include="OpenAuthSecurityManager.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.Core\DotNetOpenAuth.Core.csproj">
+ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project>
+ <Name>DotNetOpenAuth.Core</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.AspNet/IAuthenticationClient.cs b/src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs
new file mode 100644
index 0000000..b5745c3
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/IAuthenticationClient.cs
@@ -0,0 +1,31 @@
+namespace DotNetOpenAuth.AspNet {
+ using System;
+ using System.Web;
+
+ /// <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.AspNet/IOpenAuthDataProvider.cs b/src/DotNetOpenAuth.AspNet/IOpenAuthDataProvider.cs
new file mode 100644
index 0000000..5eb19a5
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/IOpenAuthDataProvider.cs
@@ -0,0 +1,5 @@
+namespace DotNetOpenAuth.AspNet {
+ public interface IOpenAuthDataProvider {
+ string GetUserNameFromOpenAuth(string openAuthProvider, string openAuthId);
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/OpenAuthAuthenticationTicketHelper.cs b/src/DotNetOpenAuth.AspNet/OpenAuthAuthenticationTicketHelper.cs
new file mode 100644
index 0000000..22c1556
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/OpenAuthAuthenticationTicketHelper.cs
@@ -0,0 +1,73 @@
+namespace DotNetOpenAuth.AspNet {
+ using System;
+ using System.Diagnostics;
+ using System.Web;
+ using System.Web.Security;
+ using DotNetOpenAuth.AspNet.Resources;
+
+ internal static class OpenAuthAuthenticationTicketHelper {
+ private const string OpenAuthCookieToken = "OpenAuth";
+
+ 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 IsValidAuthenticationTicket(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 == OpenAuthCookieToken;
+ } 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,
+ OpenAuthCookieToken,
+ 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.AspNet/OpenAuthSecurityManager.cs b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
new file mode 100644
index 0000000..188307f
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/OpenAuthSecurityManager.cs
@@ -0,0 +1,112 @@
+namespace DotNetOpenAuth.AspNet {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Manage authenticating with an external OAuth or OpenID provider
+ /// </summary>
+ public class OpenAuthSecurityManager {
+ private const string ProviderQueryStringName = "__provider__";
+
+ private readonly HttpContextBase _requestContext;
+ private readonly IOpenAuthDataProvider _dataProvider;
+ private readonly IAuthenticationClient _authenticationProvider;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenAuthSecurityManager"/> class.
+ /// </summary>
+ /// <param name="requestContext">The request context.</param>
+ public OpenAuthSecurityManager(HttpContextBase requestContext) :
+ this(requestContext, provider: null, dataProvider: null) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenAuthSecurityManager"/> class.
+ /// </summary>
+ /// <param name="requestContext">The request context.</param>
+ /// <param name="provider">The provider.</param>
+ /// <param name="dataProvider">The data provider.</param>
+ public OpenAuthSecurityManager(HttpContextBase requestContext, IAuthenticationClient provider, IOpenAuthDataProvider dataProvider) {
+ if (requestContext == null) {
+ throw new ArgumentNullException("requestContext");
+ }
+
+ _requestContext = requestContext;
+ _dataProvider = dataProvider;
+ _authenticationProvider = provider;
+ }
+
+ /// <summary>
+ /// Requests the specified provider to start the authentication by directing users to an external website
+ /// </summary>
+ /// <param name="returnUrl">The return url after user is authenticated.</param>
+ public void RequestAuthentication(string returnUrl) {
+ // convert returnUrl to an absolute path
+ Uri uri;
+ if (!String.IsNullOrEmpty(returnUrl)) {
+ uri = UriHelper.ConvertToAbsoluteUri(returnUrl, _requestContext);
+ } else {
+ uri = HttpRequestInfo.GetPublicFacingUrl(_requestContext.Request, _requestContext.Request.ServerVariables);
+ }
+ // 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, _authenticationProvider.ProviderName);
+ _authenticationProvider.RequestAuthentication(_requestContext, uri);
+ }
+
+ public static string GetProviderName(HttpContextBase context) {
+ return context.Request.QueryString[ProviderQueryStringName];
+ }
+
+ /// <summary>
+ /// Checks if user is successfully authenticated when user is redirected back to this user.
+ /// </summary>
+ /// <returns></returns>
+ public AuthenticationResult VerifyAuthentication() {
+ AuthenticationResult result = _authenticationProvider.VerifyAuthentication(_requestContext);
+ 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: _authenticationProvider.ProviderName,
+ providerUserId: null,
+ userName: null,
+ extraData: null);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Checks if the specified provider user id represents a valid account.
+ /// If it does, log user in.
+ /// </summary>
+ /// <param name="providerUserId">The provider user id.</param>
+ /// <param name="createPersistentCookie">if set to <c>true</c> create persistent cookie.</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 bool Login(string providerUserId, bool createPersistentCookie) {
+ string userName = _dataProvider.GetUserNameFromOpenAuth(_authenticationProvider.ProviderName, providerUserId);
+ if (String.IsNullOrEmpty(userName)) {
+ return false;
+ }
+
+ OpenAuthAuthenticationTicketHelper.SetAuthenticationTicket(_requestContext, userName, createPersistentCookie);
+ return true;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the current user is authenticated by an OAuth & OpenID provider.
+ /// </summary>
+ public bool IsAuthenticatedWithOpenAuth {
+ get {
+ return _requestContext.Request.IsAuthenticated &&
+ OpenAuthAuthenticationTicketHelper.IsValidAuthenticationTicket(_requestContext);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.AspNet/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..30776c7
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+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.AspNet")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("DotNetOpenAuth.AspNet")]
+[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")]
+
+#if StrongNameSigned
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+#else
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test")]
+#endif \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/Resources/WebResources.Designer.cs b/src/DotNetOpenAuth.AspNet/Resources/WebResources.Designer.cs
new file mode 100644
index 0000000..624aa2b
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Resources/WebResources.Designer.cs
@@ -0,0 +1,117 @@
+//------------------------------------------------------------------------------
+// <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.AspNet.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.AspNet.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 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);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/Resources/WebResources.resx b/src/DotNetOpenAuth.AspNet/Resources/WebResources.resx
new file mode 100644
index 0000000..dce188c
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Resources/WebResources.resx
@@ -0,0 +1,138 @@
+<?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="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>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.AspNet/UriHelper.cs b/src/DotNetOpenAuth.AspNet/UriHelper.cs
new file mode 100644
index 0000000..83e0ade
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/UriHelper.cs
@@ -0,0 +1,66 @@
+namespace DotNetOpenAuth.AspNet {
+ using System;
+ using System.Text.RegularExpressions;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+
+ 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>
+ /// 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, HttpContextBase context) {
+ if (Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute)) {
+ return new Uri(returnUrl, UriKind.Absolute);
+ }
+
+ if (!VirtualPathUtility.IsAbsolute(returnUrl)) {
+ returnUrl = VirtualPathUtility.ToAbsolute(returnUrl);
+ }
+
+ Uri publicUrl = HttpRequestInfo.GetPublicFacingUrl(context.Request, context.Request.ServerVariables);
+ return new Uri(publicUrl, returnUrl);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln
index ddf80bd..3ccdfae 100644
--- a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln
+++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln
@@ -29,6 +29,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.BuildTasks",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet", "NuGet", "{D49E2011-0E1C-4AB5-9887-BD1D42266503}"
ProjectSection(SolutionItems) = preProject
+ ..\..\nuget\DotNetOpenAuth.AspNet.nuspec = ..\..\nuget\DotNetOpenAuth.AspNet.nuspec
..\..\nuget\DotNetOpenAuth.Core.nuspec = ..\..\nuget\DotNetOpenAuth.Core.nuspec
..\..\nuget\DotNetOpenAuth.OAuth.Consumer.nuspec = ..\..\nuget\DotNetOpenAuth.OAuth.Consumer.nuspec
..\..\nuget\DotNetOpenAuth.OAuth.Core.nuspec = ..\..\nuget\DotNetOpenAuth.OAuth.Core.nuspec
diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
index 9b8a613..f669731 100644
--- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
+++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
@@ -127,6 +127,7 @@
<Compile Include="Loggers\NoOpLogger.cs" />
<Compile Include="Loggers\TraceLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Messaging\ReadOnlyDictionary.cs" />
<Compile Include="Reporting.cs" />
<Compile Include="Requires.cs" />
<Compile Include="Strings.Designer.cs">
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
index 49ecb36..579225b 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
@@ -245,7 +245,8 @@ namespace DotNetOpenAuth.Messaging {
if (this.InputStream.CanSeek) {
this.InputStream.Seek(originalPosition, SeekOrigin.Begin);
}
- } else {
+ }
+ else {
this.form = new NameValueCollection();
}
}
@@ -280,7 +281,8 @@ namespace DotNetOpenAuth.Messaging {
if (!this.IsUrlRewritten) {
// No rewriting has taken place.
this.queryStringBeforeRewriting = this.QueryString;
- } else {
+ }
+ else {
// Rewriting detected! Recover the original request URI.
ErrorUtilities.VerifyInternal(this.UrlBeforeRewriting != null, "UrlBeforeRewriting is null, so the query string cannot be determined.");
this.queryStringBeforeRewriting = HttpUtility.ParseQueryString(this.UrlBeforeRewriting.Query);
@@ -317,6 +319,24 @@ namespace DotNetOpenAuth.Messaging {
/// is a read-only kind of <see cref="NameValueCollection"/>.
/// </remarks>
internal static Uri GetPublicFacingUrl(HttpRequest request, NameValueCollection serverVariables) {
+ return GetPublicFacingUrl(new HttpRequestWrapper(request), serverVariables);
+ }
+
+ /// <summary>
+ /// Gets the public facing URL for the given incoming HTTP request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="serverVariables">The server variables to consider part of the request.</param>
+ /// <returns>
+ /// The URI that the outside world used to create this request.
+ /// </returns>
+ /// <remarks>
+ /// Although the <paramref name="serverVariables"/> value can be obtained from
+ /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them
+ /// in so we can simulate injected values from our unit tests since the actual property
+ /// is a read-only kind of <see cref="NameValueCollection"/>.
+ /// </remarks>
+ internal static Uri GetPublicFacingUrl(HttpRequestBase request, NameValueCollection serverVariables) {
Requires.NotNull(request, "request");
Requires.NotNull(serverVariables, "serverVariables");
@@ -336,7 +356,8 @@ namespace DotNetOpenAuth.Messaging {
publicRequestUri.Host = hostAndPort.Host;
publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
return publicRequestUri.Uri;
- } else {
+ }
+ else {
// Failover to the method that works for non-web farm enviroments.
// We use Request.Url for the full path to the server, and modify it
// with Request.RawUrl to capture both the cookieless session "directory" if it exists
@@ -358,7 +379,8 @@ namespace DotNetOpenAuth.Messaging {
NameValueCollection query;
if (this.HttpMethod == "GET") {
query = this.QueryStringBeforeRewriting;
- } else {
+ }
+ else {
query = this.Form;
}
return query;
@@ -396,7 +418,8 @@ namespace DotNetOpenAuth.Messaging {
foreach (string key in pairs) {
try {
headers.Add(key, pairs[key]);
- } catch (ArgumentException ex) {
+ }
+ catch (ArgumentException ex) {
Logger.Messaging.WarnFormat(
"{0} thrown when trying to add web header \"{1}: {2}\". {3}",
ex.GetType().Name,
diff --git a/src/DotNetOpenAuth.Core/Messaging/ReadOnlyDictionary.cs b/src/DotNetOpenAuth.Core/Messaging/ReadOnlyDictionary.cs
new file mode 100644
index 0000000..0ba1ff5
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/ReadOnlyDictionary.cs
@@ -0,0 +1,224 @@
+//-----------------------------------------------------------------------
+// <copyright file="ReadOnlyDictionary.cs" company="Microsoft Corporation">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+
+ /// <summary>
+ /// Represents a read-only dictionary.
+ /// </summary>
+ /// <typeparam name="K">The type of the key.</typeparam>
+ /// <typeparam name="V">The type of the value.</typeparam>
+ internal class ReadOnlyDictionary<K, V> : IDictionary<K, V> {
+ /// <summary>
+ /// Contains base dictionary.
+ /// </summary>
+ private readonly IDictionary<K, V> baseDictionary;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ReadOnlyDictionary&lt;K, V&gt;"/> class.
+ /// </summary>
+ /// <param name="baseDictionary">The base dictionary.</param>
+ public ReadOnlyDictionary(IDictionary<K, V> baseDictionary) {
+ this.baseDictionary = baseDictionary;
+ }
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </returns>
+ public ICollection<K> Keys {
+ get { return this.baseDictionary.Keys; }
+ }
+
+ /// <summary>
+ /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </returns>
+ public ICollection<V> Values {
+ get { return this.baseDictionary.Values; }
+ }
+
+ /// <summary>
+ /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+ /// </summary>
+ /// <returns>
+ /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+ /// </returns>
+ public int Count {
+ get { return this.baseDictionary.Count; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+ /// </summary>
+ /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
+ /// </returns>
+ public bool IsReadOnly {
+ get { return true; }
+ }
+
+ /// <summary>
+ /// Gets or sets the element with the specified key.
+ /// </summary>
+ /// <param name="key">The key being read or written.</param>
+ /// <returns>
+ /// The element with the specified key.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.
+ /// </exception>
+ /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
+ /// The property is retrieved and <paramref name="key"/> is not found.
+ /// </exception>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The property is set and the <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
+ /// </exception>
+ public V this[K key] {
+ get { return this.baseDictionary[key]; }
+ set { throw new NotSupportedException(); }
+ }
+
+ /// <summary>
+ /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </summary>
+ /// <param name="key">The object to use as the key of the element to add.</param>
+ /// <param name="value">The object to use as the value of the element to add.</param>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.
+ /// </exception>
+ /// <exception cref="T:System.ArgumentException">
+ /// An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </exception>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
+ /// </exception>
+ public void Add(K key, V value) {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key.
+ /// </summary>
+ /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</param>
+ /// <returns>
+ /// true if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.
+ /// </exception>
+ public bool ContainsKey(K key) {
+ return this.baseDictionary.ContainsKey(key);
+ }
+
+ /// <summary>
+ /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </summary>
+ /// <param name="key">The key of the element to remove.</param>
+ /// <returns>
+ /// true if the element is successfully removed; otherwise, false. This method also returns false if <paramref name="key"/> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"/>.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.
+ /// </exception>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.
+ /// </exception>
+ public bool Remove(K key) {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Gets the value associated with the specified key.
+ /// </summary>
+ /// <param name="key">The key whose value to get.</param>
+ /// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.</param>
+ /// <returns>
+ /// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.
+ /// </exception>
+ public bool TryGetValue(K key, out V value) {
+ return this.baseDictionary.TryGetValue(key, out value);
+ }
+
+ /// <summary>
+ /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+ /// </summary>
+ /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+ /// </exception>
+ public void Add(KeyValuePair<K, V> item) {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+ /// </summary>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+ /// </exception>
+ public void Clear() {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
+ /// </summary>
+ /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
+ /// <returns>
+ /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
+ /// </returns>
+ public bool Contains(KeyValuePair<K, V> item) {
+ return this.baseDictionary.Contains(item);
+ }
+
+ /// <summary>
+ /// Copies to.
+ /// </summary>
+ /// <param name="array">The array.</param>
+ /// <param name="arrayIndex">Index of the array.</param>
+ public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) {
+ this.baseDictionary.CopyTo(array, arrayIndex);
+ }
+
+ /// <summary>
+ /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+ /// </summary>
+ /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
+ /// <returns>
+ /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
+ /// </returns>
+ /// <exception cref="T:System.NotSupportedException">
+ /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+ /// </exception>
+ public bool Remove(KeyValuePair<K, V> item) {
+ throw new NotSupportedException();
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
+ /// </returns>
+ public IEnumerator<KeyValuePair<K, V>> GetEnumerator() {
+ return this.baseDictionary.GetEnumerator();
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through a collection.
+ /// </summary>
+ /// <returns>
+ /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
+ /// </returns>
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
+ return this.baseDictionary.GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
index 950a7cd..e57b211 100644
--- a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
@@ -67,6 +67,8 @@ 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.AspNet.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
#else
[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")]
@@ -88,5 +90,7 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
#endif
diff --git a/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs
index cbc1307..7f63d1b 100644
--- a/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth.OAuth/Properties/AssemblyInfo.cs
@@ -53,6 +53,7 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdOAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
#else
[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")]
@@ -60,5 +61,6 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth.ServiceProvider")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdOAuth")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
#endif
diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln
index 2aff83c..73e27f8 100644
--- a/src/DotNetOpenAuth.sln
+++ b/src/DotNetOpenAuth.sln
@@ -211,6 +211,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthServiceProvider", "..\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenIdOAuth", "DotNetOpenAuth.OpenIdOAuth\DotNetOpenAuth.OpenIdOAuth.csproj", "{4BFAA336-5DF3-4F27-82D3-06D13240E8AB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.AspNet", "DotNetOpenAuth.AspNet\DotNetOpenAuth.AspNet.csproj", "{51835086-9611-4C53-819B-F2D5C9320873}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.AspNet.Test", "DotNetOpenAuth.AspNet.Test\DotNetOpenAuth.AspNet.Test.csproj", "{C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.Common", "DotNetOpenAuth.OAuth.Common\DotNetOpenAuth.OAuth.Common.csproj", "{115217C5-22CD-415C-A292-0DD0238CDD89}"
EndProject
Global
@@ -609,6 +613,7 @@ Global
{60426312-6AE5-4835-8667-37EDEA670222} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
{57A7DD35-666C-4FA3-9A1B-38961E50CA27} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
{115217C5-22CD-415C-A292-0DD0238CDD89} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE}
+ {51835086-9611-4C53-819B-F2D5C9320873} = {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}