diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2012-04-18 19:55:50 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2012-04-18 19:55:50 -0700 |
commit | 2ddd19d9f037bebbbdc80d7de35ce4d899710859 (patch) | |
tree | baf8ad19d4799b1a1284b9fc668afd53e3b008a4 | |
parent | bd0de8217763d02759815b91588cd578becf496b (diff) | |
download | DotNetOpenAuth-2ddd19d9f037bebbbdc80d7de35ce4d899710859.zip DotNetOpenAuth-2ddd19d9f037bebbbdc80d7de35ce4d899710859.tar.gz DotNetOpenAuth-2ddd19d9f037bebbbdc80d7de35ce4d899710859.tar.bz2 |
We have HTTP Basic client authentication working now in OAuth 2.
49 files changed, 950 insertions, 76 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.Core/web.config.transform b/nuget/content/OAuth2.Core/web.config.transform new file mode 100644 index 0000000..cbb42e1 --- /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"> + <section name="oauth2" type="DotNetOpenAuth.Configuration.OAuth2Element, DotNetOpenAuth.OAuth2" requirePermission="false" allowLocation="true" /> + </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..e98d63b 100644 --- a/samples/OAuthAuthorizationServer/Web.config +++ b/samples/OAuthAuthorizationServer/Web.config @@ -38,6 +38,12 @@ <dotNetOpenAuth> <!-- Allow DotNetOpenAuth to publish usage statistics to library authors to improve the library. --> <reporting enabled="true" /> + <oauth2> + <authorizationServer> + <clientAuthenticationModules> + </clientAuthenticationModules> + </authorizationServer> + </oauth2> <!-- Relaxing SSL requirements is useful for simple samples, but NOT a good idea in production. --> <messaging relaxSslRequirements="true"> diff --git a/samples/OAuthClient/Facebook.aspx.cs b/samples/OAuthClient/Facebook.aspx.cs index 0f71712..6202651 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.SecretParameter(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..e5eb5d6 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.SecretParameter(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..3caadde 100644 --- a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd @@ -898,6 +898,71 @@ </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="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </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..fbcb3d6 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; 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/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..c17a4c2 --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <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 + + Version IMessage.Version { + get { throw new NotImplementedException(); } + } + + IDictionary<string, string> IMessage.ExtraData { + get { throw new NotImplementedException(); } + } + + void IMessage.EnsureValidMessage() { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Core/Requires.cs b/src/DotNetOpenAuth.Core/Requires.cs index 7a196a3..5cd8b6a 100644 --- a/src/DotNetOpenAuth.Core/Requires.cs +++ b/src/DotNetOpenAuth.Core/Requires.cs @@ -47,10 +47,12 @@ namespace DotNetOpenAuth { [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.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj index c1f3124..ad21f21 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj @@ -25,6 +25,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 +35,7 @@ <Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithAuthorizationServer.cs" /> <Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" /> <Compile Include="OAuth2\ChannelElements\RefreshToken.cs" /> + <Compile Include="OAuth2\ChannelElements\ClientCredentialReader.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/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs index 3809c3d..dc8245d 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,34 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> public class AuthorizationServer { /// <summary> + /// The built-in set of client authentication modules. + /// </summary> + private static readonly TypeConfigurationCollection<IClientAuthenticationModule> defaultClientAuthenticationModules = + new TypeConfigurationCollection<IClientAuthenticationModule>(new Type[] { typeof(ClientCredentialHttpBasicReader), typeof(ClientCredentialMessagePartReader) }); + + private readonly List<IClientAuthenticationModule> clientAuthenticationModules = new List<IClientAuthenticationModule>(); + + private readonly ClientAuthenticationModuleBase 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); + + var modules = OAuth2Element.Configuration.AuthorizationServer.ClientAuthenticationModules; + if (modules.Count == 0) { + modules = defaultClientAuthenticationModules; + } + + // TODO: work this out once we move configurations into the oauth2 authorization server. + ////this.clientAuthenticationModules.AddRange(modules.CreateInstances(true)); + this.clientAuthenticationModules.Add(new ClientCredentialMessagePartReader(authorizationServer)); + this.clientAuthenticationModules.Add(new ClientCredentialHttpBasicReader(authorizationServer)); + } /// <summary> @@ -45,6 +67,10 @@ namespace DotNetOpenAuth.OAuth2 { get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; } } + public IList<IClientAuthenticationModule> 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). 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..4248c6f --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <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 : ClientAuthenticationModuleBase { + /// <summary> + /// The set of authenticators to apply to an incoming request. + /// </summary> + private readonly IEnumerable<IClientAuthenticationModule> 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<IClientAuthenticationModule> authenticators) { + Requires.NotNull(authenticators, "readers"); + this.authenticators = authenticators; + } + + public override ClientAuthenticationResult TryAuthenticateClient(AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(requestMessage, "requestMessage"); + + IClientAuthenticationModule authenticator = null; + ClientAuthenticationResult result = ClientAuthenticationResult.NoAuthenticationRecognized; + clientIdentifier = null; + + foreach (var candidateAuthenticator in this.authenticators) { + string candidateClientIdentifier; + var resultCandidate = candidateAuthenticator.TryAuthenticateClient(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/ClientCredentialHttpBasicReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs new file mode 100644 index 0000000..da3f8ff --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <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; + + public class ClientCredentialHttpBasicReader : ClientAuthenticationModuleBase { + private readonly IAuthorizationServerHost authorizationServerHost; + + public ClientCredentialHttpBasicReader(IAuthorizationServerHost authorizationServerHost) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + this.authorizationServerHost = authorizationServerHost; + } + + public override ClientAuthenticationResult TryAuthenticateClient(AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(requestMessage, "requestMessage"); + + var credential = OAuthUtilities.ParseHttpBasicAuth(requestMessage.Headers); + if (credential != null) { + clientIdentifier = credential.UserName; + return TryAuthenticateClient(this.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..07ededf --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// <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; + + public class ClientCredentialMessagePartReader : ClientAuthenticationModuleBase { + private readonly IAuthorizationServerHost authorizationServerHost; + + public ClientCredentialMessagePartReader(IAuthorizationServerHost authorizationServerHost) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + this.authorizationServerHost = authorizationServerHost; + } + + public override ClientAuthenticationResult TryAuthenticateClient(AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(requestMessage, "requestMessage"); + clientIdentifier = requestMessage.ClientIdentifier; + return TryAuthenticateClient(this.authorizationServerHost, requestMessage.ClientIdentifier, requestMessage.ClientSecret); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialReader.cs new file mode 100644 index 0000000..085600a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialReader.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialReader.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; + + public abstract class ClientAuthenticationModuleBase : IClientAuthenticationModule { + protected ClientAuthenticationModuleBase() { + } + + public abstract ClientAuthenticationResult TryAuthenticateClient(AuthenticatedClientRequestBase requestMessage, out string clientIdentifier); + + public ClientAuthenticationResult TryAuthenticateClient(IDirectedProtocolMessage requestMessage, out string clientIdentifier) { + return this.TryAuthenticateClient((AuthenticatedClientRequestBase)requestMessage, out clientIdentifier); + } + + protected static ClientAuthenticationResult TryAuthenticateClient(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/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs index 7361fb9..fa21bdd 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs @@ -23,6 +23,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// not been revoked and that an access token has not expired. /// </remarks> internal class MessageValidationBindingElement : AuthServerBindingElementBase { + private readonly IClientAuthenticationModule clientAuthenticationModule; + + internal MessageValidationBindingElement(IClientAuthenticationModule clientAuthenticationModule) { + Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule"); + this.clientAuthenticationModule = clientAuthenticationModule; + } + /// <summary> /// Gets the protection commonly offered (if any) by this binding element. /// </summary> @@ -80,9 +87,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { var clientCredentialOnly = message as AccessTokenClientCredentialsRequest; var authenticatedClientRequest = message as AuthenticatedClientRequestBase; 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(authenticatedClientRequest, out clientIdentifier); + AuthServerUtilities.TokenEndpointVerify(result != ClientAuthenticationResult.ClientIdNotAuthenticated, Protocol.AccessTokenRequestErrorCodes.UnauthorizedClient); // an empty secret is not allowed for client authenticated calls. + AuthServerUtilities.TokenEndpointVerify(result == ClientAuthenticationResult.ClientAuthenticated, Protocol.AccessTokenRequestErrorCodes.InvalidClient, AuthServerStrings.ClientSecretMismatch); + authenticatedClientRequest.ClientIdentifier = clientIdentifier; if (clientCredentialOnly != null) { clientCredentialOnly.CredentialsValidated = true; diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs index bd154bc..2521e5f 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs @@ -35,8 +35,8 @@ 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)) { + protected internal OAuth2AuthorizationServerChannel(IAuthorizationServerHost authorizationServer, IClientAuthenticationModule clientAuthenticationModule) + : base(MessageTypes, InitializeBindingElements(authorizationServer, clientAuthenticationModule)) { Requires.NotNull(authorizationServer, "authorizationServer"); this.AuthorizationServer = authorizationServer; } @@ -109,12 +109,14 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <returns> /// An array of binding elements used to initialize the channel. /// </returns> - private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServerHost authorizationServer) { + private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServerHost authorizationServer, IClientAuthenticationModule 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)); bindingElements.Add(new TokenCodeSerializationBindingElement()); return bindingElements.ToArray(); diff --git a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj index 662bf86..cfe7d6d 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj +++ b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj @@ -20,7 +20,9 @@ <ItemGroup> <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 +62,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..85c3242 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs @@ -0,0 +1,15 @@ +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal interface IOAuth2ChannelWithClient { + /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + string ClientIdentifier { get; set; } + + 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..51b1646 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,18 @@ 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; } + + 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 +139,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { // Clients don't ever send direct responses. throw new NotImplementedException(); } + + protected override IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { + if (this.ClientCredentialApplicator != null) { + this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, webRequest); + } + + return base.GetDirectResponse(webRequest); + } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index 77b9cc6..271a87e 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,23 @@ 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 client secret shared with the Authorization Server. + /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server. /// </summary> - public string ClientSecret { get; set; } + /// <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; } + } + + internal IOAuth2ChannelWithClient OAuthChannel { + get { return (IOAuth2ChannelWithClient)this.Channel; } + } /// <summary> /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources @@ -118,10 +132,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 +160,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 +266,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 +283,22 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Applies any applicable client credential to an authenticated outbound request to the authorization server. + /// </summary> + /// <param name="request"></param> + protected void ApplyClientCredential(AuthenticatedClientRequestBase request) { + Requires.NotNull(request, "request"); + + if (this.ClientCredentialApplicator != null) { + this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request); + } + } + + protected static ClientCredentialApplicator DefaultSecretApplicator(string secret) { + return secret == null ? ClientCredentialApplicator.NoSecret() : ClientCredentialApplicator.SecretParameter(secret); + } + + /// <summary> /// Calculates the fraction of life remaining in an access token. /// </summary> /// <param name="authorization">The authorization to measure.</param> @@ -295,7 +327,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..ac78a90 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// <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; + + public abstract class ClientCredentialApplicator { + protected ClientCredentialApplicator() { + } + + public static ClientCredentialApplicator SecretParameter(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + return new SecretParameterApplicator(clientSecret); + } + + public static ClientCredentialApplicator NetworkCredential(NetworkCredential credential) { + Requires.NotNull(credential, "credential"); + return new NetworkCredentialApplicator(credential); + } + + public static ClientCredentialApplicator NoSecret() { + return null; + } + + public virtual void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + } + + public virtual void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + } + + private class NetworkCredentialApplicator : ClientCredentialApplicator { + private readonly NetworkCredential credential; + + internal NetworkCredentialApplicator(NetworkCredential credential) { + Requires.NotNull(credential, "credential"); + this.credential = credential; + } + + 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; + OAuthUtilities.ApplyHttpBasicAuth(request.Headers, clientIdentifier, this.credential.Password); + } + + public override void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + } + } + + private class SecretParameterApplicator : ClientCredentialApplicator { + private readonly string secret; + + internal SecretParameterApplicator(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + this.secret = clientSecret; + } + + public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + request.ClientSecret = this.secret; + } + + public override void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + } + } + } +} 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/AuthenticatedClientRequestBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs index bc4d0ca..03d5c8a 100644 --- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs @@ -6,13 +6,15 @@ 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 { + private readonly WebHeaderCollection headers = new WebHeaderCollection(); + /// <summary> /// Initializes a new instance of the <see cref="AuthenticatedClientRequestBase"/> class. /// </summary> @@ -26,7 +28,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 +43,9 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// </remarks> [MessagePart(Protocol.client_secret, IsRequired = false)] public string ClientSecret { get; internal set; } + + public WebHeaderCollection Headers { + get { return this.headers; } + } } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2AuthorizationServerElement.cs b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2AuthorizationServerElement.cs new file mode 100644 index 0000000..fa7b52e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2AuthorizationServerElement.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2AuthorizationServerElement.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// Represents the <oauth2/authorizationServer> element in the host's .config file. + /// </summary> + internal class OAuth2AuthorizationServerElement : ConfigurationElement { + + /// <summary> + /// The name of the <clientAuthenticationModules> sub-element. + /// </summary> + private const string ClientAuthenticationModulesElementName = "clientAuthenticationModules"; + + /// <summary> + /// The built-in set of identifier discovery services. + /// </summary> + private static readonly TypeConfigurationCollection<IClientAuthenticationModule> defaultClientAuthenticationModules = + new TypeConfigurationCollection<IClientAuthenticationModule>(); + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerElement"/> class. + /// </summary> + internal OAuth2AuthorizationServerElement() { + } + + /// <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<IClientAuthenticationModule>))] + internal TypeConfigurationCollection<IClientAuthenticationModule> ClientAuthenticationModules { + get { + var configResult = (TypeConfigurationCollection<IClientAuthenticationModule>)this[ClientAuthenticationModulesElementName]; + return configResult != null && configResult.Count > 0 ? configResult : defaultClientAuthenticationModules; + } + + set { + this[ClientAuthenticationModulesElementName] = value; + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2ClientElement.cs b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2ClientElement.cs new file mode 100644 index 0000000..95a7a36 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2ClientElement.cs @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ClientElement.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + + /// <summary> + /// Represents the <oauth2/client> element in the host's .config file. + /// </summary> + internal class OAuth2ClientElement : ConfigurationElement { + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ClientElement"/> class. + /// </summary> + internal OAuth2ClientElement() { + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2Element.cs b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2Element.cs new file mode 100644 index 0000000..6ba7e23 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2Element.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2Element.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 <oauth> element in the host's .config file. + /// </summary> + internal class OAuth2Element : ConfigurationSection { + /// <summary> + /// The name of the oauth section. + /// </summary> + private const string SectionName = DotNetOpenAuthSection.SectionName + "/oauth2"; + + /// <summary> + /// The name of the <client> sub-element. + /// </summary> + private const string ClientElementName = "client"; + + /// <summary> + /// The name of the <authorizationServer> sub-element. + /// </summary> + private const string AuthorizationServerElementName = "authorizationServer"; + + /// <summary> + /// The name of the <resourceServer> sub-element. + /// </summary> + private const string ResourceServerElementName = "resourceServer"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2Element"/> class. + /// </summary> + internal OAuth2Element() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + public static OAuth2Element Configuration { + get { + Contract.Ensures(Contract.Result<OAuth2Element>() != null); + return (OAuth2Element)ConfigurationManager.GetSection(SectionName) ?? new OAuth2Element(); + } + } + + /// <summary> + /// Gets or sets the configuration specific for Clients. + /// </summary> + [ConfigurationProperty(ClientElementName)] + internal OAuth2ClientElement Client { + get { return (OAuth2ClientElement)this[ClientElementName] ?? new OAuth2ClientElement(); } + set { this[ClientElementName] = value; } + } + + /// <summary> + /// Gets or sets the configuration specific for Authorization Servers. + /// </summary> + [ConfigurationProperty(AuthorizationServerElementName)] + internal OAuth2AuthorizationServerElement AuthorizationServer { + get { return (OAuth2AuthorizationServerElement)this[AuthorizationServerElementName] ?? new OAuth2AuthorizationServerElement(); } + set { this[AuthorizationServerElementName] = value; } + } + + /// <summary> + /// Gets or sets the configuration specific for Resource Servers. + /// </summary> + [ConfigurationProperty(ResourceServerElementName)] + internal OAuth2ResourceServerElement ResourceServer { + get { return (OAuth2ResourceServerElement)this[ResourceServerElementName] ?? new OAuth2ResourceServerElement(); } + set { this[ResourceServerElementName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2ResourceServerElement.cs b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2ResourceServerElement.cs new file mode 100644 index 0000000..a07e973 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2ResourceServerElement.cs @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ResourceServerElement.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + + /// <summary> + /// Represents the <oauth2/resourceServer> element in the host's .config file. + /// </summary> + internal class OAuth2ResourceServerElement : ConfigurationElement { + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ResourceServerElement"/> class. + /// </summary> + internal OAuth2ResourceServerElement() { + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj index 921cd84..a3eda22 100644 --- a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj +++ b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj @@ -18,10 +18,15 @@ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> </PropertyGroup> <ItemGroup> + <Compile Include="Configuration\OAuth2ResourceServerElement.cs" /> + <Compile Include="Configuration\OAuth2Element.cs" /> + <Compile Include="Configuration\OAuth2ClientElement.cs" /> + <Compile Include="Configuration\OAuth2AuthorizationServerElement.cs" /> <Compile Include="GlobalSuppressions.cs" /> <Compile Include="OAuth2\AccessToken.cs" /> <Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" /> <Compile Include="OAuth2\ChannelElements\IAccessTokenCarryingRequest.cs" /> + <Compile Include="OAuth2\ChannelElements\IClientAuthenticationModule.cs" /> <Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" /> <Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" /> <Compile Include="OAuth2\ChannelElements\IAuthorizationCarryingRequest.cs" /> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IClientAuthenticationModule.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IClientAuthenticationModule.cs new file mode 100644 index 0000000..b7c4792 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IClientAuthenticationModule.cs @@ -0,0 +1,22 @@ +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + + public enum ClientAuthenticationResult { + NoAuthenticationRecognized, + + ClientIdNotAuthenticated, + + ClientAuthenticated, + + ClientAuthenticationRejected, + } + + public interface IClientAuthenticationModule { + ClientAuthenticationResult TryAuthenticateClient(IDirectedProtocolMessage requestMessage, out string clientIdentifier); + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs index eb5c8e4..2e83482 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs @@ -18,6 +18,8 @@ namespace DotNetOpenAuth.OAuth2 { /// Some common utility methods for OAuth 2.0. /// </summary> public static class OAuthUtilities { + private const string HttpBasicAuthScheme = "Basic "; + /// <summary> /// The <see cref="StringComparer"/> instance to use when comparing scope equivalence. /// </summary> @@ -28,6 +30,8 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> private static char[] scopeDelimiter = new char[] { ' ' }; + private static readonly char[] ColonSeparator = new char[] { ':' }; + /// <summary> /// The characters that may appear in an access token that is included in an HTTP Authorization header. /// </summary> @@ -35,9 +39,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 +133,36 @@ namespace DotNetOpenAuth.OAuth2 { Protocol.BearerHttpAuthorizationHeaderFormat, accessToken); } + + private static readonly Encoding HttpBasicEncoding = Encoding.UTF8; + + 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; + } + + 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..1e70f80 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs @@ -0,0 +1,27 @@ +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; } + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs b/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs index 89271ae..dff831b 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<IClientAuthenticationModule>().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..fa4ffef 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.NetworkCredential(new NetworkCredential(OAuth2TestBase.ClientId, 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; + } } } |