summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements')
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AccessTokenBindingElement.cs94
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs81
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs118
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs19
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs160
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs124
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs56
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs120
8 files changed, 772 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AccessTokenBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AccessTokenBindingElement.cs
new file mode 100644
index 0000000..9d7e8f2
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AccessTokenBindingElement.cs
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------
+// <copyright file="AccessTokenBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Serializes access tokens inside an outgoing message.
+ /// </summary>
+ internal class AccessTokenBindingElement : AuthServerBindingElementBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AccessTokenBindingElement"/> class.
+ /// </summary>
+ internal AccessTokenBindingElement() {
+ }
+
+ /// <summary>
+ /// Gets the protection commonly offered (if any) by this binding element.
+ /// </summary>
+ /// <value>Always <c>MessageProtections.None</c></value>
+ /// <remarks>
+ /// This value is used to assist in sorting binding elements in the channel stack.
+ /// </remarks>
+ public override MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ var directResponse = message as IDirectResponseProtocolMessage;
+ var request = directResponse != null ? directResponse.OriginatingRequest as IAccessTokenRequestInternal : null;
+ var authCarryingRequest = request as IAuthorizationCarryingRequest;
+ var accessTokenResponse = message as IAccessTokenIssuingResponse;
+ var implicitGrantResponse = message as EndUserAuthorizationSuccessAccessTokenResponse;
+
+ if (request != null) {
+ request.AccessTokenCreationParameters = this.AuthorizationServer.GetAccessTokenParameters(request);
+ ErrorUtilities.VerifyHost(request.AccessTokenCreationParameters != null, "IAuthorizationServer.GetAccessTokenParameters must not return null.");
+
+ if (accessTokenResponse != null) {
+ accessTokenResponse.Lifetime = request.AccessTokenCreationParameters.AccessTokenLifetime;
+ }
+ }
+
+ if (authCarryingRequest != null) {
+ ErrorUtilities.VerifyInternal(request != null, MessagingStrings.UnexpectedMessageReceived, typeof(IAccessTokenRequestInternal), request.GetType());
+ accessTokenResponse.AuthorizationDescription = new AccessToken(authCarryingRequest.AuthorizationDescription, accessTokenResponse.Lifetime);
+ } else if (implicitGrantResponse != null) {
+ IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse;
+ accessTokenResponse.AuthorizationDescription = new AccessToken(
+ request.ClientIdentifier,
+ implicitGrantResponse.Scope,
+ implicitGrantResponse.AuthorizingUsername,
+ implicitGrantResponse.Lifetime);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ return null;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs
new file mode 100644
index 0000000..49f820d
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthServerBindingElementBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using Messaging;
+
+ /// <summary>
+ /// The base class for any authorization server channel binding element.
+ /// </summary>
+ internal abstract class AuthServerBindingElementBase : IChannelBindingElement {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthServerBindingElementBase"/> class.
+ /// </summary>
+ protected AuthServerBindingElementBase() {
+ }
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ /// <remarks>
+ /// This property is set by the channel when it is first constructed.
+ /// </remarks>
+ public Channel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the protection commonly offered (if any) by this binding element.
+ /// </summary>
+ /// <remarks>
+ /// This value is used to assist in sorting binding elements in the channel stack.
+ /// </remarks>
+ public abstract MessageProtections Protection { get; }
+
+ /// <summary>
+ /// Gets the authorization server hosting this channel.
+ /// </summary>
+ /// <value>The authorization server.</value>
+ protected IAuthorizationServer AuthorizationServer {
+ get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public abstract MessageProtections? ProcessOutgoingMessage(IProtocolMessage message);
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public abstract MessageProtections? ProcessIncomingMessage(IProtocolMessage message);
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs
new file mode 100644
index 0000000..f5f25b2
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs
@@ -0,0 +1,118 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthorizationCode.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Represents the authorization code created when a user approves authorization that
+ /// allows the client to request an access/refresh token.
+ /// </summary>
+ internal class AuthorizationCode : AuthorizationDataBag {
+ /// <summary>
+ /// The name of the bucket for symmetric keys used to sign authorization codes.
+ /// </summary>
+ internal const string AuthorizationCodeKeyBucket = "https://localhost/dnoa/oauth_authorization_code";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthorizationCode"/> class.
+ /// </summary>
+ public AuthorizationCode() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthorizationCode"/> class.
+ /// </summary>
+ /// <param name="clientIdentifier">The client identifier.</param>
+ /// <param name="callback">The callback the client used to obtain authorization, if one was explicitly included in the request.</param>
+ /// <param name="scopes">The authorized scopes.</param>
+ /// <param name="username">The name on the account that authorized access.</param>
+ internal AuthorizationCode(string clientIdentifier, Uri callback, IEnumerable<string> scopes, string username) {
+ Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier");
+
+ this.ClientIdentifier = clientIdentifier;
+ this.CallbackHash = CalculateCallbackHash(callback);
+ this.Scope.ResetContents(scopes);
+ this.User = username;
+ this.UtcCreationDate = DateTime.UtcNow;
+ }
+
+ /// <summary>
+ /// Gets the maximum message age from the standard expiration binding element.
+ /// </summary>
+ /// <value>This interval need not account for clock skew because it is only compared within a single authorization server or farm of servers.</value>
+ internal static TimeSpan MaximumMessageAge {
+ get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetimeNoSkew; }
+ }
+
+ /// <summary>
+ /// Gets or sets the hash of the callback URL.
+ /// </summary>
+ [MessagePart("cb")]
+ private byte[] CallbackHash { get; set; }
+
+ /// <summary>
+ /// Creates a serializer/deserializer for this type.
+ /// </summary>
+ /// <param name="authorizationServer">The authorization server that will be serializing/deserializing this authorization code. Must not be null.</param>
+ /// <returns>A DataBag formatter.</returns>
+ internal static IDataBagFormatter<AuthorizationCode> CreateFormatter(IAuthorizationServer authorizationServer) {
+ Requires.NotNull(authorizationServer, "authorizationServer");
+ Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null);
+
+ var cryptoStore = authorizationServer.CryptoKeyStore;
+ ErrorUtilities.VerifyHost(cryptoStore != null, OAuthStrings.ResultShouldNotBeNull, authorizationServer.GetType(), "CryptoKeyStore");
+
+ return new UriStyleMessageFormatter<AuthorizationCode>(
+ cryptoStore,
+ AuthorizationCodeKeyBucket,
+ signed: true,
+ encrypted: true,
+ compressed: false,
+ maximumAge: MaximumMessageAge,
+ decodeOnceOnly: authorizationServer.VerificationCodeNonceStore);
+ }
+
+ /// <summary>
+ /// Verifies the the given callback URL matches the callback originally given in the authorization request.
+ /// </summary>
+ /// <param name="callback">The callback.</param>
+ /// <remarks>
+ /// This method serves to verify that the callback URL given in the original authorization request
+ /// and the callback URL given in the access token request match.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown when the callback URLs do not match.</exception>
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "redirecturimismatch", Justification = "Protocol requirement")]
+ [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")]
+ internal void VerifyCallback(Uri callback) {
+ ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalentConstantTime(this.CallbackHash, CalculateCallbackHash(callback)), Protocol.redirect_uri_mismatch);
+ }
+
+ /// <summary>
+ /// Calculates the hash of the callback URL.
+ /// </summary>
+ /// <param name="callback">The callback whose hash should be calculated.</param>
+ /// <returns>
+ /// A base64 encoding of the hash of the URL.
+ /// </returns>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")]
+ private static byte[] CalculateCallbackHash(Uri callback) {
+ if (callback == null) {
+ return null;
+ }
+
+ using (var hasher = new SHA256Managed()) {
+ return hasher.ComputeHash(Encoding.UTF8.GetBytes(callback.AbsoluteUri));
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs
new file mode 100644
index 0000000..5fc73ce
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs
@@ -0,0 +1,19 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOAuth2ChannelWithAuthorizationServer.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ /// <summary>
+ /// An interface on an OAuth 2 Authorization Server channel
+ /// to expose the host provided authorization server object.
+ /// </summary>
+ internal interface IOAuth2ChannelWithAuthorizationServer {
+ /// <summary>
+ /// Gets the authorization server.
+ /// </summary>
+ /// <value>The authorization server.</value>
+ IAuthorizationServer AuthorizationServer { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
new file mode 100644
index 0000000..43ce243
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
@@ -0,0 +1,160 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageValidationBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.OAuth2.Messages;
+ using Messaging;
+
+ /// <summary>
+ /// A guard for all messages to or from an Authorization Server to ensure that they are well formed,
+ /// have valid secrets, callback URIs, etc.
+ /// </summary>
+ /// <remarks>
+ /// This binding element also ensures that the code/token coming in is issued to
+ /// the same client that is sending the code/token and that the authorization has
+ /// not been revoked and that an access token has not expired.
+ /// </remarks>
+ internal class MessageValidationBindingElement : AuthServerBindingElementBase {
+ /// <summary>
+ /// Gets the protection commonly offered (if any) by this binding element.
+ /// </summary>
+ /// <remarks>
+ /// This value is used to assist in sorting binding elements in the channel stack.
+ /// </remarks>
+ public override MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ var accessTokenResponse = message as AccessTokenSuccessResponse;
+ if (accessTokenResponse != null) {
+ var directResponseMessage = (IDirectResponseProtocolMessage)accessTokenResponse;
+ var accessTokenRequest = (AccessTokenRequestBase)directResponseMessage.OriginatingRequest;
+ ErrorUtilities.VerifyProtocol(accessTokenRequest.GrantType != GrantType.ClientCredentials || accessTokenResponse.RefreshToken == null, OAuthStrings.NoGrantNoRefreshToken);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ bool applied = false;
+
+ // Check that the client secret is correct for client authenticated messages.
+ var clientCredentialOnly = message as AccessTokenClientCredentialsRequest;
+ var authenticatedClientRequest = message as AuthenticatedClientRequestBase;
+ if (authenticatedClientRequest != null) {
+ var client = this.AuthorizationServer.GetClientOrThrow(authenticatedClientRequest.ClientIdentifier);
+ string secret = client.Secret;
+ AuthServerUtilities.TokenEndpointVerify(!string.IsNullOrEmpty(secret), Protocol.AccessTokenRequestErrorCodes.UnauthorizedClient); // an empty secret is not allowed for client authenticated calls.
+ AuthServerUtilities.TokenEndpointVerify(MessagingUtilities.EqualsConstantTime(secret, authenticatedClientRequest.ClientSecret), Protocol.AccessTokenRequestErrorCodes.InvalidClient, AuthServerStrings.ClientSecretMismatch);
+
+ if (clientCredentialOnly != null) {
+ clientCredentialOnly.CredentialsValidated = true;
+ }
+
+ applied = true;
+ }
+
+ // Check that any resource owner password credential is correct.
+ var resourceOwnerPasswordCarrier = message as AccessTokenResourceOwnerPasswordCredentialsRequest;
+ if (resourceOwnerPasswordCarrier != null) {
+ try {
+ if (this.AuthorizationServer.IsResourceOwnerCredentialValid(resourceOwnerPasswordCarrier.UserName, resourceOwnerPasswordCarrier.Password)) {
+ resourceOwnerPasswordCarrier.CredentialsValidated = true;
+ } else {
+ 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);
+ }
+ } catch (NotSupportedException) {
+ throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
+ } catch (NotImplementedException) {
+ throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
+ }
+ }
+
+ // Check that authorization requests come with an acceptable callback URI.
+ var authorizationRequest = message as EndUserAuthorizationRequest;
+ if (authorizationRequest != null) {
+ var client = this.AuthorizationServer.GetClientOrThrow(authorizationRequest.ClientIdentifier);
+ ErrorUtilities.VerifyProtocol(authorizationRequest.Callback == null || client.IsCallbackAllowed(authorizationRequest.Callback), OAuthStrings.ClientCallbackDisallowed, authorizationRequest.Callback);
+ ErrorUtilities.VerifyProtocol(authorizationRequest.Callback != null || client.DefaultCallback != null, OAuthStrings.NoCallback);
+ applied = true;
+ }
+
+ // Check that the callback URI in a direct message from the client matches the one in the indirect message received earlier.
+ var request = message as AccessTokenAuthorizationCodeRequestAS;
+ if (request != null) {
+ IAuthorizationCodeCarryingRequest tokenRequest = request;
+ tokenRequest.AuthorizationDescription.VerifyCallback(request.Callback);
+ applied = true;
+ }
+
+ var authCarrier = message as IAuthorizationCarryingRequest;
+ if (authCarrier != null) {
+ 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);
+
+ 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);
+ }
+ }
+ }
+
+ // 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 IAuthorizationServer.IsAuthorizationValid method returned false.");
+ throw new TokenEndpointProtocolException(Protocol.AccessTokenRequestErrorCodes.InvalidGrant);
+ }
+
+ applied = true;
+ }
+
+ return applied ? (MessageProtections?)MessageProtections.None : null;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs
new file mode 100644
index 0000000..09d35ee
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuth2AuthorizationServerChannel.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Net.Mime;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth2.AuthServer.Messages;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// The channel for the OAuth protocol.
+ /// </summary>
+ internal class OAuth2AuthorizationServerChannel : OAuth2ChannelBase, IOAuth2ChannelWithAuthorizationServer {
+ /// <summary>
+ /// The messages receivable by this channel.
+ /// </summary>
+ private static readonly Type[] MessageTypes = new Type[] {
+ typeof(AccessTokenRefreshRequestAS),
+ typeof(AccessTokenAuthorizationCodeRequestAS),
+ typeof(AccessTokenResourceOwnerPasswordCredentialsRequest),
+ typeof(AccessTokenClientCredentialsRequest),
+ typeof(EndUserAuthorizationRequest),
+ typeof(EndUserAuthorizationImplicitRequest),
+ typeof(EndUserAuthorizationFailedResponse),
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerChannel"/> class.
+ /// </summary>
+ /// <param name="authorizationServer">The authorization server.</param>
+ protected internal OAuth2AuthorizationServerChannel(IAuthorizationServer authorizationServer)
+ : base(MessageTypes, InitializeBindingElements(authorizationServer)) {
+ Requires.NotNull(authorizationServer, "authorizationServer");
+ this.AuthorizationServer = authorizationServer;
+ }
+
+ /// <summary>
+ /// Gets the authorization server.
+ /// </summary>
+ /// <value>The authorization server.</value>
+ public IAuthorizationServer AuthorizationServer { get; private set; }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response.
+ /// </summary>
+ /// <param name="response">The response that is anticipated to contain an protocol message.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
+ protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <returns>
+ /// The pending user agent redirect based message to be sent as an HttpResponse.
+ /// </returns>
+ /// <remarks>
+ /// This method implements spec OAuth V1.0 section 5.3.
+ /// </remarks>
+ protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
+ var webResponse = new OutgoingWebResponse();
+ ApplyMessageTemplate(response, webResponse);
+ string json = this.SerializeAsJson(response);
+ webResponse.SetResponse(json, new ContentType(JsonEncoded));
+ return webResponse;
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be embedded in the given HTTP request.
+ /// </summary>
+ /// <param name="request">The request to search for an embedded message.</param>
+ /// <returns>
+ /// The deserialized message, if one is found. Null otherwise.
+ /// </returns>
+ protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request) {
+ if (!string.IsNullOrEmpty(request.Url.Fragment)) {
+ var fields = HttpUtility.ParseQueryString(request.Url.Fragment.Substring(1)).ToDictionary();
+
+ MessageReceivingEndpoint recipient;
+ try {
+ recipient = request.GetRecipient();
+ } catch (ArgumentException ex) {
+ Logger.Messaging.WarnFormat("Unrecognized HTTP request: " + ex.ToString());
+ return null;
+ }
+
+ return (IDirectedProtocolMessage)this.Receive(fields, recipient);
+ }
+
+ return base.ReadFromRequestCore(request);
+ }
+
+ /// <summary>
+ /// Initializes the binding elements for the OAuth channel.
+ /// </summary>
+ /// <param name="authorizationServer">The authorization server.</param>
+ /// <returns>
+ /// An array of binding elements used to initialize the channel.
+ /// </returns>
+ private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServer authorizationServer) {
+ Requires.NotNull(authorizationServer, "authorizationServer");
+ 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 AccessTokenBindingElement());
+ bindingElements.Add(new TokenCodeSerializationBindingElement());
+
+ return bindingElements.ToArray();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs
new file mode 100644
index 0000000..993583c
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------
+// <copyright file="RefreshToken.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// The refresh token issued to a client by an authorization server that allows the client
+ /// to periodically obtain new short-lived access tokens.
+ /// </summary>
+ internal class RefreshToken : AuthorizationDataBag {
+ /// <summary>
+ /// The name of the bucket for symmetric keys used to sign refresh tokens.
+ /// </summary>
+ internal const string RefreshTokenKeyBucket = "https://localhost/dnoa/oauth_refresh_token";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RefreshToken"/> class.
+ /// </summary>
+ public RefreshToken() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RefreshToken"/> class.
+ /// </summary>
+ /// <param name="authorization">The authorization this refresh token should describe.</param>
+ internal RefreshToken(IAuthorizationDescription authorization) {
+ Requires.NotNull(authorization, "authorization");
+
+ this.ClientIdentifier = authorization.ClientIdentifier;
+ this.UtcCreationDate = authorization.UtcIssued;
+ this.User = authorization.User;
+ this.Scope.ResetContents(authorization.Scope);
+ }
+
+ /// <summary>
+ /// Creates a formatter capable of serializing/deserializing a refresh token.
+ /// </summary>
+ /// <param name="cryptoKeyStore">The crypto key store.</param>
+ /// <returns>
+ /// A DataBag formatter. Never null.
+ /// </returns>
+ internal static IDataBagFormatter<RefreshToken> CreateFormatter(ICryptoKeyStore cryptoKeyStore) {
+ Requires.NotNull(cryptoKeyStore, "cryptoKeyStore");
+ Contract.Ensures(Contract.Result<IDataBagFormatter<RefreshToken>>() != null);
+
+ return new UriStyleMessageFormatter<RefreshToken>(cryptoKeyStore, RefreshTokenKeyBucket, signed: true, encrypted: true);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs
new file mode 100644
index 0000000..b14f366
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs
@@ -0,0 +1,120 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenCodeSerializationBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Globalization;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth2.AuthServer.ChannelElements;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Serializes and deserializes authorization codes, refresh tokens and access tokens
+ /// on incoming and outgoing messages.
+ /// </summary>
+ internal class TokenCodeSerializationBindingElement : AuthServerBindingElementBase {
+ /// <summary>
+ /// Gets the protection commonly offered (if any) by this binding element.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// This value is used to assist in sorting binding elements in the channel stack.
+ /// </remarks>
+ public override MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ var directResponse = message as IDirectResponseProtocolMessage;
+ var request = directResponse != null ? directResponse.OriginatingRequest as IAccessTokenRequestInternal : null;
+
+ // Serialize the authorization code, if there is one.
+ var authCodeCarrier = message as IAuthorizationCodeCarryingRequest;
+ if (authCodeCarrier != null) {
+ var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer);
+ var code = authCodeCarrier.AuthorizationDescription;
+ authCodeCarrier.Code = codeFormatter.Serialize(code);
+ return MessageProtections.None;
+ }
+
+ // Serialize the refresh token, if applicable.
+ var refreshTokenResponse = message as AccessTokenSuccessResponse;
+ if (refreshTokenResponse != null && refreshTokenResponse.HasRefreshToken) {
+ var refreshTokenCarrier = (IAuthorizationCarryingRequest)message;
+ var refreshToken = new RefreshToken(refreshTokenCarrier.AuthorizationDescription);
+ var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore);
+ refreshTokenResponse.RefreshToken = refreshTokenFormatter.Serialize(refreshToken);
+ }
+
+ // Serialize the access token, if applicable.
+ var accessTokenResponse = message as IAccessTokenIssuingResponse;
+ if (accessTokenResponse != null && accessTokenResponse.AuthorizationDescription != null) {
+ ErrorUtilities.VerifyInternal(request != null, "We should always have a direct request message for this case.");
+ var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServer.AccessTokenSigningKey, request.AccessTokenCreationParameters.ResourceServerEncryptionKey);
+ accessTokenResponse.AccessToken = accessTokenFormatter.Serialize(accessTokenResponse.AuthorizationDescription);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "unauthorizedclient", Justification = "Protocol requirement")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "incorrectclientcredentials", Justification = "Protocol requirement")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "authorizationexpired", Justification = "Protocol requirement")]
+ [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")]
+ public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ var authCodeCarrier = message as IAuthorizationCodeCarryingRequest;
+ if (authCodeCarrier != null) {
+ var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer);
+ var authorizationCode = authorizationCodeFormatter.Deserialize(message, authCodeCarrier.Code, Protocol.code);
+ authCodeCarrier.AuthorizationDescription = authorizationCode;
+ }
+
+ var refreshTokenCarrier = message as IRefreshTokenCarryingRequest;
+ if (refreshTokenCarrier != null) {
+ var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore);
+ var refreshToken = refreshTokenFormatter.Deserialize(message, refreshTokenCarrier.RefreshToken, Protocol.refresh_token);
+ refreshTokenCarrier.AuthorizationDescription = refreshToken;
+ }
+
+ return null;
+ }
+ }
+}