diff options
8 files changed, 84 insertions, 7 deletions
diff --git a/samples/OAuthServiceProvider/App_Code/DataClasses.dbml b/samples/OAuthServiceProvider/App_Code/DataClasses.dbml index 78518c2..651de9f 100644 --- a/samples/OAuthServiceProvider/App_Code/DataClasses.dbml +++ b/samples/OAuthServiceProvider/App_Code/DataClasses.dbml @@ -25,9 +25,9 @@ <Column Name="ConsumerId" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" /> <Column Name="ConsumerKey" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" /> <Column Name="ConsumerSecret" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" /> - <Column Member="Callback" Type="System.String" CanBeNull="true" /> - <Column Member="VerificationCodeFormat" Type="DotNetOpenAuth.OAuth.VerificationCodeFormat" CanBeNull="false" /> - <Column Member="VerificationCodeLength" Type="System.Int32" CanBeNull="false" /> + <Column Name="Callback" Type="System.String" CanBeNull="true" /> + <Column Name="VerificationCodeFormat" Type="DotNetOpenAuth.OAuth.VerificationCodeFormat" CanBeNull="false" /> + <Column Name="VerificationCodeLength" Type="System.Int32" CanBeNull="false" /> <Association Name="OAuthConsumer_OAuthToken" Member="OAuthTokens" ThisKey="ConsumerId" OtherKey="ConsumerId" Type="OAuthToken" /> </Type> </Table> diff --git a/samples/OAuthServiceProvider/App_Code/OAuthToken.cs b/samples/OAuthServiceProvider/App_Code/OAuthToken.cs index 445e88c..ec9b31e 100644 --- a/samples/OAuthServiceProvider/App_Code/OAuthToken.cs +++ b/samples/OAuthServiceProvider/App_Code/OAuthToken.cs @@ -21,6 +21,10 @@ public partial class OAuthToken : IServiceProviderRequestToken { get { return this.OAuthConsumer.ConsumerKey; } } + DateTime IServiceProviderRequestToken.CreatedOn { + get { return this.IssueDate; } + } + Uri IServiceProviderRequestToken.Callback { get { return new Uri(this.RequestTokenCallback); } set { this.RequestTokenCallback = value.AbsoluteUri; } diff --git a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs index 5585107..6980761 100644 --- a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs +++ b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs @@ -126,8 +126,14 @@ namespace DotNetOpenAuth.Test.Mocks { } private class TokenInfo : IServiceProviderRequestToken { + internal TokenInfo() { + this.CreatedOn = DateTime.Now; + } + public string ConsumerKey { get; set; } + public DateTime CreatedOn { get; set; } + public string Token { get; set; } public string VerificationCode { get; set; } diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs index 92d9645..c58c023 100644 --- a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs @@ -22,6 +22,11 @@ namespace DotNetOpenAuth.Configuration { private const string MinimumRequiredOAuthVersionConfigName = "minimumRequiredOAuthVersion"; /// <summary> + /// Gets the name of the @maxAuthorizationTime attribute. + /// </summary> + private const string MaximumRequestTokenTimeToLiveConfigName = "maxAuthorizationTime"; + + /// <summary> /// Initializes a new instance of the <see cref="OAuthServiceProviderSecuritySettingsElement"/> class. /// </summary> internal OAuthServiceProviderSecuritySettingsElement() { @@ -41,6 +46,22 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the maximum time a user can take to complete authorization. + /// </summary> + /// <remarks> + /// This time limit serves as a security mitigation against brute force attacks to + /// compromise (unauthorized or authorized) request tokens. + /// Longer time limits is more friendly to slow users or consumers, while shorter + /// time limits provide better security. + /// </remarks> + [ConfigurationProperty(MaximumRequestTokenTimeToLiveConfigName, DefaultValue = "0:05")] // 5 minutes + [PositiveTimeSpanValidator] + public TimeSpan MaximumRequestTokenTimeToLive { + get { return (TimeSpan)this[MaximumRequestTokenTimeToLiveConfigName]; } + set { this[MaximumRequestTokenTimeToLiveConfigName] = value; } + } + + /// <summary> /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file. /// </summary> /// <returns>The newly created security settings object.</returns> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs index 48f39a6..6dfa416 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs @@ -23,6 +23,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { string ConsumerKey { get; } /// <summary> + /// Gets the (local) date that this request token was first created on. + /// </summary> + DateTime CreatedOn { get; } + + /// <summary> /// Gets or sets the callback associated specifically with this token, if any. /// </summary> /// <value>The callback URI; or <c>null</c> if no callback was specifically assigned to this token.</value> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs index b8273c3..e8acdcf 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System.Diagnostics.Contracts; using System.Linq; using System.Text; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.Messages; @@ -111,15 +112,52 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { ErrorUtilities.VerifyArgumentNotNull(message, "message"); var authorizedTokenRequest = message as AuthorizedTokenRequest; - if (authorizedTokenRequest != null && 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 MessageProtections.None; + 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 MessageProtections.None; + } + + this.VerifyThrowTokenTimeToLive(authorizedTokenRequest); + } + + var userAuthorizationRequest = message as UserAuthorizationRequest; + if (userAuthorizationRequest != null) { + this.VerifyThrowTokenTimeToLive(userAuthorizationRequest); } return null; } #endregion + + /// <summary> + /// Ensures that short-lived request tokens included in incoming messages have not expired. + /// </summary> + /// <param name="message">The incoming message.</param> + /// <exception cref="ProtocolException">Thrown when the token in the message has expired.</exception> + private void VerifyThrowTokenTimeToLive(ITokenContainingMessage message) { + ErrorUtilities.VerifyInternal(!(message is AccessProtectedResourceRequest), "We shouldn't be verifying TTL on access tokens."); + if (message == null) { + return; + } + + try { + IServiceProviderRequestToken token = this.tokenManager.GetRequestToken(message.Token); + TimeSpan ttl = DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.MaximumRequestTokenTimeToLive; + if (DateTime.Now >= token.CreatedOn.ToLocalTime() + ttl) { + Logger.OAuth.ErrorFormat( + "OAuth 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); + } + } } } diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index d4fe85e..69d66f9 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OAuth { using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs b/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs index 094f54c..b8e12fd 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs @@ -5,6 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth { + using System; + /// <summary> /// Security settings that are applicable to service providers. /// </summary> |