//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth2 {
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
///
/// A base class for common OAuth Client behaviors.
///
public class ClientBase {
///
/// Initializes a new instance of the class.
///
/// The token issuer.
/// The client identifier.
/// The client secret.
protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) {
Requires.NotNull(authorizationServer, "authorizationServer");
this.AuthorizationServer = authorizationServer;
this.Channel = new OAuth2ClientChannel();
this.ClientIdentifier = clientIdentifier;
this.ClientSecret = clientSecret;
}
///
/// Gets the token issuer.
///
/// The token issuer.
public AuthorizationServerDescription AuthorizationServer { get; private set; }
///
/// Gets the OAuth channel.
///
/// The channel.
public Channel Channel { get; private set; }
///
/// Gets or sets the identifier by which this client is known to the Authorization Server.
///
public string ClientIdentifier { get; set; }
///
/// Gets or sets the client secret shared with the Authorization Server.
///
public string ClientSecret { get; set; }
///
/// Adds the necessary HTTP Authorization header to an HTTP request for protected resources
/// so that the Service Provider will allow the request through.
///
/// The request for protected resources from the service provider.
/// The access token previously obtained from the Authorization Server.
public static void AuthorizeRequest(HttpWebRequest request, string accessToken) {
Requires.NotNull(request, "request");
Requires.NotNullOrEmpty(accessToken, "accessToken");
OAuthUtilities.AuthorizeWithBearerToken(request, accessToken);
}
///
/// Adds the OAuth authorization token to an outgoing HTTP request, renewing a
/// (nearly) expired access token if necessary.
///
/// The request for protected resources from the service provider.
/// The authorization for this request previously obtained via OAuth.
public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) {
Requires.NotNull(request, "request");
Requires.NotNull(authorization, "authorization");
Requires.True(!string.IsNullOrEmpty(authorization.AccessToken), "authorization");
ErrorUtilities.VerifyProtocol(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc < DateTime.UtcNow || authorization.RefreshToken != null, "authorization has expired");
if (authorization.AccessTokenExpirationUtc.HasValue && authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) {
ErrorUtilities.VerifyProtocol(authorization.RefreshToken != null, "Access token has expired and cannot be automatically refreshed.");
this.RefreshAuthorization(authorization);
}
AuthorizeRequest(request, authorization.AccessToken);
}
///
/// Refreshes a short-lived access token using a longer-lived refresh token
/// with a new access token that has the same scope as the refresh token.
/// The refresh token itself may also be refreshed.
///
/// The authorization to update.
/// If given, the access token will not be refreshed if its remaining lifetime exceeds this value.
/// A value indicating whether the access token was actually renewed; true if it was renewed, or false if it still had useful life remaining.
///
/// This method may modify the value of the property on
/// the parameter if the authorization server has cycled out your refresh token.
/// If the parameter value was updated, this method calls on that instance.
///
public bool RefreshAuthorization(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null) {
Requires.NotNull(authorization, "authorization");
Requires.True(!string.IsNullOrEmpty(authorization.RefreshToken), "authorization");
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.OAuth.DebugFormat("Skipping token refresh step because access token's remaining life is {0}, which exceeds {1}.", usefulLifeRemaining, skipIfUsefulLifeExceeds.Value);
return false;
}
}
var request = new AccessTokenRefreshRequest(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
ClientSecret = this.ClientSecret,
RefreshToken = authorization.RefreshToken,
};
var response = this.Channel.Request(request);
UpdateAuthorizationWithResponse(authorization, response);
return true;
}
///
/// Gets an access token that may be used for only a subset of the scope for which a given
/// refresh token is authorized.
///
/// The refresh token.
/// The scope subset desired in the access token.
/// A description of the obtained access token, and possibly a new refresh token.
///
/// If the return value includes a new refresh token, the old refresh token should be discarded and
/// replaced with the new one.
///
public IAuthorizationState GetScopedAccessToken(string refreshToken, HashSet scope) {
Requires.NotNullOrEmpty(refreshToken, "refreshToken");
Requires.NotNull(scope, "scope");
Contract.Ensures(Contract.Result() != null);
var request = new AccessTokenRefreshRequest(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
ClientSecret = this.ClientSecret,
RefreshToken = refreshToken,
};
var response = this.Channel.Request(request);
var authorization = new AuthorizationState();
UpdateAuthorizationWithResponse(authorization, response);
return authorization;
}
///
/// Updates the authorization state maintained by the client with the content of an outgoing response.
///
/// The authorization state maintained by the client.
/// The access token containing response message.
internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, AccessTokenSuccessResponse accessTokenSuccess) {
Requires.NotNull(authorizationState, "authorizationState");
Requires.NotNull(accessTokenSuccess, "accessTokenSuccess");
authorizationState.AccessToken = accessTokenSuccess.AccessToken;
authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime;
authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow;
// The authorization server MAY choose to renew the refresh token itself.
if (accessTokenSuccess.RefreshToken != null) {
authorizationState.RefreshToken = accessTokenSuccess.RefreshToken;
}
// An included scope parameter in the response only describes the access token's scope.
// Don't update the whole authorization state object with that scope because that represents
// the refresh token's original scope.
if ((authorizationState.Scope == null || authorizationState.Scope.Count == 0) && accessTokenSuccess.Scope != null) {
authorizationState.Scope.ResetContents(accessTokenSuccess.Scope);
}
authorizationState.SaveChanges();
}
///
/// Updates the authorization state maintained by the client with the content of an outgoing response.
///
/// The authorization state maintained by the client.
/// The access token containing response message.
internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess) {
Requires.NotNull(authorizationState, "authorizationState");
Requires.NotNull(accessTokenSuccess, "accessTokenSuccess");
authorizationState.AccessToken = accessTokenSuccess.AccessToken;
authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime;
authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow;
if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) {
if (authorizationState.Scope != null) {
Logger.OAuth.InfoFormat(
"Requested scope of \"{0}\" changed to \"{1}\" by authorization server.",
authorizationState.Scope,
accessTokenSuccess.Scope);
}
authorizationState.Scope.ResetContents(accessTokenSuccess.Scope);
}
authorizationState.SaveChanges();
}
///
/// Updates authorization state with a success response from the Authorization Server.
///
/// The authorization state to update.
/// The authorization success message obtained from the authorization server.
internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) {
Requires.NotNull(authorizationState, "authorizationState");
Requires.NotNull(authorizationSuccess, "authorizationSuccess");
var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
ClientSecret = this.ClientSecret,
Callback = authorizationState.Callback,
AuthorizationCode = authorizationSuccess.AuthorizationCode,
};
IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest);
var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse;
var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse;
if (accessTokenSuccess != null) {
UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess);
} else {
authorizationState.Delete();
string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)";
ErrorUtilities.ThrowProtocol(OAuthStrings.CannotObtainAccessTokenWithReason, error);
}
}
///
/// Calculates the fraction of life remaining in an access token.
///
/// The authorization to measure.
/// A fractional number no greater than 1. Could be negative if the access token has already expired.
private static double ProportionalLifeRemaining(IAuthorizationState authorization) {
Requires.NotNull(authorization, "authorization");
Requires.True(authorization.AccessTokenIssueDateUtc.HasValue, "authorization");
Requires.True(authorization.AccessTokenExpirationUtc.HasValue, "authorization");
// 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;
}
}
}