Files
yavsc/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ServiceCredential.cs
2017-06-20 02:47:52 +02:00

235 lines
9.7 KiB
C#

/*
Copyright 2014 Google Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Http;
using Google.Apis.Logging;
using Google.Apis.Util;
namespace Google.Apis.Auth.OAuth2
{
/// <summary>
/// This type of Google OAuth 2.0 credential enables access to protected resources using an access token when
/// interacting server to server. For example, a service account credential could be used to access Google Cloud
/// Storage from a web application without a user's involvement.
/// <para>
/// <code>ServiceAccountCredential</code> inherits from this class in order to support Service Account. More
/// details available at: https://developers.google.com/accounts/docs/OAuth2ServiceAccount.
/// <see cref="Google.Apis.Auth.OAuth2.ComputeCredential"/> is another example for a class that inherits from this
/// class in order to support Compute credentials. For more information about Compute authentication, see:
/// https://cloud.google.com/compute/docs/authentication.
/// </para>
/// </summary>
public abstract class ServiceCredential : ICredential, IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler
{
/// <summary>Logger for this class</summary>
protected static readonly ILogger Logger = ApplicationContext.Logger.ForType<ServiceCredential>();
/// <summary>An initializer class for the service credential. </summary>
public class Initializer
{
/// <summary>Gets the token server URL.</summary>
public string TokenServerUrl { get; private set; }
/// <summary>
/// Gets or sets the clock used to refresh the token when it expires. The default value is
/// <see cref="Google.Apis.Util.SystemClock.Default"/>.
/// </summary>
public IClock Clock { get; set; }
/// <summary>
/// Gets or sets the method for presenting the access token to the resource server.
/// The default value is <see cref="BearerToken.AuthorizationHeaderAccessMethod"/>.
/// </summary>
public IAccessMethod AccessMethod { get; set; }
/// <summary>
/// Gets or sets the factory for creating a <see cref="System.Net.Http.HttpClient"/> instance.
/// </summary>
public IHttpClientFactory HttpClientFactory { get; set; }
/// <summary>
/// Get or sets the exponential back-off policy. Default value is <c>UnsuccessfulResponse503</c>, which
/// means that exponential back-off is used on 503 abnormal HTTP responses.
/// If the value is set to <c>None</c>, no exponential back-off policy is used, and it's up to the user to
/// configure the <see cref="Google.Apis.Http.ConfigurableMessageHandler"/> in an
/// <see cref="Google.Apis.Http.IConfigurableHttpClientInitializer"/> to set a specific back-off
/// implementation (using <see cref="Google.Apis.Http.BackOffHandler"/>).
/// </summary>
public ExponentialBackOffPolicy DefaultExponentialBackOffPolicy { get; set; }
/// <summary>Constructs a new initializer using the given token server URL.</summary>
public Initializer(string tokenServerUrl)
{
TokenServerUrl = tokenServerUrl;
AccessMethod = new BearerToken.AuthorizationHeaderAccessMethod();
Clock = SystemClock.Default;
DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.UnsuccessfulResponse503;
}
}
#region Readonly fields
private readonly string tokenServerUrl;
private readonly IClock clock;
private readonly IAccessMethod accessMethod;
private readonly ConfigurableHttpClient httpClient;
#endregion
/// <summary>Gets the token server URL.</summary>
public string TokenServerUrl { get { return tokenServerUrl; } }
/// <summary>Gets the clock used to refresh the token if it expires.</summary>
public IClock Clock { get { return clock; } }
/// <summary>Gets the method for presenting the access token to the resource server.</summary>
public IAccessMethod AccessMethod { get { return accessMethod; } }
/// <summary>Gets the HTTP client used to make authentication requests to the server.</summary>
public ConfigurableHttpClient HttpClient { get { return httpClient; } }
private TokenResponse token;
private object lockObject = new object();
/// <summary>Gets the token response which contains the access token.</summary>
public TokenResponse Token
{
get
{
lock (lockObject)
{
return token;
}
}
protected set
{
lock (lockObject)
{
token = value;
}
}
}
/// <summary>Constructs a new service account credential using the given initializer.</summary>
public ServiceCredential(Initializer initializer)
{
tokenServerUrl = initializer.TokenServerUrl;
accessMethod = initializer.AccessMethod.ThrowIfNull("initializer.AccessMethod");
clock = initializer.Clock.ThrowIfNull("initializer.Clock");
// Set the HTTP client.
var httpArgs = new CreateHttpClientArgs();
// Add exponential back-off initializer if necessary.
if (initializer.DefaultExponentialBackOffPolicy != ExponentialBackOffPolicy.None)
{
httpArgs.Initializers.Add(
new ExponentialBackOffInitializer(initializer.DefaultExponentialBackOffPolicy,
() => new BackOffHandler(new ExponentialBackOff())));
}
httpClient = (initializer.HttpClientFactory ?? new HttpClientFactory()).CreateHttpClient(httpArgs);
}
#region IConfigurableHttpClientInitializer
/// <inheritdoc/>
public void Initialize(ConfigurableHttpClient httpClient)
{
httpClient.MessageHandler.AddExecuteInterceptor(this);
httpClient.MessageHandler.AddUnsuccessfulResponseHandler(this);
}
#endregion
#region IHttpExecuteInterceptor implementation
/// <inheritdoc/>
public async Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var accessToken = await GetAccessTokenForRequestAsync(request.RequestUri.ToString(), cancellationToken)
.ConfigureAwait(false);
AccessMethod.Intercept(request, accessToken);
}
#endregion
#region IHttpUnsuccessfulResponseHandler
/// <summary>
/// Decorates unsuccessful responses, returns true if the response gets modified.
/// See IHttpUnsuccessfulResponseHandler for more information.
/// </summary>
public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args)
{
// If the response was unauthorized, request a new access token so that the original
// request can be retried.
// TODO(peleyal): check WWW-Authenticate header.
if (args.Response.StatusCode == HttpStatusCode.Unauthorized)
{
bool tokensEqual = false;
if (Token != null)
{
tokensEqual = Object.Equals(
Token.AccessToken, AccessMethod.GetAccessToken(args.Request));
}
return !tokensEqual
|| await RequestAccessTokenAsync(args.CancellationToken).ConfigureAwait(false);
}
return false;
}
#endregion
#region ITokenAccess implementation
/// <summary>
/// Gets an access token to authorize a request. If the existing token has expired, try to refresh it first.
/// <seealso cref="ITokenAccess.GetAccessTokenForRequestAsync"/>
/// </summary>
public virtual async Task<string> GetAccessTokenForRequestAsync(string authUri = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (Token == null || Token.IsExpired(Clock))
{
Logger.Debug("Token has expired, trying to get a new one.");
if (!await RequestAccessTokenAsync(cancellationToken).ConfigureAwait(false))
{
throw new InvalidOperationException("The access token has expired but we can't refresh it");
}
Logger.Info("New access token was received successfully");
}
return Token.AccessToken;
}
#endregion
/// <summary>Requests a new token.</summary>
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
/// <returns><c>true</c> if a new token was received successfully.</returns>
public abstract Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken);
}
}