summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nuget/DotNetOpenAuth.OAuth2.Core.nuspec2
-rw-r--r--nuget/content/OAuth2.AuthorizationServer/web.config.transform9
-rw-r--r--nuget/content/OAuth2.Client/web.config.transform9
-rw-r--r--nuget/content/OAuth2.Core/web.config.transform7
-rw-r--r--nuget/content/OAuth2.ResourceServer/web.config.transform9
-rw-r--r--projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj2
-rw-r--r--projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj11
-rw-r--r--samples/OAuthAuthorizationServer/Web.config9
-rw-r--r--samples/OAuthClient/Facebook.aspx.cs2
-rw-r--r--samples/OAuthClient/WindowsLive.aspx.cs2
-rw-r--r--samples/OAuthServiceProvider/OAuthServiceProvider.csproj4
-rw-r--r--src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd89
-rw-r--r--src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj2
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Channel.cs25
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs16
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs2
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs22
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs75
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Requires.cs5
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs1
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs70
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj5
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs14
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs25
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs91
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs74
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs48
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs34
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs44
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs12
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs36
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj5
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs27
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs28
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs60
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs165
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs31
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs17
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs9
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs22
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs36
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs36
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj3
-rw-r--r--src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs26
-rw-r--r--src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj2
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs32
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs63
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs3
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj1
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs37
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs7
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs33
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs5
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs1
-rw-r--r--src/DotNetOpenAuth.Test/TestUtilities.cs21
59 files changed, 1347 insertions, 93 deletions
diff --git a/nuget/DotNetOpenAuth.OAuth2.Core.nuspec b/nuget/DotNetOpenAuth.OAuth2.Core.nuspec
index f957069..28c2614 100644
--- a/nuget/DotNetOpenAuth.OAuth2.Core.nuspec
+++ b/nuget/DotNetOpenAuth.OAuth2.Core.nuspec
@@ -26,6 +26,8 @@
<file src="$OutputPath35$DotNetOpenAuth.OAuth2.xml" target="lib\net35-full" />
<file src="$OutputPath40$DotNetOpenAuth.OAuth2.xml" target="lib\net40-full" />
+ <file src="content\OAuth2.Core\web.config.transform" target="content\web.config.transform" />
+
<file src="..\src\DotNetOpenAuth.OAuth2\**\*.cs" target="src" />
</files>
</package> \ No newline at end of file
diff --git a/nuget/content/OAuth2.AuthorizationServer/web.config.transform b/nuget/content/OAuth2.AuthorizationServer/web.config.transform
new file mode 100644
index 0000000..c53fb7c
--- /dev/null
+++ b/nuget/content/OAuth2.AuthorizationServer/web.config.transform
@@ -0,0 +1,9 @@
+<configuration>
+ <configSections>
+ <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
+ <sectionGroup name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2SectionGroup, DotNetOpenAuth.OAuth2">
+ <section name="authorizationServer" type="DotNetOpenAuth.Configuration.OAuth2AuthorizationServerSection, DotNetOpenAuth.OAuth2.AuthorizationServer" requirePermission="false" allowLocation="true" />
+ </sectionGroup>
+ </sectionGroup>
+ </configSections>
+</configuration> \ No newline at end of file
diff --git a/nuget/content/OAuth2.Client/web.config.transform b/nuget/content/OAuth2.Client/web.config.transform
new file mode 100644
index 0000000..b1f4429
--- /dev/null
+++ b/nuget/content/OAuth2.Client/web.config.transform
@@ -0,0 +1,9 @@
+<configuration>
+ <configSections>
+ <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
+ <sectionGroup name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2SectionGroup, DotNetOpenAuth.OAuth2">
+ <section name="client" type="DotNetOpenAuth.Configuration.OAuth2ClientSection, DotNetOpenAuth.OAuth2.Client" requirePermission="false" allowLocation="true" />
+ </sectionGroup>
+ </sectionGroup>
+ </configSections>
+</configuration> \ No newline at end of file
diff --git a/nuget/content/OAuth2.Core/web.config.transform b/nuget/content/OAuth2.Core/web.config.transform
new file mode 100644
index 0000000..2c47af1
--- /dev/null
+++ b/nuget/content/OAuth2.Core/web.config.transform
@@ -0,0 +1,7 @@
+<configuration>
+ <configSections>
+ <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
+ <sectionGroup name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2SectionGroup, DotNetOpenAuth.OAuth2" />
+ </sectionGroup>
+ </configSections>
+</configuration> \ No newline at end of file
diff --git a/nuget/content/OAuth2.ResourceServer/web.config.transform b/nuget/content/OAuth2.ResourceServer/web.config.transform
new file mode 100644
index 0000000..a92ff7d
--- /dev/null
+++ b/nuget/content/OAuth2.ResourceServer/web.config.transform
@@ -0,0 +1,9 @@
+<configuration>
+ <configSections>
+ <sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
+ <sectionGroup name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2SectionGroup, DotNetOpenAuth.OAuth2">
+ <section name="resourceServer" type="DotNetOpenAuth.Configuration.OAuth2ResourceServerSection, DotNetOpenAuth.OAuth2.ResourceServer" requirePermission="false" allowLocation="true" />
+ </sectionGroup>
+ </sectionGroup>
+ </configSections>
+</configuration> \ No newline at end of file
diff --git a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj
index 4db4969..4ef10f6 100644
--- a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj
+++ b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj
@@ -81,7 +81,6 @@
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Default.aspx.cs">
<DependentUpon>Default.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
@@ -94,7 +93,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Setup.aspx.cs">
<DependentUpon>Setup.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Setup.aspx.designer.cs">
<DependentUpon>Setup.aspx</DependentUpon>
diff --git a/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj b/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj
index 46236ab..92a0b5f 100644
--- a/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj
+++ b/projecttemplates/WebFormsRelyingParty/WebFormsRelyingParty.csproj
@@ -99,42 +99,36 @@
<Compile Include="Code\SiteUtilities.cs" />
<Compile Include="Members\OAuthAuthorize.aspx.cs">
<DependentUpon>OAuthAuthorize.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Members\OAuthAuthorize.aspx.designer.cs">
<DependentUpon>OAuthAuthorize.aspx</DependentUpon>
</Compile>
<Compile Include="LoginFrame.aspx.cs">
<DependentUpon>LoginFrame.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="LoginFrame.aspx.designer.cs">
<DependentUpon>LoginFrame.aspx</DependentUpon>
</Compile>
<Compile Include="Members\AccountInfo.aspx.cs">
<DependentUpon>AccountInfo.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Members\AccountInfo.aspx.designer.cs">
<DependentUpon>AccountInfo.aspx</DependentUpon>
</Compile>
<Compile Include="Admin\Admin.Master.cs">
<DependentUpon>Admin.Master</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Admin\Admin.Master.designer.cs">
<DependentUpon>Admin.Master</DependentUpon>
</Compile>
<Compile Include="Admin\Default.aspx.cs">
<DependentUpon>Default.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Admin\Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="Default.aspx.cs">
<DependentUpon>Default.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
@@ -144,21 +138,18 @@
</Compile>
<Compile Include="Login.aspx.cs">
<DependentUpon>Login.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Login.aspx.designer.cs">
<DependentUpon>Login.aspx</DependentUpon>
</Compile>
<Compile Include="Logout.aspx.cs">
<DependentUpon>Logout.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Logout.aspx.designer.cs">
<DependentUpon>Logout.aspx</DependentUpon>
</Compile>
<Compile Include="Members\Default.aspx.cs">
<DependentUpon>Default.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Members\Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
@@ -169,14 +160,12 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Setup.aspx.cs">
<DependentUpon>Setup.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Setup.aspx.designer.cs">
<DependentUpon>Setup.aspx</DependentUpon>
</Compile>
<Compile Include="Site.Master.cs">
<DependentUpon>Site.Master</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Site.Master.designer.cs">
<DependentUpon>Site.Master</DependentUpon>
diff --git a/samples/OAuthAuthorizationServer/Web.config b/samples/OAuthAuthorizationServer/Web.config
index b68bb88..37157fd 100644
--- a/samples/OAuthAuthorizationServer/Web.config
+++ b/samples/OAuthAuthorizationServer/Web.config
@@ -11,6 +11,9 @@
<sectionGroup name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth.Core">
<section name="openid" type="DotNetOpenAuth.Configuration.OpenIdElement, DotNetOpenAuth.OpenId" requirePermission="false" allowLocation="true" />
<section name="oauth" type="DotNetOpenAuth.Configuration.OAuthElement, DotNetOpenAuth.OAuth" requirePermission="false" allowLocation="true" />
+ <sectionGroup name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2SectionGroup, DotNetOpenAuth.OAuth2">
+ <section name="authorizationServer" type="DotNetOpenAuth.Configuration.OAuth2AuthorizationServerSection, DotNetOpenAuth.OAuth2.AuthorizationServer" requirePermission="false" allowLocation="true" />
+ </sectionGroup>
<section name="messaging" type="DotNetOpenAuth.Configuration.MessagingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" />
<section name="reporting" type="DotNetOpenAuth.Configuration.ReportingElement, DotNetOpenAuth.Core" requirePermission="false" allowLocation="true" />
</sectionGroup>
@@ -38,6 +41,10 @@
<dotNetOpenAuth>
<!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. -->
<reporting enabled="true" />
+ <oauth2>
+ <authorizationServer>
+ </authorizationServer>
+ </oauth2>
<!-- Relaxing SSL requirements is useful for simple samples, but NOT a good idea in production. -->
<messaging relaxSslRequirements="true">
@@ -64,7 +71,7 @@
</log4net>
<connectionStrings>
- <add name="DatabaseConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Database2.mdf;Integrated Security=True;User Instance=True"
+ <add name="DatabaseConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Database4.mdf;Integrated Security=True;User Instance=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
diff --git a/samples/OAuthClient/Facebook.aspx.cs b/samples/OAuthClient/Facebook.aspx.cs
index 0f71712..4701d24 100644
--- a/samples/OAuthClient/Facebook.aspx.cs
+++ b/samples/OAuthClient/Facebook.aspx.cs
@@ -10,7 +10,7 @@
public partial class Facebook : System.Web.UI.Page {
private static readonly FacebookClient client = new FacebookClient {
ClientIdentifier = ConfigurationManager.AppSettings["facebookAppID"],
- ClientSecret = ConfigurationManager.AppSettings["facebookAppSecret"],
+ ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(ConfigurationManager.AppSettings["facebookAppSecret"]),
};
protected void Page_Load(object sender, EventArgs e) {
diff --git a/samples/OAuthClient/WindowsLive.aspx.cs b/samples/OAuthClient/WindowsLive.aspx.cs
index b550e17..05101a7 100644
--- a/samples/OAuthClient/WindowsLive.aspx.cs
+++ b/samples/OAuthClient/WindowsLive.aspx.cs
@@ -14,7 +14,7 @@
public partial class WindowsLive : System.Web.UI.Page {
private static readonly WindowsLiveClient client = new WindowsLiveClient {
ClientIdentifier = ConfigurationManager.AppSettings["windowsLiveAppID"],
- ClientSecret = ConfigurationManager.AppSettings["WindowsLiveAppSecret"],
+ ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(ConfigurationManager.AppSettings["WindowsLiveAppSecret"]),
};
protected void Page_Load(object sender, EventArgs e) {
diff --git a/samples/OAuthServiceProvider/OAuthServiceProvider.csproj b/samples/OAuthServiceProvider/OAuthServiceProvider.csproj
index c96721e..fd2a5bb 100644
--- a/samples/OAuthServiceProvider/OAuthServiceProvider.csproj
+++ b/samples/OAuthServiceProvider/OAuthServiceProvider.csproj
@@ -114,19 +114,15 @@
</Compile>
<Compile Include="Default.aspx.cs">
<DependentUpon>Default.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Members\Authorize.aspx.cs">
<DependentUpon>Authorize.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Members\AuthorizedConsumers.aspx.cs">
<DependentUpon>AuthorizedConsumers.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="TracePage.aspx.cs">
<DependentUpon>TracePage.aspx</DependentUpon>
- <SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="TracePage.aspx.designer.cs">
<DependentUpon>TracePage.aspx</DependentUpon>
diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
index d193776..74d4db4 100644
--- a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
@@ -479,12 +479,19 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="add">
<xs:complexType>
- <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="type" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>
+ The fully-qualified name of the type that implements the IIdentifierDiscoveryService interface.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="xaml" type="xs:string" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="remove">
<xs:complexType>
- <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="clear">
@@ -898,6 +905,84 @@
</xs:choice>
</xs:complexType>
</xs:element>
+ <xs:element name="oauth2">
+ <xs:annotation>
+ <xs:documentation>
+ Settings OAuth 2 clients, authorization servers and resource servers.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="client">
+ <xs:annotation>
+ <xs:documentation>
+ Settings applicable to OAuth 2 Clients.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="authorizationServer">
+ <xs:annotation>
+ <xs:documentation>
+ Settings applicable to OAuth 2 Authorization Servers.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="clientAuthenticationModules">
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="add">
+ <xs:complexType>
+ <xs:attribute name="type" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>
+ The fully-qualified name of the ClientAuthenticationModule-derived type.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="xaml" type="xs:string" use="optional" />
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="remove">
+ <xs:complexType>
+ <xs:attribute name="type" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>
+ The fully-qualified name of the ClientAuthenticationModule-derived type.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="clear">
+ <xs:complexType>
+ <!--tag is empty-->
+ </xs:complexType>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="resourceServer">
+ <xs:annotation>
+ <xs:documentation>
+ Settings applicable to OAuth 2 Resource Servers.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
<xs:element name="reporting">
<xs:annotation>
<xs:documentation>
diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
index 447a3c5..65dee44 100644
--- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
+++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
@@ -29,6 +29,8 @@
<Compile Include="Messaging\ChannelContract.cs" />
<Compile Include="Messaging\DataBagFormatterBase.cs" />
<Compile Include="Messaging\HttpRequestHeaders.cs" />
+ <Compile Include="Messaging\IHttpDirectRequest.cs" />
+ <Compile Include="Messaging\IHttpDirectRequestContract.cs" />
<Compile Include="Messaging\IHttpIndirectResponse.cs" />
<Compile Include="Messaging\IMessageOriginalPayload.cs" />
<Compile Include="Messaging\DirectWebRequestOptions.cs" />
diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
index 016a2b6..235c41b 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
@@ -478,6 +478,14 @@ namespace DotNetOpenAuth.Messaging {
IDirectedProtocolMessage requestMessage = this.ReadFromRequestCore(httpRequest);
if (requestMessage != null) {
Logger.Channel.DebugFormat("Incoming request received: {0}", requestMessage.GetType().Name);
+
+ var directRequest = requestMessage as IHttpDirectRequest;
+ if (directRequest != null) {
+ foreach (string header in httpRequest.Headers) {
+ directRequest.Headers[header] = httpRequest.Headers[header];
+ }
+ }
+
this.ProcessIncomingMessage(requestMessage);
}
@@ -717,6 +725,13 @@ namespace DotNetOpenAuth.Messaging {
Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
HttpWebRequest webRequest = this.CreateHttpRequest(request);
+ var directRequest = request as IHttpDirectRequest;
+ if (directRequest != null) {
+ foreach (string header in directRequest.Headers) {
+ webRequest.Headers[header] = directRequest.Headers[header];
+ }
+ }
+
IDictionary<string, string> responseFields;
IDirectResponseProtocolMessage responseMessage;
@@ -1082,6 +1097,7 @@ namespace DotNetOpenAuth.Messaging {
UriBuilder builder = new UriBuilder(requestMessage.Recipient);
MessagingUtilities.AppendQueryArgs(builder, fields);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
+ this.PrepareHttpWebRequest(httpRequest);
return httpRequest;
}
@@ -1122,6 +1138,7 @@ namespace DotNetOpenAuth.Messaging {
var fields = messageAccessor.Serialize();
var httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
+ this.PrepareHttpWebRequest(httpRequest);
httpRequest.CachePolicy = this.CachePolicy;
httpRequest.Method = "POST";
@@ -1299,6 +1316,14 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Performs additional processing on an outgoing web request before it is sent to the remote server.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ protected virtual void PrepareHttpWebRequest(HttpWebRequest request) {
+ Requires.NotNull(request, "request");
+ }
+
+ /// <summary>
/// Customizes the binding element order for outgoing and incoming messages.
/// </summary>
/// <param name="outgoingOrder">The outgoing order.</param>
diff --git a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs
index f499d67..2237cc7 100644
--- a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs
@@ -193,17 +193,17 @@ namespace DotNetOpenAuth.Messaging {
/// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false.
/// </summary>
/// <param name="condition">True to do nothing; false to throw the exception.</param>
- /// <param name="message">The error message for the exception.</param>
+ /// <param name="unformattedMessage">The error message for the exception.</param>
/// <param name="args">The string formatting arguments, if any.</param>
/// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception>
[Pure]
- internal static void VerifyProtocol(bool condition, string message, params object[] args) {
+ internal static void VerifyProtocol(bool condition, string unformattedMessage, params object[] args) {
Requires.NotNull(args, "args");
Contract.Ensures(condition);
Contract.EnsuresOnThrow<ProtocolException>(!condition);
- Contract.Assume(message != null);
+ Contract.Assume(unformattedMessage != null);
if (!condition) {
- var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, message, args));
+ var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args));
if (Logger.Messaging.IsErrorEnabled) {
Logger.Messaging.Error(
string.Format(
@@ -220,7 +220,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Throws a <see cref="ProtocolException"/>.
/// </summary>
- /// <param name="message">The message to set in the exception.</param>
+ /// <param name="unformattedMessage">The message to set in the exception.</param>
/// <param name="args">The formatting arguments of the message.</param>
/// <returns>
/// An InternalErrorException, which may be "thrown" by the caller in order
@@ -229,10 +229,10 @@ namespace DotNetOpenAuth.Messaging {
/// </returns>
/// <exception cref="ProtocolException">Always thrown.</exception>
[Pure]
- internal static Exception ThrowProtocol(string message, params object[] args) {
+ internal static Exception ThrowProtocol(string unformattedMessage, params object[] args) {
Requires.NotNull(args, "args");
- Contract.Assume(message != null);
- VerifyProtocol(false, message, args);
+ Contract.Assume(unformattedMessage != null);
+ VerifyProtocol(false, unformattedMessage, args);
// we never reach here, but this allows callers to "throw" this method.
return new InternalErrorException();
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs
index 9579a81..dad6bf6 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs
@@ -20,6 +20,11 @@ namespace DotNetOpenAuth.Messaging {
internal const string Authorization = "Authorization";
/// <summary>
+ /// The WWW-Authenticate header, which is included in HTTP 401 Unauthorized responses to help the client know which authorization schemes are supported.
+ /// </summary>
+ internal const string WwwAuthenticate = "WWW-Authenticate";
+
+ /// <summary>
/// The Content-Type header, which specifies the MIME type of the accompanying body data.
/// </summary>
internal const string ContentType = "Content-Type";
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
index 0f60e04..f613dc5 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
@@ -90,7 +90,7 @@ namespace DotNetOpenAuth.Messaging {
this.requestUri = requestUri;
this.form = form ?? new NameValueCollection();
this.queryString = HttpUtility.ParseQueryString(requestUri.Query);
- this.headers = headers ?? new NameValueCollection();
+ this.headers = headers ?? new WebHeaderCollection();
this.serverVariables = new NameValueCollection();
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs
new file mode 100644
index 0000000..7153334
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="IHttpDirectRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System.Diagnostics.Contracts;
+ using System.Net;
+
+ /// <summary>
+ /// An interface that allows direct request messages to capture the details of the HTTP request they arrived on.
+ /// </summary>
+ [ContractClass(typeof(IHttpDirectRequestContract))]
+ public interface IHttpDirectRequest : IMessage {
+ /// <summary>
+ /// Gets the HTTP headers of the request.
+ /// </summary>
+ /// <value>May be an empty collection, but must not be <c>null</c>.</value>
+ WebHeaderCollection Headers { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs
new file mode 100644
index 0000000..cfde6cf
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------
+// <copyright file="IHttpDirectRequestContract.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+
+ /// <summary>
+ /// Contract class for the <see cref="IHttpDirectRequest"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IHttpDirectRequest))]
+ public abstract class IHttpDirectRequestContract : IHttpDirectRequest {
+ #region IHttpDirectRequest Members
+
+ /// <summary>
+ /// Gets the HTTP headers of the request.
+ /// </summary>
+ /// <value>May be an empty collection, but must not be <c>null</c>.</value>
+ WebHeaderCollection IHttpDirectRequest.Headers {
+ get {
+ Contract.Ensures(Contract.Result<WebHeaderCollection>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ #region IMessage Members
+
+ /// <summary>
+ /// Gets the version of the protocol or extension this message is prepared to implement.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ Version IMessage.Version {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ IDictionary<string, string> IMessage.ExtraData {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void IMessage.EnsureValidMessage() {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
index a5fe782..7691cc4 100644
--- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
@@ -35,6 +35,11 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="context">The context in which to set the response.</param>
public override void ExecuteResult(ControllerContext context) {
this.response.Respond(context.HttpContext);
+
+ // MVC likes to muck with our response. For example, when returning contrived 401 Unauthorized responses
+ // MVC will rewrite our response and turn it into a redirect, which breaks OAuth 2 authorization server token endpoints.
+ // It turns out we can prevent this unwanted behavior by flushing the response before returning from this method.
+ context.HttpContext.Response.Flush();
}
}
}
diff --git a/src/DotNetOpenAuth.Core/Requires.cs b/src/DotNetOpenAuth.Core/Requires.cs
index 7a196a3..7d4d5be 100644
--- a/src/DotNetOpenAuth.Core/Requires.cs
+++ b/src/DotNetOpenAuth.Core/Requires.cs
@@ -43,14 +43,17 @@ namespace DotNetOpenAuth {
/// </summary>
/// <param name="value">The value.</param>
/// <param name="parameterName">Name of the parameter.</param>
+ /// <returns>The validated value.</returns>
#if !CLR4
[ContractArgumentValidator]
#endif
[Pure, DebuggerStepThrough]
- internal static void NotNullOrEmpty(string value, string parameterName) {
+ internal static string NotNullOrEmpty(string value, string parameterName) {
NotNull(value, parameterName);
True(value.Length > 0, parameterName, Strings.EmptyStringNotAllowed);
+ Contract.Ensures(Contract.Result<string>() == value);
Contract.EndContractBlock();
+ return value;
}
/// <summary>
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs
index b04c67e..db131a9 100644
--- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -297,6 +297,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
MessagingUtilities.AppendQueryArgs(recipientBuilder, requestMessage.ExtraData);
}
httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri);
+ this.PrepareHttpWebRequest(httpRequest);
httpRequest.Method = GetHttpMethod(requestMessage);
httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields));
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs
new file mode 100644
index 0000000..6511a11
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth2AuthorizationServerSection.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+
+ /// <summary>
+ /// Represents the &lt;oauth2/authorizationServer&gt; section in the host's .config file.
+ /// </summary>
+ internal class OAuth2AuthorizationServerSection : ConfigurationSection {
+ /// <summary>
+ /// The name of the oauth2/authorizationServer section.
+ /// </summary>
+ private const string SectionName = OAuth2SectionGroup.SectionName + "/authorizationServer";
+
+ /// <summary>
+ /// The name of the &lt;clientAuthenticationModules&gt; sub-element.
+ /// </summary>
+ private const string ClientAuthenticationModulesElementName = "clientAuthenticationModules";
+
+ /// <summary>
+ /// The built-in set of client authentication modules.
+ /// </summary>
+ private static readonly TypeConfigurationCollection<ClientAuthenticationModule> defaultClientAuthenticationModules =
+ new TypeConfigurationCollection<ClientAuthenticationModule>(new Type[] { typeof(ClientCredentialHttpBasicReader), typeof(ClientCredentialMessagePartReader) });
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerSection"/> class.
+ /// </summary>
+ internal OAuth2AuthorizationServerSection() {
+ }
+
+ /// <summary>
+ /// Gets the configuration section from the .config file.
+ /// </summary>
+ internal static OAuth2AuthorizationServerSection Configuration {
+ get {
+ Contract.Ensures(Contract.Result<OAuth2AuthorizationServerSection>() != null);
+ return (OAuth2AuthorizationServerSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2AuthorizationServerSection();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the services to use for discovering service endpoints for identifiers.
+ /// </summary>
+ /// <remarks>
+ /// If no discovery services are defined in the (web) application's .config file,
+ /// the default set of discovery services built into the library are used.
+ /// </remarks>
+ [ConfigurationProperty(ClientAuthenticationModulesElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<ClientAuthenticationModule>))]
+ internal TypeConfigurationCollection<ClientAuthenticationModule> ClientAuthenticationModules {
+ get {
+ var configResult = (TypeConfigurationCollection<ClientAuthenticationModule>)this[ClientAuthenticationModulesElementName];
+ return configResult != null && configResult.Count > 0 ? configResult : defaultClientAuthenticationModules;
+ }
+
+ set {
+ this[ClientAuthenticationModulesElementName] = value;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
index c1f3124..c6371c0 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
@@ -18,6 +18,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup>
+ <Compile Include="Configuration\OAuth2AuthorizationServerSection.cs" />
<Compile Include="OAuth2\AuthorizationServer.cs" />
<Compile Include="OAuth2\AuthServerStrings.Designer.cs">
<AutoGen>True</AutoGen>
@@ -25,6 +26,9 @@
<DependentUpon>AuthServerStrings.resx</DependentUpon>
</Compile>
<Compile Include="OAuth2\AuthServerUtilities.cs" />
+ <Compile Include="OAuth2\ChannelElements\AggregatingClientCredentialReader.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientCredentialHttpBasicReader.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientCredentialMessagePartReader.cs" />
<Compile Include="OAuth2\ChannelElements\TokenCodeSerializationBindingElement.cs" />
<Compile Include="OAuth2\ChannelElements\AuthorizationCode.cs" />
<Compile Include="OAuth2\ChannelElements\MessageValidationBindingElement.cs" />
@@ -32,6 +36,7 @@
<Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithAuthorizationServer.cs" />
<Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" />
<Compile Include="OAuth2\ChannelElements\RefreshToken.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientAuthenticationModule.cs" />
<Compile Include="OAuth2\ClientDescription.cs" />
<Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequestAS.cs" />
<Compile Include="OAuth2\Messages\AccessTokenRefreshRequestAS.cs" />
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs
index cd222e2..b8a1071 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs
@@ -12,6 +12,8 @@ namespace DotNetOpenAuth.OAuth2 {
using System.Linq;
using System.Text;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+ using DotNetOpenAuth.OAuth2.Messages;
/// <summary>
/// Utility methods for authorization servers.
@@ -42,13 +44,21 @@ namespace DotNetOpenAuth.OAuth2 {
/// Verifies a condition is true or throws an exception describing the problem.
/// </summary>
/// <param name="condition">The condition that evaluates to true to avoid an exception.</param>
+ /// <param name="requestMessage">The request message.</param>
/// <param name="error">A single error code from <see cref="Protocol.AccessTokenRequestErrorCodes"/>.</param>
+ /// <param name="authenticationModule">The authentication module from which to glean the WWW-Authenticate header when applicable.</param>
/// <param name="unformattedDescription">A human-readable UTF-8 encoded text providing additional information, used to assist the client developer in understanding the error that occurred.</param>
/// <param name="args">The formatting arguments to generate the actual description.</param>
- internal static void TokenEndpointVerify(bool condition, string error, string unformattedDescription = null, params object[] args) {
+ internal static void TokenEndpointVerify(bool condition, AccessTokenRequestBase requestMessage, string error, ClientAuthenticationModule authenticationModule = null, string unformattedDescription = null, params object[] args) {
if (!condition) {
string description = unformattedDescription != null ? string.Format(CultureInfo.CurrentCulture, unformattedDescription, args) : null;
- throw new TokenEndpointProtocolException(error, description);
+
+ string wwwAuthenticateHeader = null;
+ if (authenticationModule != null) {
+ wwwAuthenticateHeader = authenticationModule.AuthenticateHeader;
+ }
+
+ throw new TokenEndpointProtocolException(requestMessage, error, description, authenticateHeader: wwwAuthenticateHeader);
}
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
index 3809c3d..59b75bf 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
@@ -13,7 +13,7 @@ namespace DotNetOpenAuth.OAuth2 {
using System.Security.Cryptography;
using System.Text;
using System.Web;
-
+ using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
@@ -23,12 +23,24 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
public class AuthorizationServer {
/// <summary>
+ /// The list of modules that verify client authentication data.
+ /// </summary>
+ private readonly List<ClientAuthenticationModule> clientAuthenticationModules = new List<ClientAuthenticationModule>();
+
+ /// <summary>
+ /// The lone aggregate client authentication module that uses the <see cref="clientAuthenticationModules"/> and applies aggregating policy.
+ /// </summary>
+ private readonly ClientAuthenticationModule aggregatingClientAuthenticationModule;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="AuthorizationServer"/> class.
/// </summary>
/// <param name="authorizationServer">The authorization server.</param>
public AuthorizationServer(IAuthorizationServerHost authorizationServer) {
Requires.NotNull(authorizationServer, "authorizationServer");
- this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer);
+ this.aggregatingClientAuthenticationModule = new AggregatingClientCredentialReader(this.clientAuthenticationModules);
+ this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer, this.aggregatingClientAuthenticationModule);
+ this.clientAuthenticationModules.AddRange(OAuth2AuthorizationServerSection.Configuration.ClientAuthenticationModules.CreateInstances(true));
}
/// <summary>
@@ -46,6 +58,13 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Gets the extension modules that can read client authentication data from incoming messages.
+ /// </summary>
+ public IList<ClientAuthenticationModule> ClientAuthenticationModules {
+ get { return this.clientAuthenticationModules; }
+ }
+
+ /// <summary>
/// Reads in a client's request for the Authorization Server to obtain permission from
/// the user to authorize the Client's access of some protected resource(s).
/// </summary>
@@ -129,7 +148,7 @@ namespace DotNetOpenAuth.OAuth2 {
responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest, };
}
} catch (TokenEndpointProtocolException ex) {
- responseMessage = new AccessTokenFailedResponse() { Error = ex.Error, ErrorDescription = ex.Description, ErrorUri = ex.MoreInformation };
+ responseMessage = ex.GetResponse();
} catch (ProtocolException) {
responseMessage = new AccessTokenFailedResponse() {
Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest,
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs
new file mode 100644
index 0000000..ace95b3
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------
+// <copyright file="AggregatingClientCredentialReader.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Applies OAuth 2 spec policy for supporting multiple methods of client authentication.
+ /// </summary>
+ internal class AggregatingClientCredentialReader : ClientAuthenticationModule {
+ /// <summary>
+ /// The set of authenticators to apply to an incoming request.
+ /// </summary>
+ private readonly IEnumerable<ClientAuthenticationModule> authenticators;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AggregatingClientCredentialReader"/> class.
+ /// </summary>
+ /// <param name="authenticators">The set of authentication modules to apply.</param>
+ internal AggregatingClientCredentialReader(IEnumerable<ClientAuthenticationModule> authenticators) {
+ Requires.NotNull(authenticators, "readers");
+ this.authenticators = authenticators;
+ }
+
+ /// <summary>
+ /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports.
+ /// </summary>
+ public override string AuthenticateHeader {
+ get {
+ var builder = new StringBuilder();
+ foreach (var authenticator in this.authenticators) {
+ string scheme = authenticator.AuthenticateHeader;
+ if (scheme != null) {
+ if (builder.Length > 0) {
+ builder.Append(", ");
+ }
+
+ builder.Append(scheme);
+ }
+ }
+
+ return builder.Length > 0 ? builder.ToString() : null;
+ }
+ }
+
+ /// <summary>
+ /// Attempts to extract client identification/authentication information from a message.
+ /// </summary>
+ /// <param name="authorizationServerHost">The authorization server host.</param>
+ /// <param name="requestMessage">The incoming message.</param>
+ /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param>
+ /// <returns>The level of the extracted client information.</returns>
+ public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) {
+ Requires.NotNull(authorizationServerHost, "authorizationServerHost");
+ Requires.NotNull(requestMessage, "requestMessage");
+
+ ClientAuthenticationModule authenticator = null;
+ ClientAuthenticationResult result = ClientAuthenticationResult.NoAuthenticationRecognized;
+ clientIdentifier = null;
+
+ foreach (var candidateAuthenticator in this.authenticators) {
+ string candidateClientIdentifier;
+ var resultCandidate = candidateAuthenticator.TryAuthenticateClient(authorizationServerHost, requestMessage, out candidateClientIdentifier);
+
+ ErrorUtilities.VerifyProtocol(
+ result == ClientAuthenticationResult.NoAuthenticationRecognized || resultCandidate == ClientAuthenticationResult.NoAuthenticationRecognized,
+ "Message rejected because multiple forms of client authentication ({0} and {1}) were detected, which is forbidden by the OAuth 2 Protocol Framework specification.",
+ authenticator,
+ candidateAuthenticator);
+
+ if (resultCandidate != ClientAuthenticationResult.NoAuthenticationRecognized) {
+ authenticator = candidateAuthenticator;
+ result = resultCandidate;
+ clientIdentifier = candidateClientIdentifier;
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs
new file mode 100644
index 0000000..027929a
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClientAuthenticationModule.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// A base class for extensions that can read incoming messages and extract the client identifier and
+ /// possibly authentication information (like a shared secret, signed nonce, etc.)
+ /// </summary>
+ public abstract class ClientAuthenticationModule {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClientAuthenticationModule"/> class.
+ /// </summary>
+ protected ClientAuthenticationModule() {
+ }
+
+ /// <summary>
+ /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports.
+ /// </summary>
+ public virtual string AuthenticateHeader {
+ get { return null; }
+ }
+
+ /// <summary>
+ /// Attempts to extract client identification/authentication information from a message.
+ /// </summary>
+ /// <param name="authorizationServerHost">The authorization server host.</param>
+ /// <param name="requestMessage">The incoming message.</param>
+ /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param>
+ /// <returns>The level of the extracted client information.</returns>
+ public abstract ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier);
+
+ /// <summary>
+ /// Validates a client identifier and shared secret against the authoriation server's database.
+ /// </summary>
+ /// <param name="authorizationServerHost">The authorization server host; cannot be <c>null</c>.</param>
+ /// <param name="clientIdentifier">The alleged client identifier.</param>
+ /// <param name="clientSecret">The alleged client secret to be verified.</param>
+ /// <returns>An indication as to the outcome of the validation.</returns>
+ protected static ClientAuthenticationResult TryAuthenticateClientBySecret(IAuthorizationServerHost authorizationServerHost, string clientIdentifier, string clientSecret) {
+ Requires.NotNull(authorizationServerHost, "authorizationServerHost");
+
+ if (!string.IsNullOrEmpty(clientIdentifier)) {
+ var client = authorizationServerHost.GetClient(clientIdentifier);
+ if (client != null) {
+ if (!string.IsNullOrEmpty(clientSecret)) {
+ if (client.IsValidClientSecret(clientSecret)) {
+ return ClientAuthenticationResult.ClientAuthenticated;
+ } else { // invalid client secret
+ return ClientAuthenticationResult.ClientAuthenticationRejected;
+ }
+ } else { // no client secret provided
+ return ClientAuthenticationResult.ClientIdNotAuthenticated;
+ }
+ } else { // The client identifier is not recognized.
+ return ClientAuthenticationResult.ClientAuthenticationRejected;
+ }
+ } else { // no client id provided.
+ return ClientAuthenticationResult.NoAuthenticationRecognized;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs
new file mode 100644
index 0000000..655d38f
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClientCredentialHttpBasicReader.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Reads client authentication information from the HTTP Authorization header via Basic authentication.
+ /// </summary>
+ public class ClientCredentialHttpBasicReader : ClientAuthenticationModule {
+ /// <summary>
+ /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports.
+ /// </summary>
+ public override string AuthenticateHeader {
+ get { return "Basic"; }
+ }
+
+ /// <summary>
+ /// Attempts to extract client identification/authentication information from a message.
+ /// </summary>
+ /// <param name="authorizationServerHost">The authorization server host.</param>
+ /// <param name="requestMessage">The incoming message.</param>
+ /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param>
+ /// <returns>The level of the extracted client information.</returns>
+ public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) {
+ Requires.NotNull(authorizationServerHost, "authorizationServerHost");
+ Requires.NotNull(requestMessage, "requestMessage");
+
+ var credential = OAuthUtilities.ParseHttpBasicAuth(requestMessage.Headers);
+ if (credential != null) {
+ clientIdentifier = credential.UserName;
+ return TryAuthenticateClientBySecret(authorizationServerHost, credential.UserName, credential.Password);
+ }
+
+ clientIdentifier = null;
+ return ClientAuthenticationResult.NoAuthenticationRecognized;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs
new file mode 100644
index 0000000..2afd06e
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClientCredentialMessagePartReader.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Reads client authentication information from the message payload itself (POST entity as a URI-encoded parameter).
+ /// </summary>
+ public class ClientCredentialMessagePartReader : ClientAuthenticationModule {
+ /// <summary>
+ /// Attempts to extract client identification/authentication information from a message.
+ /// </summary>
+ /// <param name="authorizationServerHost">The authorization server host.</param>
+ /// <param name="requestMessage">The incoming message.</param>
+ /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param>
+ /// <returns>The level of the extracted client information.</returns>
+ public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) {
+ Requires.NotNull(authorizationServerHost, "authorizationServerHost");
+ Requires.NotNull(requestMessage, "requestMessage");
+
+ clientIdentifier = requestMessage.ClientIdentifier;
+ return TryAuthenticateClientBySecret(authorizationServerHost, requestMessage.ClientIdentifier, requestMessage.ClientSecret);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
index 7361fb9..ac23e24 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
@@ -24,6 +24,29 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// </remarks>
internal class MessageValidationBindingElement : AuthServerBindingElementBase {
/// <summary>
+ /// The aggregating client authentication module.
+ /// </summary>
+ private readonly ClientAuthenticationModule clientAuthenticationModule;
+
+ /// <summary>
+ /// The authorization server host that applies.
+ /// </summary>
+ private readonly IAuthorizationServerHost authorizationServer;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MessageValidationBindingElement"/> class.
+ /// </summary>
+ /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param>
+ /// <param name="authorizationServer">The authorization server host.</param>
+ internal MessageValidationBindingElement(ClientAuthenticationModule clientAuthenticationModule, IAuthorizationServerHost authorizationServer) {
+ Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule");
+ Requires.NotNull(authorizationServer, "authorizationServer");
+
+ this.clientAuthenticationModule = clientAuthenticationModule;
+ this.authorizationServer = authorizationServer;
+ }
+
+ /// <summary>
/// Gets the protection commonly offered (if any) by this binding element.
/// </summary>
/// <remarks>
@@ -79,10 +102,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
// Check that the client secret is correct for client authenticated messages.
var clientCredentialOnly = message as AccessTokenClientCredentialsRequest;
var authenticatedClientRequest = message as AuthenticatedClientRequestBase;
+ var accessTokenRequest = authenticatedClientRequest as AccessTokenRequestBase; // currently the only type of message.
if (authenticatedClientRequest != null) {
- var client = this.AuthorizationServer.GetClientOrThrow(authenticatedClientRequest.ClientIdentifier);
- AuthServerUtilities.TokenEndpointVerify(client.HasNonEmptySecret, Protocol.AccessTokenRequestErrorCodes.UnauthorizedClient); // an empty secret is not allowed for client authenticated calls.
- AuthServerUtilities.TokenEndpointVerify(client.IsValidClientSecret(authenticatedClientRequest.ClientSecret), Protocol.AccessTokenRequestErrorCodes.InvalidClient, AuthServerStrings.ClientSecretMismatch);
+ string clientIdentifier;
+ var result = this.clientAuthenticationModule.TryAuthenticateClient(this.authorizationServer, authenticatedClientRequest, out clientIdentifier);
+ AuthServerUtilities.TokenEndpointVerify(result != ClientAuthenticationResult.ClientIdNotAuthenticated, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnauthorizedClient); // an empty secret is not allowed for client authenticated calls.
+ AuthServerUtilities.TokenEndpointVerify(result == ClientAuthenticationResult.ClientAuthenticated, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch);
+ authenticatedClientRequest.ClientIdentifier = clientIdentifier;
if (clientCredentialOnly != null) {
clientCredentialOnly.CredentialsValidated = true;
@@ -104,12 +130,12 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
Logger.OAuth.ErrorFormat(
"Resource owner password credential for user \"{0}\" rejected by authorization server host.",
resourceOwnerPasswordCarrier.UserName);
- throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.InvalidGrant, AuthServerStrings.InvalidResourceOwnerPasswordCredential);
+ throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant, AuthServerStrings.InvalidResourceOwnerPasswordCredential);
}
} catch (NotSupportedException) {
- throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
+ throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
} catch (NotImplementedException) {
- throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
+ throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
}
}
@@ -135,14 +161,14 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
var accessRequest = authCarrier as AccessTokenRequestBase;
if (accessRequest != null) {
// Make sure the client sending us this token is the client we issued the token to.
- AuthServerUtilities.TokenEndpointVerify(string.Equals(accessRequest.ClientIdentifier, authCarrier.AuthorizationDescription.ClientIdentifier, StringComparison.Ordinal), Protocol.AccessTokenRequestErrorCodes.InvalidClient);
+ AuthServerUtilities.TokenEndpointVerify(string.Equals(accessRequest.ClientIdentifier, authCarrier.AuthorizationDescription.ClientIdentifier, StringComparison.Ordinal), accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient);
var scopedAccessRequest = accessRequest as ScopedAccessTokenRequest;
if (scopedAccessRequest != null) {
// Make sure the scope the client is requesting does not exceed the scope in the grant.
if (!scopedAccessRequest.Scope.IsSubsetOf(authCarrier.AuthorizationDescription.Scope)) {
Logger.OAuth.ErrorFormat("The requested access scope (\"{0}\") exceeds the grant scope (\"{1}\").", scopedAccessRequest.Scope, authCarrier.AuthorizationDescription.Scope);
- throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.InvalidScope, AuthServerStrings.AccessScopeExceedsGrantScope);
+ throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidScope, AuthServerStrings.AccessScopeExceedsGrantScope);
}
}
}
@@ -150,7 +176,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
// Make sure the authorization this token represents hasn't already been revoked.
if (!this.AuthorizationServer.IsAuthorizationValid(authCarrier.AuthorizationDescription)) {
Logger.OAuth.Error("Rejecting access token request because the IAuthorizationServerHost.IsAuthorizationValid method returned false.");
- throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.InvalidGrant);
+ throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant);
}
applied = true;
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs
index bd154bc..53dfb54 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs
@@ -35,8 +35,9 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// Initializes a new instance of the <see cref="OAuth2AuthorizationServerChannel"/> class.
/// </summary>
/// <param name="authorizationServer">The authorization server.</param>
- protected internal OAuth2AuthorizationServerChannel(IAuthorizationServerHost authorizationServer)
- : base(MessageTypes, InitializeBindingElements(authorizationServer)) {
+ /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param>
+ protected internal OAuth2AuthorizationServerChannel(IAuthorizationServerHost authorizationServer, ClientAuthenticationModule clientAuthenticationModule)
+ : base(MessageTypes, InitializeBindingElements(authorizationServer, clientAuthenticationModule)) {
Requires.NotNull(authorizationServer, "authorizationServer");
this.AuthorizationServer = authorizationServer;
}
@@ -106,15 +107,18 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// Initializes the binding elements for the OAuth channel.
/// </summary>
/// <param name="authorizationServer">The authorization server.</param>
+ /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param>
/// <returns>
/// An array of binding elements used to initialize the channel.
/// </returns>
- private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServerHost authorizationServer) {
+ private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServerHost authorizationServer, ClientAuthenticationModule clientAuthenticationModule) {
Requires.NotNull(authorizationServer, "authorizationServer");
+ Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule");
+
var bindingElements = new List<IChannelBindingElement>();
// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.
- bindingElements.Add(new MessageValidationBindingElement());
+ bindingElements.Add(new MessageValidationBindingElement(clientAuthenticationModule, authorizationServer));
bindingElements.Add(new TokenCodeSerializationBindingElement());
return bindingElements.ToArray();
diff --git a/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs b/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs
new file mode 100644
index 0000000..1ee5aa5
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth2ClientSection.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Represents the &lt;oauth2/client&gt; section in the host's .config file.
+ /// </summary>
+ internal class OAuth2ClientSection : ConfigurationSection {
+ /// <summary>
+ /// The name of the oauth2/client section.
+ /// </summary>
+ private const string SectionName = OAuth2SectionGroup.SectionName + "/client";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2ClientSection"/> class.
+ /// </summary>
+ internal OAuth2ClientSection() {
+ }
+
+ /// <summary>
+ /// Gets the configuration section from the .config file.
+ /// </summary>
+ internal static OAuth2ClientSection Configuration {
+ get {
+ Contract.Ensures(Contract.Result<OAuth2ClientSection>() != null);
+ return (OAuth2ClientSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2ClientSection();
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj
index 662bf86..e72ee1a 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj
+++ b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj
@@ -18,9 +18,12 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup>
+ <Compile Include="Configuration\OAuth2ClientSection.cs" />
<Compile Include="OAuth2\AuthorizationServerDescription.cs" />
<Compile Include="OAuth2\AuthorizationState.cs" />
+ <Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithClient.cs" />
<Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" />
+ <Compile Include="OAuth2\ClientCredentialApplicator.cs" />
<Compile Include="OAuth2\IAuthorizationState.cs" />
<Compile Include="OAuth2\IClientAuthorizationTracker.cs" />
<Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequestC.cs" />
@@ -60,4 +63,4 @@
<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>
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs
new file mode 100644
index 0000000..c802be6
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOAuth2ChannelWithClient.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// An interface that defines the OAuth2 client specific channel additions.
+ /// </summary>
+ internal interface IOAuth2ChannelWithClient {
+ /// <summary>
+ /// Gets or sets the identifier by which this client is known to the Authorization Server.
+ /// </summary>
+ string ClientIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the client credentials applicator extension to use.
+ /// </summary>
+ ClientCredentialApplicator ClientCredentialApplicator { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs
index 54fc110..8ad2ed9 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs
@@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// The messaging channel used by OAuth 2.0 Clients.
/// </summary>
- internal class OAuth2ClientChannel : OAuth2ChannelBase {
+ internal class OAuth2ClientChannel : OAuth2ChannelBase, IOAuth2ChannelWithClient {
/// <summary>
/// The messages receivable by this channel.
/// </summary>
@@ -34,10 +34,22 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// Initializes a new instance of the <see cref="OAuth2ClientChannel"/> class.
/// </summary>
- internal OAuth2ClientChannel() : base(MessageTypes) {
+ internal OAuth2ClientChannel()
+ : base(MessageTypes) {
}
/// <summary>
+ /// Gets or sets the identifier by which this client is known to the Authorization Server.
+ /// </summary>
+ public string ClientIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server.
+ /// </summary>
+ /// <value>May be <c>null</c> if this client has no client secret.</value>
+ public ClientCredentialApplicator ClientCredentialApplicator { get; set; }
+
+ /// <summary>
/// Prepares an HTTP request that carries a given message.
/// </summary>
/// <param name="request">The message to send.</param>
@@ -131,5 +143,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
// Clients don't ever send direct responses.
throw new NotImplementedException();
}
+
+ /// <summary>
+ /// Performs additional processing on an outgoing web request before it is sent to the remote server.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ protected override void PrepareHttpWebRequest(HttpWebRequest request) {
+ base.PrepareHttpWebRequest(request);
+
+ if (this.ClientCredentialApplicator != null) {
+ this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request);
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
index 77b9cc6..5f377ae 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
@@ -26,13 +26,16 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
/// <param name="authorizationServer">The token issuer.</param>
/// <param name="clientIdentifier">The client identifier.</param>
- /// <param name="clientSecret">The client secret.</param>
- protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) {
+ /// <param name="clientCredentialApplicator">
+ /// The tool to use to apply client credentials to authenticated requests to the Authorization Server.
+ /// May be <c>null</c> for clients with no secret or other means of authentication.
+ /// </param>
+ protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, ClientCredentialApplicator clientCredentialApplicator = null) {
Requires.NotNull(authorizationServer, "authorizationServer");
this.AuthorizationServer = authorizationServer;
this.Channel = new OAuth2ClientChannel();
this.ClientIdentifier = clientIdentifier;
- this.ClientSecret = clientSecret;
+ this.ClientCredentialApplicator = clientCredentialApplicator;
}
/// <summary>
@@ -50,12 +53,26 @@ namespace DotNetOpenAuth.OAuth2 {
/// <summary>
/// Gets or sets the identifier by which this client is known to the Authorization Server.
/// </summary>
- public string ClientIdentifier { get; set; }
+ public string ClientIdentifier {
+ get { return this.OAuthChannel.ClientIdentifier; }
+ set { this.OAuthChannel.ClientIdentifier = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server.
+ /// </summary>
+ /// <value>May be <c>null</c> if this client has no client secret.</value>
+ public ClientCredentialApplicator ClientCredentialApplicator {
+ get { return this.OAuthChannel.ClientCredentialApplicator; }
+ set { this.OAuthChannel.ClientCredentialApplicator = value; }
+ }
/// <summary>
- /// Gets or sets the client secret shared with the Authorization Server.
+ /// Gets the OAuth client channel.
/// </summary>
- public string ClientSecret { get; set; }
+ internal IOAuth2ChannelWithClient OAuthChannel {
+ get { return (IOAuth2ChannelWithClient)this.Channel; }
+ }
/// <summary>
/// Adds the necessary HTTP Authorization header to an HTTP request for protected resources
@@ -118,10 +135,11 @@ namespace DotNetOpenAuth.OAuth2 {
var request = new AccessTokenRefreshRequestC(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
- ClientSecret = this.ClientSecret,
RefreshToken = authorization.RefreshToken,
};
+ this.ApplyClientCredential(request);
+
var response = this.Channel.Request<AccessTokenSuccessResponse>(request);
UpdateAuthorizationWithResponse(authorization, response);
return true;
@@ -145,10 +163,11 @@ namespace DotNetOpenAuth.OAuth2 {
var request = new AccessTokenRefreshRequestC(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
- ClientSecret = this.ClientSecret,
RefreshToken = refreshToken,
};
+ this.ApplyClientCredential(request);
+
var response = this.Channel.Request<AccessTokenSuccessResponse>(request);
var authorization = new AuthorizationState();
UpdateAuthorizationWithResponse(authorization, response);
@@ -250,10 +269,10 @@ namespace DotNetOpenAuth.OAuth2 {
var accessTokenRequest = new AccessTokenAuthorizationCodeRequestC(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
- ClientSecret = this.ClientSecret,
Callback = authorizationState.Callback,
AuthorizationCode = authorizationSuccess.AuthorizationCode,
};
+ this.ApplyClientCredential(accessTokenRequest);
IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest);
var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse;
var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse;
@@ -267,6 +286,27 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Applies the default client authentication mechanism given a client secret.
+ /// </summary>
+ /// <param name="secret">The client secret. May be <c>null</c></param>
+ /// <returns>The client credential applicator.</returns>
+ protected static ClientCredentialApplicator DefaultSecretApplicator(string secret) {
+ return secret == null ? ClientCredentialApplicator.NoSecret() : ClientCredentialApplicator.NetworkCredential(secret);
+ }
+
+ /// <summary>
+ /// Applies any applicable client credential to an authenticated outbound request to the authorization server.
+ /// </summary>
+ /// <param name="request">The request to apply authentication information to.</param>
+ protected void ApplyClientCredential(AuthenticatedClientRequestBase request) {
+ Requires.NotNull(request, "request");
+
+ if (this.ClientCredentialApplicator != null) {
+ this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request);
+ }
+ }
+
+ /// <summary>
/// Calculates the fraction of life remaining in an access token.
/// </summary>
/// <param name="authorization">The authorization to measure.</param>
@@ -295,7 +335,7 @@ namespace DotNetOpenAuth.OAuth2 {
var authorizationState = new AuthorizationState(scopes);
request.ClientIdentifier = this.ClientIdentifier;
- request.ClientSecret = this.ClientSecret;
+ this.ApplyClientCredential(request);
request.Scope.UnionWith(authorizationState.Scope);
var response = this.Channel.Request(request);
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs
new file mode 100644
index 0000000..2a9a8a7
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs
@@ -0,0 +1,165 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClientCredentialApplicator.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2 {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// A base class for extensions that apply client authentication to messages for the authorization server in specific ways.
+ /// </summary>
+ public abstract class ClientCredentialApplicator {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClientCredentialApplicator"/> class.
+ /// </summary>
+ protected ClientCredentialApplicator() {
+ }
+
+ /// <summary>
+ /// Transmits the secret the client shares with the authorization server as a parameter in the POST entity payload.
+ /// </summary>
+ /// <param name="clientSecret">The secret the client shares with the authorization server.</param>
+ /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns>
+ public static ClientCredentialApplicator PostParameter(string clientSecret) {
+ Requires.NotNullOrEmpty(clientSecret, "clientSecret");
+ return new PostParameterApplicator(clientSecret);
+ }
+
+ /// <summary>
+ /// Transmits the client identifier and secret in the HTTP Authorization header via HTTP Basic authentication.
+ /// </summary>
+ /// <param name="credential">The client id and secret.</param>
+ /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns>
+ public static ClientCredentialApplicator NetworkCredential(NetworkCredential credential) {
+ Requires.NotNull(credential, "credential");
+ return new NetworkCredentialApplicator(credential);
+ }
+
+ /// <summary>
+ /// Transmits the client identifier and secret in the HTTP Authorization header via HTTP Basic authentication.
+ /// </summary>
+ /// <param name="clientSecret">The secret the client shares with the authorization server.</param>
+ /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns>
+ public static ClientCredentialApplicator NetworkCredential(string clientSecret) {
+ Requires.NotNullOrEmpty(clientSecret, "clientSecret");
+ return new NetworkCredentialApplicator(clientSecret);
+ }
+
+ /// <summary>
+ /// Never transmits a secret. Useful for anonymous clients or clients unable to keep a secret.
+ /// </summary>
+ /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns>
+ public static ClientCredentialApplicator NoSecret() {
+ return null;
+ }
+
+ /// <summary>
+ /// Applies the client identifier and (when applicable) the client authentication to an outbound message.
+ /// </summary>
+ /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param>
+ /// <param name="request">The outbound message to apply authentication information to.</param>
+ public virtual void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) {
+ }
+
+ /// <summary>
+ /// Applies the client identifier and (when applicable) the client authentication to an outbound message.
+ /// </summary>
+ /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param>
+ /// <param name="request">The outbound message to apply authentication information to.</param>
+ public virtual void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) {
+ }
+
+ /// <summary>
+ /// Authenticates the client via HTTP Basic.
+ /// </summary>
+ private class NetworkCredentialApplicator : ClientCredentialApplicator {
+ /// <summary>
+ /// The client identifier and secret.
+ /// </summary>
+ private readonly NetworkCredential credential;
+
+ /// <summary>
+ /// The client secret.
+ /// </summary>
+ private readonly string clientSecret;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NetworkCredentialApplicator"/> class.
+ /// </summary>
+ /// <param name="clientSecret">The client secret.</param>
+ internal NetworkCredentialApplicator(string clientSecret) {
+ Requires.NotNullOrEmpty(clientSecret, "clientSecret");
+ this.clientSecret = clientSecret;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NetworkCredentialApplicator"/> class.
+ /// </summary>
+ /// <param name="credential">The client credential.</param>
+ internal NetworkCredentialApplicator(NetworkCredential credential) {
+ Requires.NotNull(credential, "credential");
+ this.credential = credential;
+ }
+
+ /// <summary>
+ /// Applies the client identifier and (when applicable) the client authentication to an outbound message.
+ /// </summary>
+ /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param>
+ /// <param name="request">The outbound message to apply authentication information to.</param>
+ public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) {
+ // When using network credentials, the client authentication is not done as standard message parts.
+ request.ClientIdentifier = null;
+ request.ClientSecret = null;
+ }
+
+ /// <summary>
+ /// Applies the client identifier and (when applicable) the client authentication to an outbound message.
+ /// </summary>
+ /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param>
+ /// <param name="request">The outbound message to apply authentication information to.</param>
+ public override void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) {
+ if (this.credential != null && this.credential.UserName == clientIdentifier) {
+ ErrorUtilities.VerifyHost(false, "Client identifiers \"{0}\" and \"{1}\" do not match.", this.credential.UserName, clientIdentifier);
+ }
+
+ request.Credentials = this.credential ?? new NetworkCredential(clientIdentifier, this.clientSecret);
+ }
+ }
+
+ /// <summary>
+ /// Authenticates the client via a client_secret parameter in the message.
+ /// </summary>
+ private class PostParameterApplicator : ClientCredentialApplicator {
+ /// <summary>
+ /// The client secret.
+ /// </summary>
+ private readonly string secret;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PostParameterApplicator"/> class.
+ /// </summary>
+ /// <param name="clientSecret">The client secret.</param>
+ internal PostParameterApplicator(string clientSecret) {
+ Requires.NotNullOrEmpty(clientSecret, "clientSecret");
+ this.secret = clientSecret;
+ }
+
+ /// <summary>
+ /// Applies the client identifier and (when applicable) the client authentication to an outbound message.
+ /// </summary>
+ /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param>
+ /// <param name="request">The outbound message to apply authentication information to.</param>
+ public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) {
+ request.ClientSecret = this.secret;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs
index 9834985..edde2a9 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs
@@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// <param name="clientIdentifier">The client identifier.</param>
/// <param name="clientSecret">The client secret.</param>
public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null)
- : base(authorizationServer, clientIdentifier, clientSecret) {
+ : this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret)) {
}
/// <summary>
@@ -38,12 +38,39 @@ namespace DotNetOpenAuth.OAuth2 {
/// <param name="clientIdentifier">The client identifier.</param>
/// <param name="clientSecret">The client secret.</param>
public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier = null, string clientSecret = null)
- : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientSecret) {
+ : this(authorizationEndpoint, tokenEndpoint, clientIdentifier, DefaultSecretApplicator(clientSecret)) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserAgentClient"/> class.
+ /// </summary>
+ /// <param name="authorizationEndpoint">The authorization endpoint.</param>
+ /// <param name="tokenEndpoint">The token endpoint.</param>
+ /// <param name="clientIdentifier">The client identifier.</param>
+ /// <param name="clientCredentialApplicator">
+ /// The tool to use to apply client credentials to authenticated requests to the Authorization Server.
+ /// May be <c>null</c> for clients with no secret or other means of authentication.
+ /// </param>
+ public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator)
+ : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientCredentialApplicator) {
Requires.NotNull(authorizationEndpoint, "authorizationEndpoint");
Requires.NotNull(tokenEndpoint, "tokenEndpoint");
}
/// <summary>
+ /// Initializes a new instance of the <see cref="UserAgentClient"/> class.
+ /// </summary>
+ /// <param name="authorizationServer">The token issuer.</param>
+ /// <param name="clientIdentifier">The client identifier.</param>
+ /// <param name="clientCredentialApplicator">
+ /// The tool to use to apply client credentials to authenticated requests to the Authorization Server.
+ /// May be <c>null</c> for clients with no secret or other means of authentication.
+ /// </param>
+ public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator)
+ : base(authorizationServer, clientIdentifier, clientCredentialApplicator) {
+ }
+
+ /// <summary>
/// Generates a URL that the user's browser can be directed to in order to authorize
/// this client to access protected data at some resource server.
/// </summary>
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs
index 5a86bef..c19757f 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs
@@ -26,7 +26,20 @@ namespace DotNetOpenAuth.OAuth2 {
/// <param name="clientIdentifier">The client identifier.</param>
/// <param name="clientSecret">The client secret.</param>
public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null)
- : base(authorizationServer, clientIdentifier, clientSecret) {
+ : this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret)) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WebServerClient"/> class.
+ /// </summary>
+ /// <param name="authorizationServer">The authorization server.</param>
+ /// <param name="clientIdentifier">The client identifier.</param>
+ /// <param name="clientCredentialApplicator">
+ /// The tool to use to apply client credentials to authenticated requests to the Authorization Server.
+ /// May be <c>null</c> for clients with no secret or other means of authentication.
+ /// </param>
+ public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator)
+ : base(authorizationServer, clientIdentifier, clientCredentialApplicator) {
}
/// <summary>
@@ -106,7 +119,7 @@ namespace DotNetOpenAuth.OAuth2 {
/// <returns>The authorization state that contains the details of the authorization.</returns>
public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null) {
Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), ClientStrings.RequiredPropertyNotYetPreset, "ClientIdentifier");
- Requires.ValidState(!string.IsNullOrEmpty(this.ClientSecret), ClientStrings.RequiredPropertyNotYetPreset, "ClientSecret");
+ Requires.ValidState(this.ClientCredentialApplicator != null, ClientStrings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator");
if (request == null) {
request = this.Channel.GetRequestFromContext();
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs
index 8c4b1c3..4aaf928 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs
@@ -25,6 +25,11 @@ namespace DotNetOpenAuth.OAuth2.Messages {
private readonly bool invalidClientCredentialsInAuthorizationHeader;
/// <summary>
+ /// The headers to include in the response.
+ /// </summary>
+ private readonly WebHeaderCollection headers = new WebHeaderCollection();
+
+ /// <summary>
/// Initializes a new instance of the <see cref="AccessTokenFailedResponse"/> class.
/// </summary>
/// <param name="request">The faulty request.</param>
@@ -63,8 +68,8 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// Gets the HTTP headers to add to the response.
/// </summary>
/// <value>May be an empty collection, but must not be <c>null</c>.</value>
- WebHeaderCollection IHttpDirectResponse.Headers {
- get { return new WebHeaderCollection(); }
+ public WebHeaderCollection Headers {
+ get { return this.headers; }
}
#endregion
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs
index bc4d0ca..4631d83 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs
@@ -6,13 +6,18 @@
namespace DotNetOpenAuth.OAuth2.Messages {
using System;
-
+ using System.Net;
using DotNetOpenAuth.Messaging;
/// <summary>
/// A direct message from the client to the authorization server that includes the client's credentials.
/// </summary>
- public abstract class AuthenticatedClientRequestBase : MessageBase {
+ public abstract class AuthenticatedClientRequestBase : MessageBase, IHttpDirectRequest {
+ /// <summary>
+ /// The backing for the <see cref="Headers"/> property.
+ /// </summary>
+ private readonly WebHeaderCollection headers = new WebHeaderCollection();
+
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticatedClientRequestBase"/> class.
/// </summary>
@@ -26,7 +31,10 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// Gets the client identifier previously obtained from the Authorization Server.
/// </summary>
/// <value>The client identifier.</value>
- [MessagePart(Protocol.client_id, IsRequired = true)]
+ /// <remarks>
+ /// Not required, because the client id may be communicate through alternate means like HTTP Basic authentication (the OAuth 2 spec allows a lot of freedom here).
+ /// </remarks>
+ [MessagePart(Protocol.client_id, IsRequired = false)]
public string ClientIdentifier { get; internal set; }
/// <summary>
@@ -38,5 +46,13 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// </remarks>
[MessagePart(Protocol.client_secret, IsRequired = false)]
public string ClientSecret { get; internal set; }
+
+ /// <summary>
+ /// Gets the HTTP headers of the request.
+ /// </summary>
+ /// <value>May be an empty collection, but must not be <c>null</c>.</value>
+ public WebHeaderCollection Headers {
+ get { return this.headers; }
+ }
}
} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs
index 308bfe2..e86c27e 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs
@@ -12,24 +12,40 @@ namespace DotNetOpenAuth.OAuth2 {
using System.Text;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.Messages;
/// <summary>
/// Describes an error generated by an Authorization Server's token endpoint.
/// </summary>
public class TokenEndpointProtocolException : ProtocolException {
/// <summary>
+ /// The message being processed that caused this exception to be thrown.
+ /// </summary>
+ private readonly AccessTokenRequestBase requestMessage;
+
+ /// <summary>
+ /// The WWW-Authenticate header to add to the response message.
+ /// </summary>
+ private readonly string authenticateHeader;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="TokenEndpointProtocolException"/> class.
/// </summary>
+ /// <param name="requestMessage">The message whose processing resulted in this error.</param>
/// <param name="error">A single error code from <see cref="Protocol.AccessTokenRequestErrorCodes"/>.</param>
/// <param name="description">A human-readable UTF-8 encoded text providing additional information, used to assist the client developer in understanding the error that occurred.</param>
/// <param name="moreInformation">A URI identifying a human-readable web page with information about the error, used to provide the client developer with additional information about the error.</param>
- public TokenEndpointProtocolException(string error, string description = null, Uri moreInformation = null)
+ /// <param name="authenticateHeader">The WWW-Authenticate header to add to the response.</param>
+ public TokenEndpointProtocolException(AccessTokenRequestBase requestMessage, string error, string description = null, Uri moreInformation = null, string authenticateHeader = null)
: base(string.Format(CultureInfo.CurrentCulture, ClientAuthorizationStrings.TokenEndpointErrorFormat, error, description)) {
+ Requires.NotNull(requestMessage, "requestMessage");
Requires.NotNullOrEmpty(error, "error");
+ this.requestMessage = requestMessage;
this.Error = error;
this.Description = description;
this.MoreInformation = moreInformation;
+ this.authenticateHeader = authenticateHeader;
}
/// <summary>
@@ -55,5 +71,23 @@ namespace DotNetOpenAuth.OAuth2 {
/// Gets a URI identifying a human-readable web page with information about the error, used to provide the client developer with additional information about the error.
/// </summary>
public Uri MoreInformation { get; private set; }
+
+ /// <summary>
+ /// Gets the response message to send to the client.
+ /// </summary>
+ /// <returns>A message.</returns>
+ public IDirectResponseProtocolMessage GetResponse() {
+ var response = this.requestMessage != null
+ ? new AccessTokenFailedResponse(this.requestMessage, this.authenticateHeader != null)
+ : new AccessTokenFailedResponse();
+ response.Error = this.Error;
+ response.ErrorDescription = this.Description;
+ response.ErrorUri = this.MoreInformation;
+ if (this.authenticateHeader != null) {
+ response.Headers.Add(HttpRequestHeaders.WwwAuthenticate, this.authenticateHeader);
+ }
+
+ return response;
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs
new file mode 100644
index 0000000..3e37018
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth2ResourceServerSection.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Represents the &lt;oauth2/resourceServer&gt; section in the host's .config file.
+ /// </summary>
+ internal class OAuth2ResourceServerSection : ConfigurationElement {
+ /// <summary>
+ /// The name of the oauth2/client section.
+ /// </summary>
+ private const string SectionName = OAuth2SectionGroup.SectionName + "/resourceServer";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2ResourceServerSection"/> class.
+ /// </summary>
+ internal OAuth2ResourceServerSection() {
+ }
+
+ /// <summary>
+ /// Gets the configuration section from the .config file.
+ /// </summary>
+ internal static OAuth2ResourceServerSection Configuration {
+ get {
+ Contract.Ensures(Contract.Result<OAuth2ResourceServerSection>() != null);
+ return (OAuth2ResourceServerSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2ResourceServerSection();
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj
index 3aa92f7..eb54fee 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj
@@ -18,6 +18,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup>
+ <Compile Include="Configuration\OAuth2ResourceServerSection.cs" />
<Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" />
<Compile Include="OAuth2\IAccessTokenAnalyzer.cs" />
<Compile Include="OAuth2\ResourceServerStrings.Designer.cs">
@@ -52,4 +53,4 @@
<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>
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs
new file mode 100644
index 0000000..112e756
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth2SectionGroup.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Represents the &lt;oauth&gt; element in the host's .config file.
+ /// </summary>
+ internal class OAuth2SectionGroup : ConfigurationSectionGroup {
+ /// <summary>
+ /// The name of the oauth section.
+ /// </summary>
+ internal const string SectionName = DotNetOpenAuthSection.SectionName + "/oauth2";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2SectionGroup"/> class.
+ /// </summary>
+ internal OAuth2SectionGroup() {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj
index 921cd84..b359508 100644
--- a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj
+++ b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj
@@ -18,9 +18,11 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup>
+ <Compile Include="Configuration\OAuth2SectionGroup.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="OAuth2\AccessToken.cs" />
<Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" />
+ <Compile Include="OAuth2\ChannelElements\ClientAuthenticationResult.cs" />
<Compile Include="OAuth2\ChannelElements\IAccessTokenCarryingRequest.cs" />
<Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" />
<Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" />
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs
new file mode 100644
index 0000000..b0f86a9
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClientAuthenticationResult.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ /// <summary>
+ /// Describes the various levels at which client information may be extracted from an inbound message.
+ /// </summary>
+ public enum ClientAuthenticationResult {
+ /// <summary>
+ /// No client identification or authentication was discovered.
+ /// </summary>
+ NoAuthenticationRecognized,
+
+ /// <summary>
+ /// The client identified itself, but did not attempt to authenticate itself.
+ /// </summary>
+ ClientIdNotAuthenticated,
+
+ /// <summary>
+ /// The client authenticated itself (provided compelling evidence that it was who it claims to be).
+ /// </summary>
+ ClientAuthenticated,
+
+ /// <summary>
+ /// The client failed in an attempt to authenticate itself, claimed to be an unrecognized client, or otherwise messed up.
+ /// </summary>
+ ClientAuthenticationRejected,
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
index eb5c8e4..661d102 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
@@ -24,9 +24,24 @@ namespace DotNetOpenAuth.OAuth2 {
public static readonly StringComparer ScopeStringComparer = StringComparer.Ordinal;
/// <summary>
+ /// The string "Basic ".
+ /// </summary>
+ private const string HttpBasicAuthScheme = "Basic ";
+
+ /// <summary>
/// The delimiter between scope elements.
/// </summary>
- private static char[] scopeDelimiter = new char[] { ' ' };
+ private static readonly char[] scopeDelimiter = new char[] { ' ' };
+
+ /// <summary>
+ /// A colon, in a 1-length character array.
+ /// </summary>
+ private static readonly char[] ColonSeparator = new char[] { ':' };
+
+ /// <summary>
+ /// The encoding to use when preparing credentials for transit in HTTP Basic base64 encoding form.
+ /// </summary>
+ private static readonly Encoding HttpBasicEncoding = Encoding.UTF8;
/// <summary>
/// The characters that may appear in an access token that is included in an HTTP Authorization header.
@@ -35,9 +50,9 @@ namespace DotNetOpenAuth.OAuth2 {
/// This is defined in OAuth 2.0 DRAFT 10, section 5.1.1. (http://tools.ietf.org/id/draft-ietf-oauth-v2-10.html#authz-header)
/// </remarks>
private static string accessTokenAuthorizationHeaderAllowedCharacters = MessagingUtilities.UppercaseLetters +
- MessagingUtilities.LowercaseLetters +
- MessagingUtilities.Digits +
- @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;";
+ MessagingUtilities.LowercaseLetters +
+ MessagingUtilities.Digits +
+ @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;";
/// <summary>
/// Determines whether one given scope is a subset of another scope.
@@ -129,5 +144,45 @@ namespace DotNetOpenAuth.OAuth2 {
Protocol.BearerHttpAuthorizationHeaderFormat,
accessToken);
}
+
+ /// <summary>
+ /// Applies the HTTP Authorization header for HTTP Basic authentication.
+ /// </summary>
+ /// <param name="headers">The headers collection to set the authorization header to.</param>
+ /// <param name="userName">The username. Cannot be empty.</param>
+ /// <param name="password">The password. Cannot be null.</param>
+ internal static void ApplyHttpBasicAuth(WebHeaderCollection headers, string userName, string password) {
+ Requires.NotNull(headers, "headers");
+ Requires.NotNullOrEmpty(userName, "userName");
+ Requires.NotNull(password, "password");
+
+ string concat = userName + ":" + password;
+ byte[] bits = HttpBasicEncoding.GetBytes(concat);
+ string base64 = Convert.ToBase64String(bits);
+ string header = HttpBasicAuthScheme + base64;
+ headers[HttpRequestHeader.Authorization] = header;
+ }
+
+ /// <summary>
+ /// Extracts the username and password from an HTTP Basic authorized web header.
+ /// </summary>
+ /// <param name="headers">The incoming web headers.</param>
+ /// <returns>The network credentials; or <c>null</c> if none could be discovered in the request.</returns>
+ internal static NetworkCredential ParseHttpBasicAuth(WebHeaderCollection headers) {
+ Requires.NotNull(headers, "headers");
+
+ string authorizationHeader = headers[HttpRequestHeaders.Authorization];
+ if (authorizationHeader != null && authorizationHeader.StartsWith(HttpBasicAuthScheme, StringComparison.Ordinal)) {
+ string base64 = authorizationHeader.Substring(HttpBasicAuthScheme.Length);
+ byte[] bits = Convert.FromBase64String(base64);
+ string usernameColonPassword = HttpBasicEncoding.GetString(bits);
+ string[] usernameAndPassword = usernameColonPassword.Split(ColonSeparator, 2);
+ if (usernameAndPassword.Length == 2) {
+ return new NetworkCredential(usernameAndPassword[0], usernameAndPassword[1]);
+ }
+ }
+
+ return null;
+ }
}
}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs
index 749659e..7d8c050 100644
--- a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs
+++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs
@@ -49,7 +49,8 @@ namespace DotNetOpenAuth.Configuration {
/// <summary>
/// The built-in set of identifier discovery services.
/// </summary>
- private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices = new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) });
+ private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices =
+ new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) });
/// <summary>
/// Initializes a new instance of the <see cref="OpenIdRelyingPartyElement"/> class.
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
index a3edcf6..dae4a65 100644
--- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
+++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
@@ -219,6 +219,7 @@
<Compile Include="Mocks\CoordinatingChannel.cs" />
<Compile Include="Mocks\CoordinatingHttpRequestInfo.cs" />
<Compile Include="Mocks\CoordinatingOAuth2AuthServerChannel.cs" />
+ <Compile Include="Mocks\CoordinatingOAuth2ClientChannel.cs" />
<Compile Include="Mocks\CoordinatingOutgoingWebResponse.cs" />
<Compile Include="Mocks\CoordinatingOAuthConsumerChannel.cs" />
<Compile Include="Mocks\IBaseMessageExplicitMembers.cs" />
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
index d7205d6..2e09943 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
@@ -9,10 +9,10 @@ namespace DotNetOpenAuth.Test.Mocks {
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Net;
using System.Text;
using System.Threading;
using System.Web;
-
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Reflection;
using DotNetOpenAuth.Test.OpenId;
@@ -65,9 +65,17 @@ namespace DotNetOpenAuth.Test.Mocks {
/// </summary>
private IDictionary<string, string> incomingMessage;
+ /// <summary>
+ /// The recipient URL of the <see cref="incomingMessage"/>, where applicable.
+ /// </summary>
private MessageReceivingEndpoint incomingMessageRecipient;
/// <summary>
+ /// The headers of the <see cref="incomingMessage"/>, where applicable.
+ /// </summary>
+ private WebHeaderCollection incomingMessageHttpHeaders;
+
+ /// <summary>
/// A delegate that gets a chance to peak at and fiddle with all
/// incoming messages.
/// </summary>
@@ -145,17 +153,27 @@ namespace DotNetOpenAuth.Test.Mocks {
this.incomingMessage = this.MessageDescriptions.GetAccessor(message).Serialize();
var directedMessage = message as IDirectedProtocolMessage;
this.incomingMessageRecipient = (directedMessage != null && directedMessage.Recipient != null) ? new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods) : null;
+ var httpMessage = message as IHttpDirectRequest;
+ this.incomingMessageHttpHeaders = (httpMessage != null) ? httpMessage.Headers.Clone() : null;
this.incomingMessageSignal.Set();
}
protected internal override HttpRequestBase GetRequestFromContext() {
MessageReceivingEndpoint recipient;
- var messageData = this.AwaitIncomingMessage(out recipient);
+ WebHeaderCollection headers;
+ var messageData = this.AwaitIncomingMessage(out recipient, out headers);
+ CoordinatingHttpRequestInfo result;
if (messageData != null) {
- return new CoordinatingHttpRequestInfo(this, this.MessageFactory, messageData, recipient);
+ result = new CoordinatingHttpRequestInfo(this, this.MessageFactory, messageData, recipient);
} else {
- return new CoordinatingHttpRequestInfo(recipient);
+ result = new CoordinatingHttpRequestInfo(recipient);
+ }
+
+ if (headers != null) {
+ headers.ApplyTo(result.Headers);
}
+
+ return result;
}
protected override IProtocolMessage RequestCore(IDirectedProtocolMessage request) {
@@ -166,7 +184,8 @@ namespace DotNetOpenAuth.Test.Mocks {
// Now wait for a response...
MessageReceivingEndpoint recipient;
- IDictionary<string, string> responseData = this.AwaitIncomingMessage(out recipient);
+ WebHeaderCollection headers;
+ IDictionary<string, string> responseData = this.AwaitIncomingMessage(out recipient, out headers);
ErrorUtilities.VerifyInternal(recipient == null, "The recipient is expected to be null for direct responses.");
// And deserialize it.
@@ -177,6 +196,10 @@ namespace DotNetOpenAuth.Test.Mocks {
var responseAccessor = this.MessageDescriptions.GetAccessor(responseMessage);
responseAccessor.Deserialize(responseData);
+ var responseMessageHttpRequest = responseMessage as IHttpDirectRequest;
+ if (headers != null && responseMessageHttpRequest != null) {
+ headers.ApplyTo(responseMessageHttpRequest.Headers);
+ }
this.ProcessMessageFilter(responseMessage, false);
return responseMessage;
@@ -258,7 +281,7 @@ namespace DotNetOpenAuth.Test.Mocks {
return channel.MessageFactoryTestHook;
}
- private IDictionary<string, string> AwaitIncomingMessage(out MessageReceivingEndpoint recipient) {
+ private IDictionary<string, string> AwaitIncomingMessage(out MessageReceivingEndpoint recipient, out WebHeaderCollection headers) {
// Special care should be taken so that we don't indefinitely
// wait for a message that may never come due to a bug in the product
// or the test.
@@ -284,8 +307,10 @@ namespace DotNetOpenAuth.Test.Mocks {
this.waitingForMessage = false;
var response = this.incomingMessage;
recipient = this.incomingMessageRecipient;
+ headers = this.incomingMessageHttpHeaders;
this.incomingMessage = null;
this.incomingMessageRecipient = null;
+ this.incomingMessageHttpHeaders = null;
// Briefly signal to another thread that might be waiting for our inbox to be empty
this.messageReceivedSignal.Set();
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs
index 9f139f3..a1f5cf5 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs
@@ -6,9 +6,10 @@
namespace DotNetOpenAuth.Test.Mocks {
using System;
- using System.Collections.Generic;
- using System.Diagnostics.Contracts;
- using DotNetOpenAuth.Messaging;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Net;
+using DotNetOpenAuth.Messaging;
internal class CoordinatingHttpRequestInfo : HttpRequestInfo {
private readonly Channel channel;
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs
new file mode 100644
index 0000000..52f381d
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="CoordinatingOAuth2ClientChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+
+ internal class CoordinatingOAuth2ClientChannel : CoordinatingChannel, IOAuth2ChannelWithClient {
+ private OAuth2ClientChannel wrappedChannel;
+
+ internal CoordinatingOAuth2ClientChannel(Channel wrappedChannel, Action<IProtocolMessage> incomingMessageFilter, Action<IProtocolMessage> outgoingMessageFilter)
+ : base(wrappedChannel, incomingMessageFilter, outgoingMessageFilter) {
+ this.wrappedChannel = (OAuth2ClientChannel)wrappedChannel;
+ }
+
+ public string ClientIdentifier {
+ get { return this.wrappedChannel.ClientIdentifier; }
+ set { this.wrappedChannel.ClientIdentifier = value; }
+ }
+
+ public DotNetOpenAuth.OAuth2.ClientCredentialApplicator ClientCredentialApplicator {
+ get { return this.wrappedChannel.ClientCredentialApplicator; }
+ set { this.wrappedChannel.ClientCredentialApplicator = value; }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs b/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs
index 89271ae..52b5371 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs
@@ -28,7 +28,7 @@ namespace DotNetOpenAuth.Test.OAuth2 {
public override void SetUp() {
base.SetUp();
- var authServerChannel = new OAuth2AuthorizationServerChannel(new Mock<IAuthorizationServerHost>().Object);
+ var authServerChannel = new OAuth2AuthorizationServerChannel(new Mock<IAuthorizationServerHost>().Object, new Mock<ClientAuthenticationModule>().Object);
this.authServerMessageFactory = authServerChannel.MessageFactoryTestHook;
var clientChannel = new OAuth2ClientChannel();
diff --git a/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs b/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs
index ede3258..6494585 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs
@@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.OAuth2 {
using System;
using System.Collections.Generic;
using System.Linq;
+ using System.Net;
using System.Text;
using DotNetOpenAuth.OAuth2;
using DotNetOpenAuth.Test.Mocks;
@@ -34,13 +35,13 @@ namespace DotNetOpenAuth.Test.OAuth2 {
this.client = client;
this.client.ClientIdentifier = OAuth2TestBase.ClientId;
- this.client.ClientSecret = OAuth2TestBase.ClientSecret;
+ this.client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(OAuth2TestBase.ClientSecret);
}
internal override void Run() {
var authServer = new AuthorizationServer(this.authServerHost);
- var rpCoordinatingChannel = new CoordinatingChannel(this.client.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
+ var rpCoordinatingChannel = new CoordinatingOAuth2ClientChannel(this.client.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
var opCoordinatingChannel = new CoordinatingOAuth2AuthServerChannel(authServer.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
rpCoordinatingChannel.RemoteChannel = opCoordinatingChannel;
opCoordinatingChannel.RemoteChannel = rpCoordinatingChannel;
diff --git a/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs b/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs
index 97c0f56..ae03b0c 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs
@@ -73,7 +73,7 @@ namespace DotNetOpenAuth.Test.OAuth2 {
server.ApproveAuthorizationRequest(request, ResourceOwnerUsername);
});
- coordinatorClient.ClientSecret = null; // implicit grant clients don't need a secret.
+ coordinatorClient.ClientCredentialApplicator = null; // implicit grant clients don't need a secret.
coordinator.Run();
}
}
diff --git a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
index fe0abd2..6f46271 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
@@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.OAuth2 {
using System;
using System.Collections.Generic;
using System.Linq;
+ using System.Net;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
diff --git a/src/DotNetOpenAuth.Test/TestUtilities.cs b/src/DotNetOpenAuth.Test/TestUtilities.cs
index cf9b5a3..a526f7f 100644
--- a/src/DotNetOpenAuth.Test/TestUtilities.cs
+++ b/src/DotNetOpenAuth.Test/TestUtilities.cs
@@ -7,16 +7,35 @@
namespace DotNetOpenAuth.Test {
using System;
using System.Collections.Generic;
+ using System.Collections.Specialized;
using System.Linq;
+ using System.Net;
using log4net;
/// <summary>
/// An assortment of methods useful for testing.
/// </summary>
- internal class TestUtilities {
+ internal static class TestUtilities {
/// <summary>
/// The logger that tests should use.
/// </summary>
internal static readonly ILog TestLogger = LogManager.GetLogger("DotNetOpenAuth.Test");
+
+ internal static void ApplyTo(this NameValueCollection source, NameValueCollection target) {
+ Requires.NotNull(source, "source");
+ Requires.NotNull(target, "target");
+
+ foreach (string header in source) {
+ target[header] = source[header];
+ }
+ }
+
+ internal static T Clone<T>(this T source) where T : NameValueCollection, new() {
+ Requires.NotNull(source, "source");
+
+ var result = new T();
+ ApplyTo(source, result);
+ return result;
+ }
}
}