summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/AuthorizationState.cs8
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ClientBase.cs85
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/IAuthorizationState.cs4
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/RefreshAccessTokenRequest.cs11
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/WebAppAuthorizationServer.cs7
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs11
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.",