summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.ApplicationBlock
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.ApplicationBlock')
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/Acme.cs80
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs49
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs68
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/UIRequestAtRelyingPartyFactory.cs47
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj191
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookClient.cs29
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookGraph.cs54
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs288
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/InMemoryClientAuthorizationTracker.cs49
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs145
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/Properties/AssemblyInfo.cs36
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/TokenManager.cs18
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs224
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/Util.cs259
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/WindowsLiveClient.cs52
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/WindowsLiveGraph.cs60
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs61
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs207
-rw-r--r--src/DotNetOpenAuth.ApplicationBlock/packages.config20
19 files changed, 1937 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/Acme.cs b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/Acme.cs
new file mode 100644
index 0000000..eff6018
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/Acme.cs
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------
+// <copyright file="Acme.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.OpenId.Provider;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A sample custom OpenID extension factory.
+ /// </summary>
+ /// <remarks>
+ /// OpenID extension factories must be registered with the library. This can be
+ /// done by calling <see cref="Acme.Register"/>, or by adding a snippet
+ /// such as the following to your web.config file:
+ /// <example>
+ /// &lt;dotNetOpenAuth&gt;
+ /// &lt;openid&gt;
+ /// &lt;extensionFactories&gt;
+ /// &lt;add type="DotNetOpenAuth.ApplicationBlock.CustomExtensions.Acme, DotNetOpenAuth.ApplicationBlock" /&gt;
+ /// &lt;/extensionFactories&gt;
+ /// &lt;/openid&gt;
+ /// &lt;/dotNetOpenAuth&gt;
+ /// </example>
+ /// </remarks>
+ public class Acme : IOpenIdExtensionFactory {
+ internal const string CustomExtensionTypeUri = "testextension";
+ internal static readonly Version Version = new Version(1, 0);
+
+ public static void Register(OpenIdRelyingParty relyingParty) {
+ if (relyingParty == null) {
+ throw new ArgumentNullException("relyingParty");
+ }
+
+ relyingParty.ExtensionFactories.Add(new Acme());
+ }
+
+ public static void Register(OpenIdProvider provider) {
+ if (provider == null) {
+ throw new ArgumentNullException("provider");
+ }
+
+ provider.ExtensionFactories.Add(new Acme());
+ }
+
+ #region IOpenIdExtensionFactory Members
+
+ /// <summary>
+ /// Creates a new instance of some extension based on the received extension parameters.
+ /// </summary>
+ /// <param name="typeUri">The type URI of the extension.</param>
+ /// <param name="data">The parameters associated specifically with this extension.</param>
+ /// <param name="baseMessage">The OpenID message carrying this extension.</param>
+ /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param>
+ /// <returns>
+ /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes
+ /// the extension described in the input parameters; <c>null</c> otherwise.
+ /// </returns>
+ /// <remarks>
+ /// This factory method need only initialize properties in the instantiated extension object
+ /// that are not bound using <see cref="MessagePartAttribute"/>.
+ /// </remarks>
+ public IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole) {
+ if (typeUri == CustomExtensionTypeUri) {
+ return isProviderRole ? (IOpenIdMessageExtension)new AcmeRequest() : new AcmeResponse();
+ }
+
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs
new file mode 100644
index 0000000..58597b8
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="AcmeRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ public class AcmeRequest : IOpenIdMessageExtension {
+ private IDictionary<string, string> extraData = new Dictionary<string, string>();
+
+ [MessagePart]
+ public string FavoriteFlavor { get; set; }
+
+ #region IOpenIdMessageExtension Members
+
+ public string TypeUri {
+ get { return Acme.CustomExtensionTypeUri; }
+ }
+
+ public IEnumerable<string> AdditionalSupportedTypeUris {
+ get { return Enumerable.Empty<string>(); }
+ }
+
+ public bool IsSignedByRemoteParty { get; set; }
+
+ #endregion
+
+ #region IMessage Members
+
+ public Version Version {
+ get { return Acme.Version; }
+ }
+
+ public IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ public void EnsureValidMessage() {
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs
new file mode 100644
index 0000000..f021d84
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------
+// <copyright file="AcmeResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ [Serializable]
+ public class AcmeResponse : IOpenIdMessageExtension {
+ private IDictionary<string, string> extraData = new Dictionary<string, string>();
+
+ [MessagePart]
+ public string FavoriteIceCream { get; set; }
+
+ #region IOpenIdMessageExtension Members
+
+ /// <summary>
+ /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
+ /// </summary>
+ public string TypeUri {
+ get { return Acme.CustomExtensionTypeUri; }
+ }
+
+ /// <summary>
+ /// Gets the additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ public IEnumerable<string> AdditionalSupportedTypeUris {
+ get { return Enumerable.Empty<string>(); }
+ }
+
+ public bool IsSignedByRemoteParty { get; set; }
+
+ #endregion
+
+ #region IMessage Members
+
+ public Version Version {
+ get { return Acme.Version; }
+ }
+
+ public IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ public void EnsureValidMessage() {
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/UIRequestAtRelyingPartyFactory.cs b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/UIRequestAtRelyingPartyFactory.cs
new file mode 100644
index 0000000..afa242e
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/CustomExtensions/UIRequestAtRelyingPartyFactory.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="UIRequestAtRelyingPartyFactory.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions {
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Extensions.UI;
+
+ /// <summary>
+ /// An extension factory that allows the <see cref="UIRequest"/> extension to be received by the relying party.
+ /// </summary>
+ /// <remarks>
+ /// Typically UIRequest is only received by the Provider. But Google mirrors back this data to the relying party
+ /// if our web user is already logged into Google.
+ /// See the OpenIdRelyingPartyWebForms sample's DetectGoogleSession.aspx page for usage of this factory.
+ /// </remarks>
+ public class UIRequestAtRelyingPartyFactory : IOpenIdExtensionFactory {
+ /// <summary>
+ /// The Type URI for the UI extension.
+ /// </summary>
+ private const string UITypeUri = "http://specs.openid.net/extensions/ui/1.0";
+
+ /// <summary>
+ /// Allows UIRequest extensions to be received by the relying party. Useful when Google mirrors back the request
+ /// to indicate that a user is logged in.
+ /// </summary>
+ /// <param name="typeUri">The type URI of the extension.</param>
+ /// <param name="data">The parameters associated specifically with this extension.</param>
+ /// <param name="baseMessage">The OpenID message carrying this extension.</param>
+ /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param>
+ /// <returns>
+ /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes
+ /// the extension described in the input parameters; <c>null</c> otherwise.
+ /// </returns>
+ public DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole) {
+ if (typeUri == UITypeUri && !isProviderRole) {
+ return new UIRequest();
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/src/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj
new file mode 100644
index 0000000..b86c930
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj
@@ -0,0 +1,191 @@
+<?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>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{AA78D112-D889-414B-A7D4-467B34C7B663}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth.ApplicationBlock</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.ApplicationBlock</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <FileUpgradeFlags>
+ </FileUpgradeFlags>
+ <OldToolsVersion>3.5</OldToolsVersion>
+ <UpgradeBackupLocation />
+ <IsWebBootstrapper>false</IsWebBootstrapper>
+ <TargetFrameworkProfile />
+ <PublishUrl>publish\</PublishUrl>
+ <Install>true</Install>
+ <InstallFrom>Disk</InstallFrom>
+ <UpdateEnabled>false</UpdateEnabled>
+ <UpdateMode>Foreground</UpdateMode>
+ <UpdateInterval>7</UpdateInterval>
+ <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+ <UpdatePeriodically>false</UpdatePeriodically>
+ <UpdateRequired>false</UpdateRequired>
+ <MapFileExtensions>true</MapFileExtensions>
+ <ApplicationRevision>0</ApplicationRevision>
+ <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+ <UseApplicationTrust>false</UseApplicationTrust>
+ <BootstrapperEnabled>true</BootstrapperEnabled>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <RestorePackages>true</RestorePackages>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>TRACE;DEBUG</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </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>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\CodeAnalysis\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
+ <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup>
+ <DefineConstants>$(DefineConstants);SAMPLESONLY</DefineConstants>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="DotNetOpenAuth.Core">
+ <HintPath>..\..\packages\DotNetOpenAuth.Core.4.0.0.12084\lib\net40-full\DotNetOpenAuth.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.InfoCard">
+ <HintPath>..\..\packages\DotNetOpenAuth.InfoCard.4.0.0.12084\lib\net40-full\DotNetOpenAuth.InfoCard.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth.Core.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OAuth.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth.Common">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth.Common.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OAuth.Common.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth.Consumer">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth.Consumer.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OAuth.Consumer.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth.ServiceProvider">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth.ServiceProvider.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OAuth.ServiceProvider.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth2">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth2.Core.0.23.0-draft2\lib\net40-full\DotNetOpenAuth.OAuth2.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth2.Client">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth2.Client.0.23.0-draft2\lib\net40-full\DotNetOpenAuth.OAuth2.Client.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OAuth2.Client.UI">
+ <HintPath>..\..\packages\DotNetOpenAuth.OAuth2.Client.UI.0.23.0-draft2\lib\net40-full\DotNetOpenAuth.OAuth2.Client.UI.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OpenId">
+ <HintPath>..\..\packages\DotNetOpenAuth.OpenId.Core.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OpenId.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OpenId.Provider">
+ <HintPath>..\..\packages\DotNetOpenAuth.OpenId.Provider.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OpenId.Provider.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OpenId.RelyingParty">
+ <HintPath>..\..\packages\DotNetOpenAuth.OpenId.RelyingParty.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OpenId.RelyingParty.dll</HintPath>
+ </Reference>
+ <Reference Include="DotNetOpenAuth.OpenIdOAuth">
+ <HintPath>..\..\packages\DotNetOpenAuth.OpenIdOAuth.4.0.0.12084\lib\net40-full\DotNetOpenAuth.OpenIdOAuth.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.configuration" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization" />
+ <Reference Include="System.ServiceModel.Web" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml.Linq">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data.DataSetExtensions">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="CustomExtensions\Acme.cs" />
+ <Compile Include="CustomExtensions\AcmeRequest.cs" />
+ <Compile Include="CustomExtensions\AcmeResponse.cs" />
+ <Compile Include="Facebook\FacebookClient.cs" />
+ <Compile Include="Facebook\FacebookGraph.cs" />
+ <Compile Include="CustomExtensions\UIRequestAtRelyingPartyFactory.cs" />
+ <Compile Include="GoogleConsumer.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="InMemoryClientAuthorizationTracker.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="InMemoryTokenManager.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="TokenManager.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="TwitterConsumer.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Util.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="WindowsLiveClient.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="WindowsLiveGraph.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="YammerConsumer.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="YubikeyRelyingParty.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+ <Visible>False</Visible>
+ <ProductName>Windows Installer 3.1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookClient.cs b/src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookClient.cs
new file mode 100644
index 0000000..4af1bbd
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookClient.cs
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------
+// <copyright file="FacebookClient.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2;
+
+ public class FacebookClient : WebServerClient {
+ private static readonly AuthorizationServerDescription FacebookDescription = new AuthorizationServerDescription {
+ TokenEndpoint = new Uri("https://graph.facebook.com/oauth/access_token"),
+ AuthorizationEndpoint = new Uri("https://graph.facebook.com/oauth/authorize"),
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FacebookClient"/> class.
+ /// </summary>
+ public FacebookClient() : base(FacebookDescription) {
+ this.AuthorizationTracker = new TokenManager();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookGraph.cs b/src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookGraph.cs
new file mode 100644
index 0000000..909ae9a
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/Facebook/FacebookGraph.cs
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------
+// <copyright file="FacebookGraph.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock.Facebook {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Runtime.Serialization;
+ using System.Runtime.Serialization.Json;
+ using System.Text;
+
+ [DataContract]
+ public class FacebookGraph {
+ private static DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(FacebookGraph));
+
+ [DataMember(Name = "id")]
+ public long Id { get; set; }
+
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ [DataMember(Name = "first_name")]
+ public string FirstName { get; set; }
+
+ [DataMember(Name = "last_name")]
+ public string LastName { get; set; }
+
+ [DataMember(Name = "link")]
+ public Uri Link { get; set; }
+
+ [DataMember(Name = "birthday")]
+ public string Birthday { get; set; }
+
+ public static FacebookGraph Deserialize(string json) {
+ if (string.IsNullOrEmpty(json)) {
+ throw new ArgumentNullException("json");
+ }
+
+ return Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(json)));
+ }
+
+ public static FacebookGraph Deserialize(Stream jsonStream) {
+ if (jsonStream == null) {
+ throw new ArgumentNullException("jsonStream");
+ }
+
+ return (FacebookGraph)jsonSerializer.ReadObject(jsonStream);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs b/src/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs
new file mode 100644
index 0000000..1bdb04d
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs
@@ -0,0 +1,288 @@
+//-----------------------------------------------------------------------
+// <copyright file="GoogleConsumer.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Xml;
+ using System.Xml.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A consumer capable of communicating with Google Data APIs.
+ /// </summary>
+ public static class GoogleConsumer {
+ /// <summary>
+ /// The Consumer to use for accessing Google data APIs.
+ /// </summary>
+ public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription {
+ RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
+ };
+
+ /// <summary>
+ /// A mapping between Google's applications and their URI scope values.
+ /// </summary>
+ private static readonly Dictionary<Applications, string> DataScopeUris = new Dictionary<Applications, string> {
+ { Applications.Analytics, "https://www.google.com/analytics/feeds/" },
+ { Applications.GoogleBase, "http://www.google.com/base/feeds/" },
+ { Applications.Blogger, "http://www.blogger.com/feeds" },
+ { Applications.BookSearch, "http://www.google.com/books/feeds/" },
+ { Applications.Calendar, "http://www.google.com/calendar/feeds/" },
+ { Applications.Contacts, "http://www.google.com/m8/feeds/" },
+ { Applications.DocumentsList, "http://docs.google.com/feeds/" },
+ { Applications.Finance, "http://finance.google.com/finance/feeds/" },
+ { Applications.Gmail, "https://mail.google.com/mail/feed/atom" },
+ { Applications.Health, "https://www.google.com/h9/feeds/" },
+ { Applications.Maps, "http://maps.google.com/maps/feeds/" },
+ { Applications.OpenSocial, "http://sandbox.gmodules.com/api/" },
+ { Applications.PicasaWeb, "http://picasaweb.google.com/data/" },
+ { Applications.Spreadsheets, "http://spreadsheets.google.com/feeds/" },
+ { Applications.WebmasterTools, "http://www.google.com/webmasters/tools/feeds/" },
+ { Applications.YouTube, "http://gdata.youtube.com" },
+ };
+
+ /// <summary>
+ /// The URI to get contacts once authorization is granted.
+ /// </summary>
+ private static readonly MessageReceivingEndpoint GetContactsEndpoint = new MessageReceivingEndpoint("http://www.google.com/m8/feeds/contacts/default/full/", HttpDeliveryMethods.GetRequest);
+
+ /// <summary>
+ /// The many specific authorization scopes Google offers.
+ /// </summary>
+ [Flags]
+ public enum Applications : long {
+ /// <summary>
+ /// The Gmail address book.
+ /// </summary>
+ Contacts = 0x1,
+
+ /// <summary>
+ /// Appointments in Google Calendar.
+ /// </summary>
+ Calendar = 0x2,
+
+ /// <summary>
+ /// Blog post authoring.
+ /// </summary>
+ Blogger = 0x4,
+
+ /// <summary>
+ /// Google Finance
+ /// </summary>
+ Finance = 0x8,
+
+ /// <summary>
+ /// Google Gmail
+ /// </summary>
+ Gmail = 0x10,
+
+ /// <summary>
+ /// Google Health
+ /// </summary>
+ Health = 0x20,
+
+ /// <summary>
+ /// Google OpenSocial
+ /// </summary>
+ OpenSocial = 0x40,
+
+ /// <summary>
+ /// Picasa Web
+ /// </summary>
+ PicasaWeb = 0x80,
+
+ /// <summary>
+ /// Google Spreadsheets
+ /// </summary>
+ Spreadsheets = 0x100,
+
+ /// <summary>
+ /// Webmaster Tools
+ /// </summary>
+ WebmasterTools = 0x200,
+
+ /// <summary>
+ /// YouTube service
+ /// </summary>
+ YouTube = 0x400,
+
+ /// <summary>
+ /// Google Docs
+ /// </summary>
+ DocumentsList = 0x800,
+
+ /// <summary>
+ /// Google Book Search
+ /// </summary>
+ BookSearch = 0x1000,
+
+ /// <summary>
+ /// Google Base
+ /// </summary>
+ GoogleBase = 0x2000,
+
+ /// <summary>
+ /// Google Analytics
+ /// </summary>
+ Analytics = 0x4000,
+
+ /// <summary>
+ /// Google Maps
+ /// </summary>
+ Maps = 0x8000,
+ }
+
+ /// <summary>
+ /// The service description to use for accessing Google data APIs using an X509 certificate.
+ /// </summary>
+ /// <param name="signingCertificate">The signing certificate.</param>
+ /// <returns>A service description that can be used to create an instance of
+ /// <see cref="DesktopConsumer"/> or <see cref="WebConsumer"/>. </returns>
+ public static ServiceProviderDescription CreateRsaSha1ServiceDescription(X509Certificate2 signingCertificate) {
+ if (signingCertificate == null) {
+ throw new ArgumentNullException("signingCertificate");
+ }
+
+ return new ServiceProviderDescription {
+ RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new RsaSha1ConsumerSigningBindingElement(signingCertificate) },
+ };
+ }
+
+ /// <summary>
+ /// Requests authorization from Google to access data from a set of Google applications.
+ /// </summary>
+ /// <param name="consumer">The Google consumer previously constructed using <see cref="CreateWebConsumer"/> or <see cref="CreateDesktopConsumer"/>.</param>
+ /// <param name="requestedAccessScope">The requested access scope.</param>
+ public static void RequestAuthorization(WebConsumer consumer, Applications requestedAccessScope) {
+ if (consumer == null) {
+ throw new ArgumentNullException("consumer");
+ }
+
+ var extraParameters = new Dictionary<string, string> {
+ { "scope", GetScopeUri(requestedAccessScope) },
+ };
+ Uri callback = Util.GetCallbackUrlFromContext();
+ var request = consumer.PrepareRequestUserAuthorization(callback, extraParameters, null);
+ consumer.Channel.Send(request);
+ }
+
+ /// <summary>
+ /// Requests authorization from Google to access data from a set of Google applications.
+ /// </summary>
+ /// <param name="consumer">The Google consumer previously constructed using <see cref="CreateWebConsumer"/> or <see cref="CreateDesktopConsumer"/>.</param>
+ /// <param name="requestedAccessScope">The requested access scope.</param>
+ /// <param name="requestToken">The unauthorized request token assigned by Google.</param>
+ /// <returns>The request token</returns>
+ public static Uri RequestAuthorization(DesktopConsumer consumer, Applications requestedAccessScope, out string requestToken) {
+ if (consumer == null) {
+ throw new ArgumentNullException("consumer");
+ }
+
+ var extraParameters = new Dictionary<string, string> {
+ { "scope", GetScopeUri(requestedAccessScope) },
+ };
+
+ return consumer.RequestUserAuthorization(extraParameters, null, out requestToken);
+ }
+
+ /// <summary>
+ /// Gets the Gmail address book's contents.
+ /// </summary>
+ /// <param name="consumer">The Google consumer.</param>
+ /// <param name="accessToken">The access token previously retrieved.</param>
+ /// <param name="maxResults">The maximum number of entries to return. If you want to receive all of the contacts, rather than only the default maximum, you can specify a very large number here.</param>
+ /// <param name="startIndex">The 1-based index of the first result to be retrieved (for paging).</param>
+ /// <returns>An XML document returned by Google.</returns>
+ public static XDocument GetContacts(ConsumerBase consumer, string accessToken, int maxResults/* = 25*/, int startIndex/* = 1*/) {
+ if (consumer == null) {
+ throw new ArgumentNullException("consumer");
+ }
+
+ var extraData = new Dictionary<string, string>() {
+ { "start-index", startIndex.ToString(CultureInfo.InvariantCulture) },
+ { "max-results", maxResults.ToString(CultureInfo.InvariantCulture) },
+ };
+ var request = consumer.PrepareAuthorizedRequest(GetContactsEndpoint, accessToken, extraData);
+
+ // Enable gzip compression. Google only compresses the response for recognized user agent headers. - Mike Lim
+ request.AutomaticDecompression = DecompressionMethods.GZip;
+ request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.151 Safari/534.16";
+
+ var response = consumer.Channel.WebRequestHandler.GetResponse(request);
+ string body = response.GetResponseReader().ReadToEnd();
+ XDocument result = XDocument.Parse(body);
+ return result;
+ }
+
+ public static void PostBlogEntry(ConsumerBase consumer, string accessToken, string blogUrl, string title, XElement body) {
+ string feedUrl;
+ var getBlogHome = WebRequest.Create(blogUrl);
+ using (var blogHomeResponse = getBlogHome.GetResponse()) {
+ using (StreamReader sr = new StreamReader(blogHomeResponse.GetResponseStream())) {
+ string homePageHtml = sr.ReadToEnd();
+ Match m = Regex.Match(homePageHtml, @"http://www.blogger.com/feeds/\d+/posts/default");
+ Debug.Assert(m.Success, "Posting operation failed.");
+ feedUrl = m.Value;
+ }
+ }
+ const string Atom = "http://www.w3.org/2005/Atom";
+ XElement entry = new XElement(
+ XName.Get("entry", Atom),
+ new XElement(XName.Get("title", Atom), new XAttribute("type", "text"), title),
+ new XElement(XName.Get("content", Atom), new XAttribute("type", "xhtml"), body),
+ new XElement(XName.Get("category", Atom), new XAttribute("scheme", "http://www.blogger.com/atom/ns#"), new XAttribute("term", "oauthdemo")));
+
+ MemoryStream ms = new MemoryStream();
+ XmlWriterSettings xws = new XmlWriterSettings() {
+ Encoding = Encoding.UTF8,
+ };
+ XmlWriter xw = XmlWriter.Create(ms, xws);
+ entry.WriteTo(xw);
+ xw.Flush();
+
+ WebRequest request = consumer.PrepareAuthorizedRequest(new MessageReceivingEndpoint(feedUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), accessToken);
+ request.ContentType = "application/atom+xml";
+ request.Method = "POST";
+ request.ContentLength = ms.Length;
+ ms.Seek(0, SeekOrigin.Begin);
+ using (Stream requestStream = request.GetRequestStream()) {
+ ms.CopyTo(requestStream);
+ }
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
+ if (response.StatusCode == HttpStatusCode.Created) {
+ // Success
+ } else {
+ // Error!
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the scope URI in Google's format.
+ /// </summary>
+ /// <param name="scope">The scope, which may include one or several Google applications.</param>
+ /// <returns>A space-delimited list of URIs for the requested Google applications.</returns>
+ public static string GetScopeUri(Applications scope) {
+ return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray());
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/InMemoryClientAuthorizationTracker.cs b/src/DotNetOpenAuth.ApplicationBlock/InMemoryClientAuthorizationTracker.cs
new file mode 100644
index 0000000..0dad973
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/InMemoryClientAuthorizationTracker.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="InMemoryClientAuthorizationTracker.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.ServiceModel;
+ using System.Text;
+ using System.Threading;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2;
+
+ internal class InMemoryClientAuthorizationTracker : IClientAuthorizationTracker {
+ private readonly Dictionary<int, IAuthorizationState> savedStates = new Dictionary<int, IAuthorizationState>();
+ private int stateCounter;
+
+ #region Implementation of IClientTokenManager
+
+ /// <summary>
+ /// Gets the state of the authorization for a given callback URL and client state.
+ /// </summary>
+ /// <param name="callbackUrl">The callback URL.</param>
+ /// <param name="clientState">State of the client stored at the beginning of an authorization request.</param>
+ /// <returns>The authorization state; may be <c>null</c> if no authorization state matches.</returns>
+ public IAuthorizationState GetAuthorizationState(Uri callbackUrl, string clientState) {
+ IAuthorizationState state;
+ if (this.savedStates.TryGetValue(int.Parse(clientState), out state)) {
+ if (state.Callback != callbackUrl) {
+ throw new DotNetOpenAuth.Messaging.ProtocolException("Client state and callback URL do not match.");
+ }
+ }
+
+ return state;
+ }
+
+ #endregion
+
+ internal IAuthorizationState NewAuthorization(HashSet<string> scope, out string clientState) {
+ int counter = Interlocked.Increment(ref this.stateCounter);
+ clientState = counter.ToString(CultureInfo.InvariantCulture);
+ return this.savedStates[counter] = new AuthorizationState(scope);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs b/src/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs
new file mode 100644
index 0000000..2a1e155
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs
@@ -0,0 +1,145 @@
+//-----------------------------------------------------------------------
+// <copyright file="InMemoryTokenManager.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
+
+ /// <summary>
+ /// A token manager that only retains tokens in memory.
+ /// Meant for SHORT TERM USE TOKENS ONLY.
+ /// </summary>
+ /// <remarks>
+ /// A likely application of this class is for "Sign In With Twitter",
+ /// where the user only signs in without providing any authorization to access
+ /// Twitter APIs except to authenticate, since that access token is only useful once.
+ /// </remarks>
+ public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager {
+ private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InMemoryTokenManager"/> class.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="consumerSecret">The consumer secret.</param>
+ public InMemoryTokenManager(string consumerKey, string consumerSecret) {
+ if (string.IsNullOrEmpty(consumerKey)) {
+ throw new ArgumentNullException("consumerKey");
+ }
+
+ this.ConsumerKey = consumerKey;
+ this.ConsumerSecret = consumerSecret;
+ }
+
+ /// <summary>
+ /// Gets the consumer key.
+ /// </summary>
+ /// <value>The consumer key.</value>
+ public string ConsumerKey { get; private set; }
+
+ /// <summary>
+ /// Gets the consumer secret.
+ /// </summary>
+ /// <value>The consumer secret.</value>
+ public string ConsumerSecret { get; private set; }
+
+ #region ITokenManager Members
+
+ /// <summary>
+ /// Gets the Token Secret given a request or access token.
+ /// </summary>
+ /// <param name="token">The request or access token.</param>
+ /// <returns>
+ /// The secret associated with the given token.
+ /// </returns>
+ /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception>
+ public string GetTokenSecret(string token) {
+ return this.tokensAndSecrets[token];
+ }
+
+ /// <summary>
+ /// Stores a newly generated unauthorized request token, secret, and optional
+ /// application-specific parameters for later recall.
+ /// </summary>
+ /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param>
+ /// <param name="response">The response message that includes the unauthorized request token.</param>
+ /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception>
+ /// <remarks>
+ /// Request tokens stored by this method SHOULD NOT associate any user account with this token.
+ /// It usually opens up security holes in your application to do so. Instead, you associate a user
+ /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/>
+ /// method.
+ /// </remarks>
+ public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) {
+ this.tokensAndSecrets[response.Token] = response.TokenSecret;
+ }
+
+ /// <summary>
+ /// Deletes a request token and its associated secret and stores a new access token and secret.
+ /// </summary>
+ /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param>
+ /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param>
+ /// <param name="accessToken">The new access token that is being issued to the Consumer.</param>
+ /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param>
+ /// <remarks>
+ /// <para>
+ /// Any scope of granted privileges associated with the request token from the
+ /// original call to <see cref="StoreNewRequestToken"/> should be carried over
+ /// to the new Access Token.
+ /// </para>
+ /// <para>
+ /// To associate a user account with the new access token,
+ /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be
+ /// useful in an ASP.NET web application within the implementation of this method.
+ /// Alternatively you may store the access token here without associating with a user account,
+ /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or
+ /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access
+ /// token to associate the access token with a user account at that point.
+ /// </para>
+ /// </remarks>
+ public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) {
+ this.tokensAndSecrets.Remove(requestToken);
+ this.tokensAndSecrets[accessToken] = accessTokenSecret;
+ }
+
+ /// <summary>
+ /// Classifies a token as a request token or an access token.
+ /// </summary>
+ /// <param name="token">The token to classify.</param>
+ /// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
+ public TokenType GetTokenType(string token) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region IOpenIdOAuthTokenManager Members
+
+ /// <summary>
+ /// Stores a new request token obtained over an OpenID request.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param>
+ /// <remarks>
+ /// <para>The token secret is the empty string.</para>
+ /// <para>Tokens stored by this method should be short-lived to mitigate
+ /// possible security threats. Their lifetime should be sufficient for the
+ /// relying party to receive the positive authentication assertion and immediately
+ /// send a follow-up request for the access token.</para>
+ /// </remarks>
+ public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) {
+ this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.ApplicationBlock/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.ApplicationBlock/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0384014
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DotNetOpenAuth.ApplicationBlock")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DotNetOpenAuth.ApplicationBlock")]
+[assembly: AssemblyCopyright("Copyright © 2011 Outercurve Foundation")]
+[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("6db0c62e-2f06-46fa-acfe-4d737467c8c7")]
+
+// 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: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/DotNetOpenAuth.ApplicationBlock/TokenManager.cs b/src/DotNetOpenAuth.ApplicationBlock/TokenManager.cs
new file mode 100644
index 0000000..d1254af
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/TokenManager.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenManager.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using DotNetOpenAuth.OAuth2;
+
+ public class TokenManager : IClientAuthorizationTracker {
+ public IAuthorizationState GetAuthorizationState(Uri callbackUrl, string clientState) {
+ return new AuthorizationState {
+ Callback = callbackUrl,
+ };
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs b/src/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs
new file mode 100644
index 0000000..013e66b
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/TwitterConsumer.cs
@@ -0,0 +1,224 @@
+//-----------------------------------------------------------------------
+// <copyright file="TwitterConsumer.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Globalization;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+ using System.Xml;
+ using System.Xml.Linq;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A consumer capable of communicating with Twitter.
+ /// </summary>
+ public static class TwitterConsumer {
+ /// <summary>
+ /// The description of Twitter's OAuth protocol URIs for use with actually reading/writing
+ /// a user's private Twitter data.
+ /// </summary>
+ public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription {
+ RequestTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
+ UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/authorize", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
+ AccessTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
+ TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
+ };
+
+ /// <summary>
+ /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
+ /// </summary>
+ public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = new ServiceProviderDescription {
+ RequestTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
+ UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/authenticate", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
+ AccessTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
+ TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
+ };
+
+ /// <summary>
+ /// The URI to get a user's favorites.
+ /// </summary>
+ private static readonly MessageReceivingEndpoint GetFavoritesEndpoint = new MessageReceivingEndpoint("http://twitter.com/favorites.xml", HttpDeliveryMethods.GetRequest);
+
+ /// <summary>
+ /// The URI to get the data on the user's home page.
+ /// </summary>
+ private static readonly MessageReceivingEndpoint GetFriendTimelineStatusEndpoint = new MessageReceivingEndpoint("http://twitter.com/statuses/friends_timeline.xml", HttpDeliveryMethods.GetRequest);
+
+ private static readonly MessageReceivingEndpoint UpdateProfileBackgroundImageEndpoint = new MessageReceivingEndpoint("http://twitter.com/account/update_profile_background_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
+
+ private static readonly MessageReceivingEndpoint UpdateProfileImageEndpoint = new MessageReceivingEndpoint("http://twitter.com/account/update_profile_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
+
+ private static readonly MessageReceivingEndpoint VerifyCredentialsEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/verify_credentials.xml", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
+
+ /// <summary>
+ /// The consumer used for the Sign in to Twitter feature.
+ /// </summary>
+ private static WebConsumer signInConsumer;
+
+ /// <summary>
+ /// The lock acquired to initialize the <see cref="signInConsumer"/> field.
+ /// </summary>
+ private static object signInConsumerInitLock = new object();
+
+ /// <summary>
+ /// Initializes static members of the <see cref="TwitterConsumer"/> class.
+ /// </summary>
+ static TwitterConsumer() {
+ // Twitter can't handle the Expect 100 Continue HTTP header.
+ ServicePointManager.FindServicePoint(GetFavoritesEndpoint.Location).Expect100Continue = false;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file.
+ /// </summary>
+ public static bool IsTwitterConsumerConfigured {
+ get {
+ return !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerKey"]) &&
+ !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerSecret"]);
+ }
+ }
+
+ /// <summary>
+ /// Gets the consumer to use for the Sign in to Twitter feature.
+ /// </summary>
+ /// <value>The twitter sign in.</value>
+ private static WebConsumer TwitterSignIn {
+ get {
+ if (signInConsumer == null) {
+ lock (signInConsumerInitLock) {
+ if (signInConsumer == null) {
+ signInConsumer = new WebConsumer(SignInWithTwitterServiceDescription, ShortTermUserSessionTokenManager);
+ }
+ }
+ }
+
+ return signInConsumer;
+ }
+ }
+
+ private static InMemoryTokenManager ShortTermUserSessionTokenManager {
+ get {
+ var store = HttpContext.Current.Session;
+ var tokenManager = (InMemoryTokenManager)store["TwitterShortTermUserSessionTokenManager"];
+ if (tokenManager == null) {
+ string consumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"];
+ string consumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"];
+ if (IsTwitterConsumerConfigured) {
+ tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
+ store["TwitterShortTermUserSessionTokenManager"] = tokenManager;
+ } else {
+ throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings.");
+ }
+ }
+
+ return tokenManager;
+ }
+ }
+
+ public static XDocument GetUpdates(ConsumerBase twitter, string accessToken) {
+ IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFriendTimelineStatusEndpoint, accessToken);
+ return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
+ }
+
+ public static XDocument GetFavorites(ConsumerBase twitter, string accessToken) {
+ IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFavoritesEndpoint, accessToken);
+ return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
+ }
+
+ public static XDocument UpdateProfileBackgroundImage(ConsumerBase twitter, string accessToken, string image, bool tile) {
+ var parts = new[] {
+ MultipartPostPart.CreateFormFilePart("image", image, "image/" + Path.GetExtension(image).Substring(1).ToLowerInvariant()),
+ MultipartPostPart.CreateFormPart("tile", tile.ToString().ToLowerInvariant()),
+ };
+ HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileBackgroundImageEndpoint, accessToken, parts);
+ request.ServicePoint.Expect100Continue = false;
+ IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request);
+ string responseString = response.GetResponseReader().ReadToEnd();
+ return XDocument.Parse(responseString);
+ }
+
+ public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, string pathToImage) {
+ string contentType = "image/" + Path.GetExtension(pathToImage).Substring(1).ToLowerInvariant();
+ return UpdateProfileImage(twitter, accessToken, File.OpenRead(pathToImage), contentType);
+ }
+
+ public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, Stream image, string contentType) {
+ var parts = new[] {
+ MultipartPostPart.CreateFormFilePart("image", "twitterPhoto", contentType, image),
+ };
+ HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileImageEndpoint, accessToken, parts);
+ IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request);
+ string responseString = response.GetResponseReader().ReadToEnd();
+ return XDocument.Parse(responseString);
+ }
+
+ public static XDocument VerifyCredentials(ConsumerBase twitter, string accessToken) {
+ IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(VerifyCredentialsEndpoint, accessToken);
+ return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
+ }
+
+ public static string GetUsername(ConsumerBase twitter, string accessToken) {
+ XDocument xml = VerifyCredentials(twitter, accessToken);
+ XPathNavigator nav = xml.CreateNavigator();
+ return nav.SelectSingleNode("/user/screen_name").Value;
+ }
+
+ /// <summary>
+ /// Prepares a redirect that will send the user to Twitter to sign in.
+ /// </summary>
+ /// <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param>
+ /// <returns>The redirect message.</returns>
+ /// <remarks>
+ /// Call <see cref="OutgoingWebResponse.Send"/> or
+ /// <c>return StartSignInWithTwitter().<see cref="MessagingUtilities.AsActionResult">AsActionResult()</see></c>
+ /// to actually perform the redirect.
+ /// </remarks>
+ public static OutgoingWebResponse StartSignInWithTwitter(bool forceNewLogin) {
+ var redirectParameters = new Dictionary<string, string>();
+ if (forceNewLogin) {
+ redirectParameters["force_login"] = "true";
+ }
+ Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_");
+ var request = TwitterSignIn.PrepareRequestUserAuthorization(callback, null, redirectParameters);
+ return TwitterSignIn.Channel.PrepareResponse(request);
+ }
+
+ /// <summary>
+ /// Checks the incoming web request to see if it carries a Twitter authentication response,
+ /// and provides the user's Twitter screen name and unique id if available.
+ /// </summary>
+ /// <param name="screenName">The user's Twitter screen name.</param>
+ /// <param name="userId">The user's Twitter unique user ID.</param>
+ /// <returns>
+ /// A value indicating whether Twitter authentication was successful;
+ /// otherwise <c>false</c> to indicate that no Twitter response was present.
+ /// </returns>
+ public static bool TryFinishSignInWithTwitter(out string screenName, out int userId) {
+ screenName = null;
+ userId = 0;
+ var response = TwitterSignIn.ProcessUserAuthorization();
+ if (response == null) {
+ return false;
+ }
+
+ screenName = response.ExtraData["screen_name"];
+ userId = int.Parse(response.ExtraData["user_id"]);
+
+ // If we were going to make this LOOK like OpenID even though it isn't,
+ // this seems like a reasonable, secure claimed id to allow the user to assume.
+ OpenId.Identifier fake_claimed_id = string.Format(CultureInfo.InvariantCulture, "http://twitter.com/{0}#{1}", screenName, userId);
+
+ return true;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/Util.cs b/src/DotNetOpenAuth.ApplicationBlock/Util.cs
new file mode 100644
index 0000000..0bec372
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/Util.cs
@@ -0,0 +1,259 @@
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Net;
+ using DotNetOpenAuth.Messaging;
+
+ public static class Util {
+ /// <summary>
+ /// Pseudo-random data generator.
+ /// </summary>
+ internal static readonly Random NonCryptoRandomDataGenerator = new Random();
+
+ /// <summary>
+ /// Sets the channel's outgoing HTTP requests to use default network credentials.
+ /// </summary>
+ /// <param name="channel">The channel to modify.</param>
+ public static void UseDefaultNetworkCredentialsOnOutgoingHttpRequests(this Channel channel)
+ {
+ Debug.Assert(!(channel.WebRequestHandler is WrappingWebRequestHandler), "Wrapping an already wrapped web request handler. This is legal, but highly suspect of a bug as you don't want to wrap the same channel repeatedly to apply the same effect.");
+ AddOutgoingHttpRequestTransform(channel, http => http.Credentials = CredentialCache.DefaultNetworkCredentials);
+ }
+
+ /// <summary>
+ /// Adds some action to any outgoing HTTP request on this channel.
+ /// </summary>
+ /// <param name="channel">The channel's whose outgoing HTTP requests should be modified.</param>
+ /// <param name="action">The action to perform on outgoing HTTP requests.</param>
+ internal static void AddOutgoingHttpRequestTransform(this Channel channel, Action<HttpWebRequest> action) {
+ if (channel == null) {
+ throw new ArgumentNullException("channel");
+ }
+
+ if (action == null) {
+ throw new ArgumentNullException("action");
+ }
+
+ channel.WebRequestHandler = new WrappingWebRequestHandler(channel.WebRequestHandler, action);
+ }
+
+ /// <summary>
+ /// Enumerates through the individual set bits in a flag enum.
+ /// </summary>
+ /// <param name="flags">The flags enum value.</param>
+ /// <returns>An enumeration of just the <i>set</i> bits in the flags enum.</returns>
+ internal static IEnumerable<long> GetIndividualFlags(Enum flags) {
+ long flagsLong = Convert.ToInt64(flags);
+ for (int i = 0; i < sizeof(long) * 8; i++) { // long is the type behind the largest enum
+ // Select an individual application from the scopes.
+ long individualFlagPosition = (long)Math.Pow(2, i);
+ long individualFlag = flagsLong & individualFlagPosition;
+ if (individualFlag == individualFlagPosition) {
+ yield return individualFlag;
+ }
+ }
+ }
+
+ internal static Uri GetCallbackUrlFromContext() {
+ Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_");
+ return callback;
+ }
+
+ /// <summary>
+ /// Copies the contents of one stream to another.
+ /// </summary>
+ /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
+ /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
+ /// <returns>The total number of bytes copied.</returns>
+ /// <remarks>
+ /// Copying begins at the streams' current positions.
+ /// The positions are NOT reset after copying is complete.
+ /// </remarks>
+ internal static int CopyTo(this Stream copyFrom, Stream copyTo) {
+ return CopyTo(copyFrom, copyTo, int.MaxValue);
+ }
+
+ /// <summary>
+ /// Copies the contents of one stream to another.
+ /// </summary>
+ /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
+ /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
+ /// <param name="maximumBytesToCopy">The maximum bytes to copy.</param>
+ /// <returns>The total number of bytes copied.</returns>
+ /// <remarks>
+ /// Copying begins at the streams' current positions.
+ /// The positions are NOT reset after copying is complete.
+ /// </remarks>
+ internal static int CopyTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) {
+ if (copyFrom == null) {
+ throw new ArgumentNullException("copyFrom");
+ }
+ if (copyTo == null) {
+ throw new ArgumentNullException("copyTo");
+ }
+
+ byte[] buffer = new byte[1024];
+ int readBytes;
+ int totalCopiedBytes = 0;
+ while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(1024, maximumBytesToCopy))) > 0) {
+ int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
+ copyTo.Write(buffer, 0, writeBytes);
+ totalCopiedBytes += writeBytes;
+ maximumBytesToCopy -= writeBytes;
+ }
+
+ return totalCopiedBytes;
+ }
+
+ /// <summary>
+ /// Wraps some instance of a web request handler in order to perform some extra operation on all
+ /// outgoing HTTP requests.
+ /// </summary>
+ private class WrappingWebRequestHandler : IDirectWebRequestHandler
+ {
+ /// <summary>
+ /// The handler being wrapped.
+ /// </summary>
+ private readonly IDirectWebRequestHandler wrappedHandler;
+
+ /// <summary>
+ /// The action to perform on outgoing HTTP requests.
+ /// </summary>
+ private readonly Action<HttpWebRequest> action;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WrappingWebRequestHandler"/> class.
+ /// </summary>
+ /// <param name="wrappedHandler">The HTTP handler to wrap.</param>
+ /// <param name="action">The action to perform on outgoing HTTP requests.</param>
+ internal WrappingWebRequestHandler(IDirectWebRequestHandler wrappedHandler, Action<HttpWebRequest> action)
+ {
+ if (wrappedHandler == null) {
+ throw new ArgumentNullException("wrappedHandler");
+ }
+
+ if (action == null) {
+ throw new ArgumentNullException("action");
+ }
+
+ this.wrappedHandler = wrappedHandler;
+ this.action = action;
+ }
+
+ #region Implementation of IDirectWebRequestHandler
+
+ /// <summary>
+ /// Determines whether this instance can support the specified options.
+ /// </summary>
+ /// <param name="options">The set of options that might be given in a subsequent web request.</param>
+ /// <returns>
+ /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>.
+ /// </returns>
+ public bool CanSupport(DirectWebRequestOptions options)
+ {
+ return this.wrappedHandler.CanSupport(options);
+ }
+
+ /// <summary>
+ /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
+ /// <returns>
+ /// The stream the caller should write out the entity data to.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown for any network error.</exception>
+ /// <remarks>
+ /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
+ /// and any other appropriate properties <i>before</i> calling this method.
+ /// Callers <i>must</i> close and dispose of the request stream when they are done
+ /// writing to it to avoid taking up the connection too long and causing long waits on
+ /// subsequent requests.</para>
+ /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
+ /// <see cref="ProtocolException"/> to abstract away the transport and provide
+ /// a single exception type for hosts to catch.</para>
+ /// </remarks>
+ public Stream GetRequestStream(HttpWebRequest request)
+ {
+ this.action(request);
+ return this.wrappedHandler.GetRequestStream(request);
+ }
+
+ /// <summary>
+ /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
+ /// <param name="options">The options to apply to this web request.</param>
+ /// <returns>
+ /// The stream the caller should write out the entity data to.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown for any network error.</exception>
+ /// <remarks>
+ /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
+ /// and any other appropriate properties <i>before</i> calling this method.
+ /// Callers <i>must</i> close and dispose of the request stream when they are done
+ /// writing to it to avoid taking up the connection too long and causing long waits on
+ /// subsequent requests.</para>
+ /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
+ /// <see cref="ProtocolException"/> to abstract away the transport and provide
+ /// a single exception type for hosts to catch.</para>
+ /// </remarks>
+ public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options)
+ {
+ this.action(request);
+ return this.wrappedHandler.GetRequestStream(request, options);
+ }
+
+ /// <summary>
+ /// Processes an <see cref="HttpWebRequest"/> and converts the
+ /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
+ /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns>
+ /// <exception cref="ProtocolException">Thrown for any network error.</exception>
+ /// <remarks>
+ /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
+ /// <see cref="ProtocolException"/> to abstract away the transport and provide
+ /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
+ /// value, if set, should be Closed before throwing.</para>
+ /// </remarks>
+ public IncomingWebResponse GetResponse(HttpWebRequest request)
+ {
+ // If the request has an entity, the action would have already been processed in GetRequestStream.
+ if (request.Method == "GET")
+ {
+ this.action(request);
+ }
+
+ return this.wrappedHandler.GetResponse(request);
+ }
+
+ /// <summary>
+ /// Processes an <see cref="HttpWebRequest"/> and converts the
+ /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
+ /// <param name="options">The options to apply to this web request.</param>
+ /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns>
+ /// <exception cref="ProtocolException">Thrown for any network error.</exception>
+ /// <remarks>
+ /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
+ /// <see cref="ProtocolException"/> to abstract away the transport and provide
+ /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
+ /// value, if set, should be Closed before throwing.</para>
+ /// </remarks>
+ public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options)
+ {
+ // If the request has an entity, the action would have already been processed in GetRequestStream.
+ if (request.Method == "GET") {
+ this.action(request);
+ }
+
+ return this.wrappedHandler.GetResponse(request, options);
+ }
+
+ #endregion
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/WindowsLiveClient.cs b/src/DotNetOpenAuth.ApplicationBlock/WindowsLiveClient.cs
new file mode 100644
index 0000000..be0a650
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/WindowsLiveClient.cs
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------
+// <copyright file="WindowsLiveClient.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.OAuth2;
+
+ public class WindowsLiveClient : WebServerClient {
+ private static readonly AuthorizationServerDescription WindowsLiveDescription = new AuthorizationServerDescription {
+ TokenEndpoint = new Uri("https://oauth.live.com/token"),
+ AuthorizationEndpoint = new Uri("https://oauth.live.com/authorize"),
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WindowsLiveClient"/> class.
+ /// </summary>
+ public WindowsLiveClient()
+ : base(WindowsLiveDescription) {
+ this.AuthorizationTracker = new TokenManager();
+ }
+
+ /// <summary>
+ /// Well-known scopes defined by the Windows Live service.
+ /// </summary>
+ /// <remarks>
+ /// This sample includes just a few scopes. For a complete list of scopes please refer to:
+ /// http://msdn.microsoft.com/en-us/library/hh243646.aspx
+ /// </remarks>
+ public static class Scopes {
+ /// <summary>
+ /// The ability of an app to read and update a user's info at any time. Without this scope, an app can access the user's info only while the user is signed in to Live Connect and is using your app.
+ /// </summary>
+ public const string OfflineAccess = "wl.offline_access";
+
+ /// <summary>
+ /// Single sign-in behavior. With single sign-in, users who are already signed in to Live Connect are also signed in to your website.
+ /// </summary>
+ public const string SignIn = "wl.signin";
+
+ /// <summary>
+ /// Read access to a user's basic profile info. Also enables read access to a user's list of contacts.
+ /// </summary>
+ public const string Basic = "wl.basic";
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/WindowsLiveGraph.cs b/src/DotNetOpenAuth.ApplicationBlock/WindowsLiveGraph.cs
new file mode 100644
index 0000000..4801226
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/WindowsLiveGraph.cs
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------
+// <copyright file="WindowsLiveGraph.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Runtime.Serialization;
+ using System.Runtime.Serialization.Json;
+ using System.Text;
+
+ [DataContract]
+ public class WindowsLiveGraph {
+ private static DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(WindowsLiveGraph));
+
+ [DataMember(Name = "id")]
+ public string Id { get; set; }
+
+ [DataMember(Name = "name")]
+ public string Name { get; set; }
+
+ [DataMember(Name = "first_name")]
+ public string FirstName { get; set; }
+
+ [DataMember(Name = "last_name")]
+ public string LastName { get; set; }
+
+ [DataMember(Name = "link")]
+ public Uri Link { get; set; }
+
+ [DataMember(Name = "gender")]
+ public string Gender { get; set; }
+
+ [DataMember(Name = "updated_time")]
+ public string UpdatedTime { get; set; }
+
+ [DataMember(Name = "locale")]
+ public string Locale { get; set; }
+
+ public static WindowsLiveGraph Deserialize(string json) {
+ if (string.IsNullOrEmpty(json)) {
+ throw new ArgumentNullException("json");
+ }
+
+ return Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(json)));
+ }
+
+ public static WindowsLiveGraph Deserialize(Stream jsonStream) {
+ if (jsonStream == null) {
+ throw new ArgumentNullException("jsonStream");
+ }
+
+ return (WindowsLiveGraph)jsonSerializer.ReadObject(jsonStream);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs b/src/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs
new file mode 100644
index 0000000..bbeb861
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/YammerConsumer.cs
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------
+// <copyright file="YammerConsumer.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ public static class YammerConsumer {
+ /// <summary>
+ /// The Consumer to use for accessing Google data APIs.
+ /// </summary>
+ public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription {
+ RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.yammer.com/oauth/request_token", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest),
+ UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.yammer.com/oauth/authorize", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
+ AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.yammer.com/oauth/access_token", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest),
+ TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new PlaintextSigningBindingElement() },
+ ProtocolVersion = ProtocolVersion.V10,
+ };
+
+ public static DesktopConsumer CreateConsumer(IConsumerTokenManager tokenManager) {
+ return new DesktopConsumer(ServiceDescription, tokenManager);
+ }
+
+ public static Uri PrepareRequestAuthorization(DesktopConsumer consumer, out string requestToken) {
+ if (consumer == null) {
+ throw new ArgumentNullException("consumer");
+ }
+
+ Uri authorizationUrl = consumer.RequestUserAuthorization(null, null, out requestToken);
+ return authorizationUrl;
+ }
+
+ public static AuthorizedTokenResponse CompleteAuthorization(DesktopConsumer consumer, string requestToken, string userCode) {
+ // Because Yammer has a proprietary callback_token parameter, and it's passed
+ // with the message that specifically bans extra arguments being passed, we have
+ // to cheat by adding the data to the URL itself here.
+ var customServiceDescription = new ServiceProviderDescription {
+ RequestTokenEndpoint = ServiceDescription.RequestTokenEndpoint,
+ UserAuthorizationEndpoint = ServiceDescription.UserAuthorizationEndpoint,
+ AccessTokenEndpoint = new MessageReceivingEndpoint(ServiceDescription.AccessTokenEndpoint.Location.AbsoluteUri + "?oauth_verifier=" + Uri.EscapeDataString(userCode), HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest),
+ TamperProtectionElements = ServiceDescription.TamperProtectionElements,
+ ProtocolVersion = ProtocolVersion.V10,
+ };
+
+ // To use a custom service description we also must create a new WebConsumer.
+ var customConsumer = new DesktopConsumer(customServiceDescription, consumer.TokenManager);
+ var response = customConsumer.ProcessUserAuthorization(requestToken, userCode);
+ return response;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs b/src/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs
new file mode 100644
index 0000000..d361373
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------
+// <copyright file="YubikeyRelyingParty.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Specialized;
+ using System.Globalization;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Text.RegularExpressions;
+
+ /// <summary>
+ /// The set of possible results from verifying a Yubikey token.
+ /// </summary>
+ public enum YubikeyResult {
+ /// <summary>
+ /// The OTP is valid.
+ /// </summary>
+ Ok,
+
+ /// <summary>
+ /// The OTP is invalid format.
+ /// </summary>
+ BadOtp,
+
+ /// <summary>
+ /// The OTP has already been seen by the service.
+ /// </summary>
+ ReplayedOtp,
+
+ /// <summary>
+ /// The HMAC signature verification failed.
+ /// </summary>
+ /// <remarks>
+ /// This indicates a bug in the relying party code.
+ /// </remarks>
+ BadSignature,
+
+ /// <summary>
+ /// The request lacks a parameter.
+ /// </summary>
+ /// <remarks>
+ /// This indicates a bug in the relying party code.
+ /// </remarks>
+ MissingParameter,
+
+ /// <summary>
+ /// The request id does not exist.
+ /// </summary>
+ NoSuchClient,
+
+ /// <summary>
+ /// The request id is not allowed to verify OTPs.
+ /// </summary>
+ OperationNotAllowed,
+
+ /// <summary>
+ /// Unexpected error in our server. Please contact Yubico if you see this error.
+ /// </summary>
+ BackendError,
+ }
+
+ /// <summary>
+ /// Provides verification of a Yubikey one-time password (OTP) as a means of authenticating
+ /// a user at your web site or application.
+ /// </summary>
+ /// <remarks>
+ /// Please visit http://yubico.com/ for more information about this authentication method.
+ /// </remarks>
+ public class YubikeyRelyingParty {
+ /// <summary>
+ /// The default Yubico authorization server to use for validation and replay protection.
+ /// </summary>
+ private const string DefaultYubicoAuthorizationServer = "https://api.yubico.com/wsapi/verify";
+
+ /// <summary>
+ /// The format of the lines in the Yubico server response.
+ /// </summary>
+ private static readonly Regex ResultLineMatcher = new Regex(@"^(?<key>[^=]+)=(?<value>.*)$");
+
+ /// <summary>
+ /// The Yubico authorization server to use for validation and replay protection.
+ /// </summary>
+ private readonly string yubicoAuthorizationServer;
+
+ /// <summary>
+ /// The authorization ID assigned to your individual site by Yubico.
+ /// </summary>
+ private readonly int yubicoAuthorizationId;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="YubikeyRelyingParty"/> class
+ /// that uses the default Yubico server for validation and replay protection.
+ /// </summary>
+ /// <param name="authorizationId">The authorization ID assigned to your individual site by Yubico.
+ /// Get one from https://upgrade.yubico.com/getapikey/</param>
+ public YubikeyRelyingParty(int authorizationId)
+ : this(authorizationId, DefaultYubicoAuthorizationServer) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="YubikeyRelyingParty"/> class.
+ /// </summary>
+ /// <param name="authorizationId">The authorization ID assigned to your individual site by Yubico.
+ /// Contact tech@yubico.com if you haven't got an authId for your site.</param>
+ /// <param name="yubicoAuthorizationServer">The Yubico authorization server to use for validation and replay protection.</param>
+ public YubikeyRelyingParty(int authorizationId, string yubicoAuthorizationServer) {
+ if (authorizationId < 0) {
+ throw new ArgumentOutOfRangeException("authorizationId");
+ }
+
+ if (!Uri.IsWellFormedUriString(yubicoAuthorizationServer, UriKind.Absolute)) {
+ throw new ArgumentException("Invalid authorization server URI", "yubicoAuthorizationServer");
+ }
+
+ if (!yubicoAuthorizationServer.StartsWith(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ throw new ArgumentException("HTTPS is required for the Yubico server. HMAC response verification not supported.", "yubicoAuthorizationServer");
+ }
+
+ this.yubicoAuthorizationId = authorizationId;
+ this.yubicoAuthorizationServer = yubicoAuthorizationServer;
+ }
+
+ /// <summary>
+ /// Extracts the username out of a Yubikey token.
+ /// </summary>
+ /// <param name="yubikeyToken">The yubikey token.</param>
+ /// <returns>A 12 character string that is unique for this particular Yubikey device.</returns>
+ public static string ExtractUsername(string yubikeyToken) {
+ EnsureWellFormedToken(yubikeyToken);
+ return yubikeyToken.Substring(0, 12);
+ }
+
+ /// <summary>
+ /// Determines whether the specified yubikey token is valid and has not yet been used.
+ /// </summary>
+ /// <param name="yubikeyToken">The yubikey token.</param>
+ /// <returns>
+ /// <c>true</c> if the specified yubikey token is valid; otherwise, <c>false</c>.
+ /// </returns>
+ /// <exception cref="WebException">Thrown when the validity of the token could not be confirmed due to network issues.</exception>
+ public YubikeyResult IsValid(string yubikeyToken) {
+ EnsureWellFormedToken(yubikeyToken);
+
+ StringBuilder authorizationUri = new StringBuilder(this.yubicoAuthorizationServer);
+ authorizationUri.Append("?id=");
+ authorizationUri.Append(Uri.EscapeDataString(this.yubicoAuthorizationId.ToString(CultureInfo.InvariantCulture)));
+ authorizationUri.Append("&otp=");
+ authorizationUri.Append(Uri.EscapeDataString(yubikeyToken));
+
+ var request = WebRequest.Create(authorizationUri.ToString());
+ using (var response = request.GetResponse()) {
+ using (var responseReader = new StreamReader(response.GetResponseStream())) {
+ string line;
+ var result = new NameValueCollection();
+ while ((line = responseReader.ReadLine()) != null) {
+ Match m = ResultLineMatcher.Match(line);
+ if (m.Success) {
+ result[m.Groups["key"].Value] = m.Groups["value"].Value;
+ }
+ }
+
+ return ParseResult(result["status"]);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Parses the Yubico server result.
+ /// </summary>
+ /// <param name="status">The status field from the response.</param>
+ /// <returns>The enum value representing the result.</returns>
+ private static YubikeyResult ParseResult(string status) {
+ switch (status) {
+ case "OK": return YubikeyResult.Ok;
+ case "BAD_OTP": return YubikeyResult.BadOtp;
+ case "REPLAYED_OTP": return YubikeyResult.ReplayedOtp;
+ case "BAD_SIGNATURE": return YubikeyResult.BadSignature;
+ case "MISSING_PARAMETER": return YubikeyResult.MissingParameter;
+ case "NO_SUCH_CLIENT": return YubikeyResult.NoSuchClient;
+ case "OPERATION_NOT_ALLOWED": return YubikeyResult.OperationNotAllowed;
+ case "BACKEND_ERROR": return YubikeyResult.BackendError;
+ default: throw new ArgumentOutOfRangeException("status", status, "Unexpected status value.");
+ }
+ }
+
+ /// <summary>
+ /// Ensures the OTP is well formed.
+ /// </summary>
+ /// <param name="yubikeyToken">The yubikey token.</param>
+ private static void EnsureWellFormedToken(string yubikeyToken) {
+ if (yubikeyToken == null) {
+ throw new ArgumentNullException("yubikeyToken");
+ }
+
+ yubikeyToken = yubikeyToken.Trim();
+
+ if (yubikeyToken.Length <= 12) {
+ throw new ArgumentException("Yubikey token has unexpected length.");
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.ApplicationBlock/packages.config b/src/DotNetOpenAuth.ApplicationBlock/packages.config
new file mode 100644
index 0000000..4766339
--- /dev/null
+++ b/src/DotNetOpenAuth.ApplicationBlock/packages.config
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="CodeContracts.Unofficial" version="1.0.0.2" />
+ <package id="DotNetOpenAuth.Core" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.InfoCard" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OAuth.Common" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OAuth.Consumer" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OAuth.Core" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OAuth.ServiceProvider" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OAuth2.Client" version="0.23.0-draft" />
+ <package id="DotNetOpenAuth.OAuth2.Client" version="0.23.0-draft2" />
+ <package id="DotNetOpenAuth.OAuth2.Client.UI" version="0.23.0-draft" />
+ <package id="DotNetOpenAuth.OAuth2.Client.UI" version="0.23.0-draft2" />
+ <package id="DotNetOpenAuth.OAuth2.Core" version="0.23.0-draft" />
+ <package id="DotNetOpenAuth.OAuth2.Core" version="0.23.0-draft2" />
+ <package id="DotNetOpenAuth.OpenId.Core" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OpenId.Provider" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OpenId.RelyingParty" version="4.0.0.12084" />
+ <package id="DotNetOpenAuth.OpenIdOAuth" version="4.0.0.12084" />
+</packages> \ No newline at end of file