summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2.AuthorizationServer
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.AuthorizationServer')
-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
10 files changed, 399 insertions, 18 deletions
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();