//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Messaging.Bindings {
using System;
using System.Threading;
using System.Threading.Tasks;
using DotNetOpenAuth.Configuration;
///
/// A message expiration enforcing binding element that supports messages
/// implementing the interface.
///
internal class StandardExpirationBindingElement : IChannelBindingElement {
///
/// A reusable pre-completed task that may be returned multiple times to reduce GC pressure.
///
private static readonly Task NullTask = Task.FromResult(null);
///
/// A reusable pre-completed task that may be returned multiple times to reduce GC pressure.
///
private static readonly Task CompletedExpirationTask = Task.FromResult(MessageProtections.Expiration);
///
/// Initializes a new instance of the class.
///
internal StandardExpirationBindingElement() {
}
#region IChannelBindingElement Properties
///
/// Gets the protection offered by this binding element.
///
///
MessageProtections IChannelBindingElement.Protection {
get { return MessageProtections.Expiration; }
}
///
/// Gets or sets the channel that this binding element belongs to.
///
public Channel Channel { get; set; }
#endregion
///
/// Gets the maximum age a message implementing the
/// interface can be before
/// being discarded as too old.
///
protected internal static TimeSpan MaximumMessageAge {
get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetime; }
}
#region IChannelBindingElement Methods
///
/// Sets the timestamp on an outgoing message.
///
/// The outgoing message.
/// 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) {
IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage;
if (expiringMessage != null) {
expiringMessage.UtcCreationDate = DateTime.UtcNow;
return CompletedExpirationTask;
}
return NullTask;
}
///
/// Reads the timestamp on a message and throws an exception if the message is too old.
///
/// The incoming message.
/// 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 if the given message has already expired.
///
/// Thrown when the binding element rules indicate that this message is invalid and should
/// NOT be processed.
///
public Task ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) {
IExpiringProtocolMessage expiringMessage = message as IExpiringProtocolMessage;
if (expiringMessage != null) {
// Yes the UtcCreationDate is supposed to always be in UTC already,
// but just in case a given message failed to guarantee that, we do it here.
DateTime creationDate = expiringMessage.UtcCreationDate.ToUniversalTimeSafe();
DateTime expirationDate = creationDate + MaximumMessageAge;
if (expirationDate < DateTime.UtcNow) {
throw new ExpiredMessageException(expirationDate, expiringMessage);
}
// Mitigate HMAC attacks (just guessing the signature until they get it) by
// disallowing post-dated messages.
ErrorUtilities.VerifyProtocol(
creationDate <= DateTime.UtcNow + DotNetOpenAuthSection.Messaging.MaximumClockSkew,
MessagingStrings.MessageTimestampInFuture,
creationDate);
return CompletedExpirationTask;
}
return NullTask;
}
#endregion
}
}