//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth.ChannelElements {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Logging;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth.Messages;
using Validation;
///
/// A binding element for Service Providers to manage the
/// callbacks and verification codes on applicable messages.
///
internal class TokenHandlingBindingElement : IChannelBindingElement {
///
/// The token manager offered by the service provider.
///
private IServiceProviderTokenManager tokenManager;
///
/// The security settings for this service provider.
///
private ServiceProviderSecuritySettings securitySettings;
///
/// Initializes a new instance of the class.
///
/// The token manager.
/// The security settings.
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires(System.Boolean,System.String,System.String)", Justification = "Code contract"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")]
internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager, ServiceProviderSecuritySettings securitySettings) {
Requires.NotNull(tokenManager, "tokenManager");
Requires.NotNull(securitySettings, "securitySettings");
this.tokenManager = tokenManager;
this.securitySettings = securitySettings;
}
#region IChannelBindingElement Members
///
/// Gets or sets the channel that this binding element belongs to.
///
///
/// This property is set by the channel when it is first constructed.
///
public Channel Channel { get; set; }
///
/// Gets the protection commonly offered (if any) by this binding element.
///
///
/// This value is used to assist in sorting binding elements in the channel stack.
///
public MessageProtections Protection {
get { return MessageProtections.None; }
}
///
/// Prepares a message for sending based on the rules of this channel binding element.
///
/// The message to prepare for sending.
/// The cancellation token.
///
/// 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.
///
///
/// Implementations that provide message protection must honor the
/// properties where applicable.
///
public Task ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) {
var userAuthResponse = message as UserAuthorizationResponse;
if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) {
var requestToken = this.tokenManager.GetRequestToken(userAuthResponse.RequestToken);
requestToken.VerificationCode = userAuthResponse.VerificationCode;
this.tokenManager.UpdateToken(requestToken);
return MessageProtectionTasks.None;
}
// Hook to store the token and secret on its way down to the Consumer.
var grantRequestTokenResponse = message as UnauthorizedTokenResponse;
if (grantRequestTokenResponse != null) {
this.tokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse);
// The host may have already set these properties, but just to make sure...
var requestToken = this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken);
requestToken.ConsumerVersion = grantRequestTokenResponse.Version;
if (grantRequestTokenResponse.RequestMessage.Callback != null) {
requestToken.Callback = grantRequestTokenResponse.RequestMessage.Callback;
}
this.tokenManager.UpdateToken(requestToken);
return MessageProtectionTasks.None;
}
return MessageProtectionTasks.Null;
}
///
/// 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.
///
/// The incoming message to process.
/// The cancellation token.
///
/// 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.
///
///
/// Thrown when the binding element rules indicate that this message is invalid and should
/// NOT be processed.
///
///
/// Implementations that provide message protection must honor the
/// properties where applicable.
///
public Task ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) {
var authorizedTokenRequest = message as AuthorizedTokenRequest;
if (authorizedTokenRequest != null) {
if (authorizedTokenRequest.Version >= Protocol.V10a.Version) {
string expectedVerifier = this.tokenManager.GetRequestToken(authorizedTokenRequest.RequestToken).VerificationCode;
ErrorUtilities.VerifyProtocol(string.Equals(authorizedTokenRequest.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier);
return MessageProtectionTasks.None;
}
this.VerifyThrowTokenTimeToLive(authorizedTokenRequest);
}
var userAuthorizationRequest = message as UserAuthorizationRequest;
if (userAuthorizationRequest != null) {
this.VerifyThrowTokenTimeToLive(userAuthorizationRequest);
}
var accessResourceRequest = message as AccessProtectedResourceRequest;
if (accessResourceRequest != null) {
this.VerifyThrowTokenNotExpired(accessResourceRequest);
}
return MessageProtectionTasks.Null;
}
#endregion
///
/// Ensures that access tokens have not yet expired.
///
/// The incoming message carrying the access token.
private void VerifyThrowTokenNotExpired(AccessProtectedResourceRequest message) {
Requires.NotNull(message, "message");
try {
IServiceProviderAccessToken token = this.tokenManager.GetAccessToken(message.AccessToken);
if (token.ExpirationDate.HasValue && DateTime.Now >= token.ExpirationDate.Value.ToLocalTimeSafe()) {
Logger.OAuth.ErrorFormat(
"OAuth access token {0} rejected because it expired at {1}, and it is now {2}.",
token.Token,
token.ExpirationDate.Value,
DateTime.Now);
ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound);
}
} catch (KeyNotFoundException ex) {
throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound);
}
}
///
/// Ensures that short-lived request tokens included in incoming messages have not expired.
///
/// The incoming message.
/// Thrown when the token in the message has expired.
private void VerifyThrowTokenTimeToLive(ITokenContainingMessage message) {
ErrorUtilities.VerifyInternal(!(message is AccessProtectedResourceRequest), "We shouldn't be verifying TTL on access tokens.");
if (message == null || string.IsNullOrEmpty(message.Token)) {
return;
}
try {
IServiceProviderRequestToken token = this.tokenManager.GetRequestToken(message.Token);
TimeSpan ttl = this.securitySettings.MaximumRequestTokenTimeToLive;
if (DateTime.Now >= token.CreatedOn.ToLocalTimeSafe() + ttl) {
Logger.OAuth.ErrorFormat(
"OAuth request token {0} rejected because it was originally issued at {1}, expired at {2}, and it is now {3}.",
token.Token,
token.CreatedOn,
token.CreatedOn + ttl,
DateTime.Now);
ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound);
}
} catch (KeyNotFoundException ex) {
throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound);
}
}
}
}