diff options
Diffstat (limited to 'src')
6 files changed, 105 insertions, 21 deletions
diff --git a/src/DotNetOpenAuth/OAuthWrap/AuthorizationState.cs b/src/DotNetOpenAuth/OAuthWrap/AuthorizationState.cs index a19f523..ac4112f 100644 --- a/src/DotNetOpenAuth/OAuthWrap/AuthorizationState.cs +++ b/src/DotNetOpenAuth/OAuthWrap/AuthorizationState.cs @@ -25,8 +25,12 @@ namespace DotNetOpenAuth.OAuthWrap { public string AccessTokenSecret { get; set; } + public string AccessTokenSecretType { get; set; } + public DateTime? AccessTokenExpirationUtc { get; set; } + public DateTime? AccessTokenIssueDateUtc { get; set; } + public string Scope { get; set; } /// <summary> @@ -37,11 +41,11 @@ namespace DotNetOpenAuth.OAuthWrap { /// </value> public bool IsDeleted { get; set; } - public void Delete() { + public virtual void Delete() { this.IsDeleted = true; } - public void SaveChanges() { + public virtual void SaveChanges() { } } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs index e417b73..841aa34 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs @@ -14,6 +14,7 @@ namespace DotNetOpenAuth.OAuthWrap { using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuthWrap.ChannelElements; + using DotNetOpenAuth.OAuthWrap.Messages; /// <summary> /// A base class for common OAuth WRAP Client behaviors. @@ -42,29 +43,101 @@ namespace DotNetOpenAuth.OAuthWrap { public Channel Channel { get; private set; } /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + public string ClientIdentifier { get; set; } + + /// <summary> + /// Gets or sets the client secret shared with the Authorization Server. + /// </summary> + public string ClientSecret { get; set; } + + /// <summary> /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources /// so that the Service Provider will allow the request through. /// </summary> /// <param name="request">The request for protected resources from the service provider.</param> /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param> - public static void AuthorizeRequest(HttpWebRequest request, string accessToken) { + public void AuthorizeRequest(HttpWebRequest request, string accessToken) { Contract.Requires<ArgumentNullException>(request != null); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(accessToken)); WrapUtilities.AuthorizeWithOAuthWrap(request, accessToken); } /// <summary> - /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources - /// so that the Service Provider will allow the request through. + /// Adds the OAuth authorization token to an outgoing HTTP request, renewing a + /// (nearly) expired access token if necessary. /// </summary> /// <param name="request">The request for protected resources from the service provider.</param> /// <param name="authorization">The authorization for this request previously obtained via OAuth WRAP.</param> - public static void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) { + public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) { Contract.Requires<ArgumentNullException>(request != null); Contract.Requires<ArgumentNullException>(authorization != null); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(authorization.AccessToken)); - Contract.Requires<ProtocolException>(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc < DateTime.UtcNow); - AuthorizeRequest(request, authorization.AccessToken); + Contract.Requires<ProtocolException>(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc < DateTime.UtcNow || authorization.RefreshToken != null); + + if (authorization.AccessTokenExpirationUtc.HasValue && authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { + ErrorUtilities.VerifyProtocol(authorization.RefreshToken != null, "Access token has expired and cannot be automatically refreshed."); + this.RefreshToken(authorization); + } + + this.AuthorizeRequest(request, authorization.AccessToken); + } + + /// <summary> + /// Refreshes a short-lived access token using a longer-lived refresh token. + /// </summary> + /// <param name="authorization">The authorization to update.</param> + /// <param name="skipIfUsefulLifeExceeds">If given, the access token will <em>not</em> be refreshed if its remaining lifetime exceeds this value.</param> + public void RefreshToken(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null) { + Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(authorization.RefreshToken)); + + if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue) { + TimeSpan usefulLifeRemaining = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow; + if (usefulLifeRemaining > skipIfUsefulLifeExceeds.Value) { + // There is useful life remaining in the access token. Don't refresh. + Logger.Wrap.DebugFormat("Skipping token refresh step because access token's remaining life is {0}, which exceeds {1}.", usefulLifeRemaining, skipIfUsefulLifeExceeds.Value); + return; + } + } + + var request = new RefreshAccessTokenRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + ClientSecret = this.ClientSecret, + RefreshToken = authorization.RefreshToken, + SecretType = authorization.AccessTokenSecretType, + }; + + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); + authorization.AccessToken = response.AccessToken; + authorization.AccessTokenExpirationUtc = DateTime.UtcNow + response.Lifetime; + authorization.AccessTokenSecret = response.AccessTokenSecret; + authorization.AccessTokenIssueDateUtc = DateTime.UtcNow; + + // Just in case the scope has changed... + if (response.Scope != null) { + authorization.Scope = response.Scope; + } + + // The authorization server MAY choose to renew the refresh token itself. + if (response.RefreshToken != null) { + authorization.RefreshToken = response.RefreshToken; + } + + authorization.SaveChanges(); + } + + private double ProportionalLifeRemaining(IAuthorizationState authorization) { + Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentException>(authorization.AccessTokenIssueDateUtc.HasValue); + Contract.Requires<ArgumentException>(authorization.AccessTokenExpirationUtc.HasValue); + + // Calculate what % of the total life this access token has left. + TimeSpan totalLifetime = authorization.AccessTokenExpirationUtc.Value - authorization.AccessTokenIssueDateUtc.Value; + TimeSpan elapsedLifetime = DateTime.UtcNow - authorization.AccessTokenIssueDateUtc.Value; + double proportionLifetimeRemaining = 1 - (elapsedLifetime.TotalSeconds / totalLifetime.TotalSeconds); + return proportionLifetimeRemaining; } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/IAuthorizationState.cs b/src/DotNetOpenAuth/OAuthWrap/IAuthorizationState.cs index 7e86b5b..caf6ddc 100644 --- a/src/DotNetOpenAuth/OAuthWrap/IAuthorizationState.cs +++ b/src/DotNetOpenAuth/OAuthWrap/IAuthorizationState.cs @@ -36,6 +36,10 @@ namespace DotNetOpenAuth.OAuthWrap /// <value>The access token secret.</value> string AccessTokenSecret { get; set; } + string AccessTokenSecretType { get; set; } + + DateTime? AccessTokenIssueDateUtc { get; set; } + /// <summary> /// Gets or sets the access token UTC expiration date. /// </summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/RefreshAccessTokenRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/RefreshAccessTokenRequest.cs index 9b322f5..b318df7 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/RefreshAccessTokenRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/RefreshAccessTokenRequest.cs @@ -28,6 +28,17 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// <param name="version">The version.</param> internal RefreshAccessTokenRequest(Uri tokenEndpoint, Version version) : base(version, MessageTransport.Direct, tokenEndpoint) { + + // We prefer URL encoding of the data. + this.Format = ResponseFormat.Form; + } + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshAccessTokenRequest"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal RefreshAccessTokenRequest(AuthorizationServerDescription authorizationServer) + : this(authorizationServer.TokenEndpoint, authorizationServer.Version) { } CodeOrTokenType ITokenCarryingRequest.CodeOrTokenType { diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs index 6dda224..dff59c7 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.OAuthWrap { using System.Security.Cryptography; using System.Text; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuthWrap.ChannelElements; using DotNetOpenAuth.OAuthWrap.Messages; public class WebAppAuthorizationServer : AuthorizationServerBase { @@ -103,17 +104,17 @@ namespace DotNetOpenAuth.OAuthWrap { return response; } - internal WebAppAccessTokenRequest ReadAccessTokenRequest(HttpRequestInfo requestInfo = null) { + internal IAccessTokenRequest ReadAccessTokenRequest(HttpRequestInfo requestInfo = null) { if (requestInfo == null) { requestInfo = this.Channel.GetRequestFromContext(); } - WebAppAccessTokenRequest request; + IAccessTokenRequest request; this.Channel.TryReadFromRequest(requestInfo, out request); return request; } - internal AccessTokenSuccessResponse PrepareAccessTokenResponse(WebAppAccessTokenRequest request, RSAParameters resourceServerPublicKey) { + internal AccessTokenSuccessResponse PrepareAccessTokenResponse(IAccessTokenRequest request, RSAParameters resourceServerPublicKey) { Contract.Requires<ArgumentNullException>(request != null, "request"); Contract.Ensures(Contract.Result<AccessTokenSuccessResponse>() != null); diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs index e351198..f3cb5bd 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs @@ -27,16 +27,6 @@ namespace DotNetOpenAuth.OAuthWrap { : base(authorizationServer) { } - /// <summary> - /// Gets or sets the identifier by which this client is known to the Authorization Server. - /// </summary> - public string ClientIdentifier { get; set; } - - /// <summary> - /// Gets or sets the client secret shared with the Authorization Server. - /// </summary> - public string ClientSecret { get; set; } - public IClientTokenManager TokenManager { get; set; } public WebAppRequest PrepareRequestUserAuthorization() { @@ -98,6 +88,7 @@ namespace DotNetOpenAuth.OAuthWrap { authorizationState.AccessTokenSecret = accessTokenSuccess.AccessTokenSecret; authorizationState.RefreshToken = accessTokenSuccess.RefreshToken; authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; + authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) { if (authorizationState.Scope != null) { Logger.Wrap.InfoFormat("Requested scope of \"{0}\" changed to \"{1}\" by authorization server.", |