Google Api
This commit is contained in:
parent
e307811d89
commit
83f9fc1bd8
123 changed files with 12961 additions and 60 deletions
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Logging;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe OAuth 2.0 authorization code flow for an installed application that persists end-user credentials.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Incremental authorization (https://developers.google.com/+/web/api/rest/oauth) is currently not supported
|
||||
/// for Installed Apps.
|
||||
/// </remarks>
|
||||
public class AuthorizationCodeInstalledApp : IAuthorizationCodeInstalledApp
|
||||
{
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<AuthorizationCodeInstalledApp>();
|
||||
|
||||
private readonly IAuthorizationCodeFlow flow;
|
||||
private readonly ICodeReceiver codeReceiver;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new authorization code installed application with the given flow and code receiver.
|
||||
/// </summary>
|
||||
public AuthorizationCodeInstalledApp(IAuthorizationCodeFlow flow, ICodeReceiver codeReceiver)
|
||||
{
|
||||
this.flow = flow;
|
||||
this.codeReceiver = codeReceiver;
|
||||
}
|
||||
|
||||
#region IAuthorizationCodeInstalledApp Members
|
||||
|
||||
/// <summary>Gets the authorization code flow.</summary>
|
||||
public IAuthorizationCodeFlow Flow
|
||||
{
|
||||
get { return flow; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the code receiver which is responsible for receiving the authorization code.</summary>
|
||||
public ICodeReceiver CodeReceiver
|
||||
{
|
||||
get { return codeReceiver; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<UserCredential> AuthorizeAsync(string userId, CancellationToken taskCancellationToken)
|
||||
{
|
||||
// Try to load a token from the data store.
|
||||
var token = await Flow.LoadTokenAsync(userId, taskCancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Check if a new authorization code is needed.
|
||||
if (ShouldRequestAuthorizationCode(token))
|
||||
{
|
||||
// Create an authorization code request.
|
||||
var redirectUri = CodeReceiver.RedirectUri;
|
||||
AuthorizationCodeRequestUrl codeRequest = Flow.CreateAuthorizationCodeRequest(redirectUri);
|
||||
|
||||
// Receive the code.
|
||||
var response = await CodeReceiver.ReceiveCodeAsync(codeRequest, taskCancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(response.Code))
|
||||
{
|
||||
var errorResponse = new TokenErrorResponse(response);
|
||||
Logger.Info("Received an error. The response is: {0}", errorResponse);
|
||||
throw new TokenResponseException(errorResponse);
|
||||
}
|
||||
|
||||
Logger.Debug("Received \"{0}\" code", response.Code);
|
||||
|
||||
// Get the token based on the code.
|
||||
token = await Flow.ExchangeCodeForTokenAsync(userId, response.Code, CodeReceiver.RedirectUri,
|
||||
taskCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new UserCredential(flow, userId, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the need for retrieval of a new authorization code, based on the given token and the
|
||||
/// authorization code flow.
|
||||
/// </summary>
|
||||
public bool ShouldRequestAuthorizationCode(TokenResponse token)
|
||||
{
|
||||
// TODO: This code should be shared between this class and AuthorizationCodeWebApp.
|
||||
// If the flow includes a parameter that requires a new token, if the stored token is null or it doesn't
|
||||
// have a refresh token and the access token is expired we need to retrieve a new authorization code.
|
||||
return Flow.ShouldForceTokenRetrieval() || token == null || (token.RefreshToken == null
|
||||
&& token.IsExpired(flow.Clock));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2013 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.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 helper for accessing protected resources using the Bearer token as specified in
|
||||
/// http://tools.ietf.org/html/rfc6750.
|
||||
/// </summary>
|
||||
public class BearerToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe OAuth 2.0 method for accessing protected resources using the Authorization header as specified
|
||||
/// in http://tools.ietf.org/html/rfc6750#section-2.1.
|
||||
/// </summary>
|
||||
public class AuthorizationHeaderAccessMethod : IAccessMethod
|
||||
{
|
||||
const string Schema = "Bearer";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Intercept(HttpRequestMessage request, string accessToken)
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(Schema, accessToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetAccessToken(HttpRequestMessage request)
|
||||
{
|
||||
if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme == Schema)
|
||||
{
|
||||
return request.Headers.Authorization.Parameter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe OAuth 2.0 method for accessing protected resources using an <c>access_token</c> query parameter
|
||||
/// as specified in http://tools.ietf.org/html/rfc6750#section-2.3.
|
||||
/// </summary>
|
||||
public class QueryParameterAccessMethod : IAccessMethod
|
||||
{
|
||||
const string AccessTokenKey = "access_token";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Intercept(HttpRequestMessage request, string accessToken)
|
||||
{
|
||||
var uri = request.RequestUri;
|
||||
request.RequestUri = new Uri(string.Format("{0}{1}{2}={3}",
|
||||
uri.ToString(), string.IsNullOrEmpty(uri.Query) ? "?" : "&", AccessTokenKey,
|
||||
Uri.EscapeDataString(accessToken)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetAccessToken(HttpRequestMessage request)
|
||||
{
|
||||
var query = request.RequestUri.Query;
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove the '?'.
|
||||
query = query.Substring(1);
|
||||
foreach (var parameter in query.Split('&'))
|
||||
{
|
||||
var keyValue = parameter.Split('=');
|
||||
if (keyValue[0].Equals(AccessTokenKey))
|
||||
{
|
||||
return keyValue[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>Client credential details for installed and web applications.</summary>
|
||||
public sealed class ClientSecrets
|
||||
{
|
||||
/// <summary>Gets or sets the client identifier.</summary>
|
||||
[Newtonsoft.Json.JsonProperty("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the client Secret.</summary>
|
||||
[Newtonsoft.Json.JsonProperty("client_secret")]
|
||||
public string ClientSecret { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
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.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Google OAuth 2.0 credential for accessing protected resources using an access token. The Google OAuth 2.0
|
||||
/// Authorization Server supports server-to-server interactions such as those between a web application and Google
|
||||
/// Cloud Storage. The requesting application has to prove its own identity to gain access to an API, and an
|
||||
/// end-user doesn't have to be involved.
|
||||
/// <para>
|
||||
/// More details about Compute Engine authentication is available at:
|
||||
/// https://cloud.google.com/compute/docs/authentication.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class ComputeCredential : ServiceCredential
|
||||
{
|
||||
/// <summary>The metadata server url.</summary>
|
||||
public const string MetadataServerUrl = "http://metadata.google.internal";
|
||||
|
||||
/// <summary>Caches result from first call to <c>IsRunningOnComputeEngine</c> </summary>
|
||||
private readonly static Lazy<Task<bool>> isRunningOnComputeEngineCached = new Lazy<Task<bool>>(
|
||||
() => IsRunningOnComputeEngineNoCache());
|
||||
|
||||
/// <summary>
|
||||
/// Experimentally, 200ms was found to be 99.9999% reliable.
|
||||
/// This is a conservative timeout to minimize hanging on some troublesome network.
|
||||
/// </summary>
|
||||
private const int MetadataServerPingTimeoutInMilliseconds = 1000;
|
||||
|
||||
/// <summary>The Metadata flavor header name.</summary>
|
||||
private const string MetadataFlavor = "Metadata-Flavor";
|
||||
|
||||
/// <summary>The Metadata header response indicating Google.</summary>
|
||||
private const string GoogleMetadataHeader = "Google";
|
||||
|
||||
private const string NotOnGceMessage = "Could not reach the Google Compute Engine metadata service. That is alright if this application is not running on GCE.";
|
||||
|
||||
/// <summary>
|
||||
/// An initializer class for the Compute credential. It uses <see cref="GoogleAuthConsts.ComputeTokenUrl"/>
|
||||
/// as the token server URL.
|
||||
/// </summary>
|
||||
new public class Initializer : ServiceCredential.Initializer
|
||||
{
|
||||
/// <summary>Constructs a new initializer using the default compute token URL.</summary>
|
||||
public Initializer()
|
||||
: this(GoogleAuthConsts.ComputeTokenUrl) {}
|
||||
|
||||
/// <summary>Constructs a new initializer using the given token URL.</summary>
|
||||
public Initializer(string tokenUrl)
|
||||
: base(tokenUrl) {}
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new Compute credential instance.</summary>
|
||||
public ComputeCredential() : this(new Initializer()) { }
|
||||
|
||||
/// <summary>Constructs a new Compute credential instance.</summary>
|
||||
public ComputeCredential(Initializer initializer) : base(initializer) { }
|
||||
|
||||
#region ServiceCredential overrides
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken)
|
||||
{
|
||||
// Create and send the HTTP request to compute server token URL.
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, TokenServerUrl);
|
||||
httpRequest.Headers.Add(MetadataFlavor, GoogleMetadataHeader);
|
||||
var response = await HttpClient.SendAsync(httpRequest, taskCancellationToken).ConfigureAwait(false);
|
||||
Token = await TokenResponse.FromHttpResponseAsync(response, Clock, Logger);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Detects if application is running on Google Compute Engine. This is achieved by attempting to contact
|
||||
/// GCE metadata server, that is only available on GCE. The check is only performed the first time you
|
||||
/// call this method, subsequent invocations used cached result of the first call.
|
||||
/// </summary>
|
||||
public static Task<bool> IsRunningOnComputeEngine()
|
||||
{
|
||||
return isRunningOnComputeEngineCached.Value;
|
||||
}
|
||||
|
||||
private static async Task<bool> IsRunningOnComputeEngineNoCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Info("Checking connectivity to ComputeEngine metadata server.");
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, MetadataServerUrl);
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(MetadataServerPingTimeoutInMilliseconds);
|
||||
|
||||
// Using the built-in HttpClient, as we want bare bones functionality without any retries.
|
||||
var httpClient = new HttpClient();
|
||||
var response = await httpClient.SendAsync(httpRequest, cts.Token).ConfigureAwait(false);
|
||||
|
||||
IEnumerable<string> headerValues = null;
|
||||
if (response.Headers.TryGetValues(MetadataFlavor, out headerValues))
|
||||
{
|
||||
foreach (var value in headerValues)
|
||||
{
|
||||
if (value == GoogleMetadataHeader)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Response came from another source, possibly a proxy server in the caller's network.
|
||||
Logger.Info("Response came from a source other than the Google Compute Engine metadata server.");
|
||||
return false;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
Logger.Debug(NotOnGceMessage);
|
||||
return false;
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
// On Mono, NameResolutionFailure is of System.Net.WebException.
|
||||
Logger.Debug(NotOnGceMessage);
|
||||
return false;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.Warning("Could not reach the Google Compute Engine metadata service. Operation timed out.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
Copyright 2015 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Json;
|
||||
using Google.Apis.Logging;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
// TODO(jtattermusch): look into getting rid of DefaultCredentialProvider and moving
|
||||
// the logic into GoogleCredential.
|
||||
|
||||
/// <summary>
|
||||
/// Provides the Application Default Credential from the environment.
|
||||
/// An instance of this class represents the per-process state used to get and cache
|
||||
/// the credential and allows overriding the state and environment for testing purposes.
|
||||
/// </summary>
|
||||
internal class DefaultCredentialProvider
|
||||
{
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<DefaultCredentialProvider>();
|
||||
|
||||
/// <summary>
|
||||
/// Environment variable override which stores the default application credentials file path.
|
||||
/// </summary>
|
||||
public const string CredentialEnvironmentVariable = "GOOGLE_APPLICATION_CREDENTIALS";
|
||||
|
||||
/// <summary>Well known file which stores the default application credentials.</summary>
|
||||
private const string WellKnownCredentialsFile = "application_default_credentials.json";
|
||||
|
||||
/// <summary>Environment variable which contains the Application Data settings.</summary>
|
||||
private const string AppdataEnvironmentVariable = "APPDATA";
|
||||
|
||||
/// <summary>Environment variable which contains the location of home directory on UNIX systems.</summary>
|
||||
private const string HomeEnvironmentVariable = "HOME";
|
||||
|
||||
/// <summary>GCloud configuration directory in Windows, relative to %APPDATA%.</summary>
|
||||
private const string CloudSDKConfigDirectoryWindows = "gcloud";
|
||||
|
||||
/// <summary>Help link to the application default credentials feature.</summary>
|
||||
private const string HelpPermalink =
|
||||
"https://developers.google.com/accounts/docs/application-default-credentials";
|
||||
|
||||
/// <summary>GCloud configuration directory on Linux/Mac, relative to $HOME.</summary>
|
||||
private static readonly string CloudSDKConfigDirectoryUnix = Path.Combine(".config", "gcloud");
|
||||
|
||||
/// <summary>Caches result from first call to <c>GetApplicationDefaultCredentialAsync</c> </summary>
|
||||
private readonly Lazy<Task<GoogleCredential>> cachedCredentialTask;
|
||||
|
||||
/// <summary>Constructs a new default credential provider.</summary>
|
||||
public DefaultCredentialProvider()
|
||||
{
|
||||
cachedCredentialTask = new Lazy<Task<GoogleCredential>>(CreateDefaultCredentialAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Application Default Credentials. Subsequent invocations return cached value from
|
||||
/// first invocation.
|
||||
/// See <see cref="M:Google.Apis.Auth.OAuth2.GoogleCredential.GetApplicationDefaultAsync"/> for details.
|
||||
/// </summary>
|
||||
public Task<GoogleCredential> GetDefaultCredentialAsync()
|
||||
{
|
||||
return cachedCredentialTask.Value;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new default credential.</summary>
|
||||
private async Task<GoogleCredential> CreateDefaultCredentialAsync()
|
||||
{
|
||||
// 1. First try the environment variable.
|
||||
string credentialPath = GetEnvironmentVariable(CredentialEnvironmentVariable);
|
||||
if (!String.IsNullOrWhiteSpace(credentialPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
return CreateDefaultCredentialFromFile(credentialPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Catching generic exception type because any corrupted file could manifest in different ways
|
||||
// including but not limited to the System, System.IO or from the Newtonsoft.Json namespace.
|
||||
throw new InvalidOperationException(
|
||||
String.Format("Error reading credential file from location {0}: {1}"
|
||||
+ "\nPlease check the value of the Environment Variable {2}",
|
||||
credentialPath,
|
||||
e.Message,
|
||||
CredentialEnvironmentVariable));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Then try the well known file.
|
||||
credentialPath = GetWellKnownCredentialFilePath();
|
||||
if (!String.IsNullOrWhiteSpace(credentialPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
return CreateDefaultCredentialFromFile(credentialPath);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// File is not present, eat the exception and move on to the next check.
|
||||
Logger.Debug("Well-known credential file {0} not found.", credentialPath);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
// Directory not present, eat the exception and move on to the next check.
|
||||
Logger.Debug("Well-known credential file {0} not found.", credentialPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
String.Format("Error reading credential file from location {0}: {1}"
|
||||
+ "\nPlease rerun 'gcloud auth login' to regenerate credentials file.",
|
||||
credentialPath,
|
||||
e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Then try the compute engine.
|
||||
Logger.Debug("Checking whether the application is running on ComputeEngine.");
|
||||
if (await ComputeCredential.IsRunningOnComputeEngine().ConfigureAwait(false))
|
||||
{
|
||||
Logger.Debug("ComputeEngine check passed. Using ComputeEngine Credentials.");
|
||||
return new GoogleCredential(new ComputeCredential());
|
||||
}
|
||||
|
||||
// If everything we tried has failed, throw an exception.
|
||||
throw new InvalidOperationException(
|
||||
String.Format("The Application Default Credentials are not available. They are available if running"
|
||||
+ " in Google Compute Engine. Otherwise, the environment variable {0} must be defined"
|
||||
+ " pointing to a file defining the credentials. See {1} for more information.",
|
||||
CredentialEnvironmentVariable,
|
||||
HelpPermalink));
|
||||
}
|
||||
|
||||
/// <summary>Creates a default credential from a JSON file.</summary>
|
||||
private GoogleCredential CreateDefaultCredentialFromFile(string credentialPath)
|
||||
{
|
||||
Logger.Debug("Loading Credential from file {0}", credentialPath);
|
||||
|
||||
using (Stream stream = GetStream(credentialPath))
|
||||
{
|
||||
return CreateDefaultCredentialFromStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a default credential from a stream that contains JSON credential data.</summary>
|
||||
internal GoogleCredential CreateDefaultCredentialFromStream(Stream stream)
|
||||
{
|
||||
JsonCredentialParameters credentialParameters;
|
||||
try
|
||||
{
|
||||
credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(stream);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Error deserializing JSON credential data.", e);
|
||||
}
|
||||
return CreateDefaultCredentialFromParameters(credentialParameters);
|
||||
}
|
||||
|
||||
/// <summary>Creates a default credential from a string that contains JSON credential data.</summary>
|
||||
internal GoogleCredential CreateDefaultCredentialFromJson(string json)
|
||||
{
|
||||
JsonCredentialParameters credentialParameters;
|
||||
try
|
||||
{
|
||||
credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Error deserializing JSON credential data.", e);
|
||||
}
|
||||
return CreateDefaultCredentialFromParameters(credentialParameters);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Creates a default credential from JSON data.</summary>
|
||||
private static GoogleCredential CreateDefaultCredentialFromParameters(JsonCredentialParameters credentialParameters)
|
||||
{
|
||||
switch (credentialParameters.Type)
|
||||
{
|
||||
case JsonCredentialParameters.AuthorizedUserCredentialType:
|
||||
return new GoogleCredential(CreateUserCredentialFromParameters(credentialParameters));
|
||||
case JsonCredentialParameters.ServiceAccountCredentialType:
|
||||
return GoogleCredential.FromCredential(
|
||||
CreateServiceAccountCredentialFromParameters(credentialParameters));
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
String.Format("Error creating credential from JSON. Unrecognized credential type {0}.",
|
||||
credentialParameters.Type));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a user credential from JSON data.</summary>
|
||||
private static UserCredential CreateUserCredentialFromParameters(JsonCredentialParameters credentialParameters)
|
||||
{
|
||||
if (credentialParameters.Type != JsonCredentialParameters.AuthorizedUserCredentialType ||
|
||||
string.IsNullOrEmpty(credentialParameters.ClientId) ||
|
||||
string.IsNullOrEmpty(credentialParameters.ClientSecret))
|
||||
{
|
||||
throw new InvalidOperationException("JSON data does not represent a valid user credential.");
|
||||
}
|
||||
|
||||
var token = new TokenResponse
|
||||
{
|
||||
RefreshToken = credentialParameters.RefreshToken
|
||||
};
|
||||
|
||||
var initializer = new GoogleAuthorizationCodeFlow.Initializer
|
||||
{
|
||||
ClientSecrets = new ClientSecrets
|
||||
{
|
||||
ClientId = credentialParameters.ClientId,
|
||||
ClientSecret = credentialParameters.ClientSecret
|
||||
}
|
||||
};
|
||||
var flow = new GoogleAuthorizationCodeFlow(initializer);
|
||||
return new UserCredential(flow, "ApplicationDefaultCredentials", token);
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="ServiceAccountCredential"/> from JSON data.</summary>
|
||||
private static ServiceAccountCredential CreateServiceAccountCredentialFromParameters(
|
||||
JsonCredentialParameters credentialParameters)
|
||||
{
|
||||
if (credentialParameters.Type != JsonCredentialParameters.ServiceAccountCredentialType ||
|
||||
string.IsNullOrEmpty(credentialParameters.ClientEmail) ||
|
||||
string.IsNullOrEmpty(credentialParameters.PrivateKey))
|
||||
{
|
||||
throw new InvalidOperationException("JSON data does not represent a valid service account credential.");
|
||||
}
|
||||
var initializer = new ServiceAccountCredential.Initializer(credentialParameters.ClientEmail);
|
||||
return new ServiceAccountCredential(initializer.FromPrivateKey(credentialParameters.PrivateKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns platform-specific well known credential file path. This file is created by
|
||||
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">gcloud auth login</a>
|
||||
/// </summary>
|
||||
private string GetWellKnownCredentialFilePath()
|
||||
{
|
||||
var appData = GetEnvironmentVariable(AppdataEnvironmentVariable);
|
||||
if (appData != null) {
|
||||
return Path.Combine(appData, CloudSDKConfigDirectoryWindows, WellKnownCredentialsFile);
|
||||
}
|
||||
var unixHome = GetEnvironmentVariable(HomeEnvironmentVariable);
|
||||
if (unixHome != null)
|
||||
{
|
||||
return Path.Combine(unixHome, CloudSDKConfigDirectoryUnix, WellKnownCredentialsFile);
|
||||
}
|
||||
return Path.Combine(CloudSDKConfigDirectoryWindows, WellKnownCredentialsFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the environment variable.
|
||||
/// This method is protected so it could be overriden for testing purposes only.
|
||||
/// </summary>
|
||||
protected virtual string GetEnvironmentVariable(string variableName)
|
||||
{
|
||||
return Environment.GetEnvironmentVariable(variableName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens file as a stream.
|
||||
/// This method is protected so it could be overriden for testing purposes only.
|
||||
/// </summary>
|
||||
protected virtual Stream GetStream(string filePath)
|
||||
{
|
||||
return new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
Copyright 2013 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Http;
|
||||
using Google.Apis.Logging;
|
||||
using Google.Apis.Util;
|
||||
using Google.Apis.Util.Store;
|
||||
using Google.Apis.Testing;
|
||||
using System.Net;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Flows
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe OAuth 2.0 authorization code flow that manages and persists end-user credentials.
|
||||
/// <para>
|
||||
/// This is designed to simplify the flow in which an end-user authorizes the application to access their protected
|
||||
/// data, and then the application has access to their data based on an access token and a refresh token to refresh
|
||||
/// that access token when it expires.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class AuthorizationCodeFlow : IAuthorizationCodeFlow
|
||||
{
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<AuthorizationCodeFlow>();
|
||||
|
||||
#region Initializer
|
||||
|
||||
/// <summary>An initializer class for the authorization code flow. </summary>
|
||||
public class Initializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the method for presenting the access token to the resource server.
|
||||
/// The default value is
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.BearerToken.AuthorizationHeaderAccessMethod"/>.
|
||||
/// </summary>
|
||||
public IAccessMethod AccessMethod { get; set; }
|
||||
|
||||
/// <summary>Gets the token server URL.</summary>
|
||||
public string TokenServerUrl { get; private set; }
|
||||
|
||||
/// <summary>Gets or sets the authorization server URL.</summary>
|
||||
public string AuthorizationServerUrl { get; private set; }
|
||||
|
||||
/// <summary>Gets or sets the client secrets which includes the client identifier and its secret.</summary>
|
||||
public ClientSecrets ClientSecrets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the client secrets stream which contains the client identifier and its secret.
|
||||
/// </summary>
|
||||
/// <remarks>The AuthorizationCodeFlow constructor is responsible for disposing the stream.</remarks>
|
||||
public Stream ClientSecretsStream { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the data store used to store the token response.</summary>
|
||||
public IDataStore DataStore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scopes which indicate the API access your application is requesting.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Scopes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the factory for creating <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 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>
|
||||
/// Gets or sets the clock. The clock is used to determine if the token has expired, if so we will try to
|
||||
/// refresh it. The default value is <see cref="Google.Apis.Util.SystemClock.Default"/>.
|
||||
/// </summary>
|
||||
public IClock Clock { get; set; }
|
||||
|
||||
/// <summary>Constructs a new initializer.</summary>
|
||||
/// <param name="authorizationServerUrl">Authorization server URL</param>
|
||||
/// <param name="tokenServerUrl">Token server URL</param>
|
||||
public Initializer(string authorizationServerUrl, string tokenServerUrl)
|
||||
{
|
||||
AuthorizationServerUrl = authorizationServerUrl;
|
||||
TokenServerUrl = tokenServerUrl;
|
||||
|
||||
Scopes = new List<string>();
|
||||
AccessMethod = new BearerToken.AuthorizationHeaderAccessMethod();
|
||||
DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.UnsuccessfulResponse503;
|
||||
Clock = SystemClock.Default;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Readonly fields
|
||||
|
||||
private readonly IAccessMethod accessMethod;
|
||||
private readonly string tokenServerUrl;
|
||||
private readonly string authorizationServerUrl;
|
||||
private readonly ClientSecrets clientSecrets;
|
||||
private readonly IDataStore dataStore;
|
||||
private readonly IEnumerable<string> scopes;
|
||||
private readonly ConfigurableHttpClient httpClient;
|
||||
private readonly IClock clock;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>Gets the token server URL.</summary>
|
||||
public string TokenServerUrl { get { return tokenServerUrl; } }
|
||||
|
||||
/// <summary>Gets the authorization code server URL.</summary>
|
||||
public string AuthorizationServerUrl { get { return authorizationServerUrl; } }
|
||||
|
||||
/// <summary>Gets the client secrets which includes the client identifier and its secret.</summary>
|
||||
public ClientSecrets ClientSecrets { get { return clientSecrets; } }
|
||||
|
||||
/// <summary>Gets the data store used to store the credentials.</summary>
|
||||
public IDataStore DataStore { get { return dataStore; } }
|
||||
|
||||
/// <summary>Gets the scopes which indicate the API access your application is requesting.</summary>
|
||||
public IEnumerable<string> Scopes { get { return scopes; } }
|
||||
|
||||
/// <summary>Gets the HTTP client used to make authentication requests to the server.</summary>
|
||||
public ConfigurableHttpClient HttpClient { get { return httpClient; } }
|
||||
|
||||
/// <summary>Constructs a new flow using the initializer's properties.</summary>
|
||||
public AuthorizationCodeFlow(Initializer initializer)
|
||||
{
|
||||
clientSecrets = initializer.ClientSecrets;
|
||||
if (clientSecrets == null)
|
||||
{
|
||||
if (initializer.ClientSecretsStream == null)
|
||||
{
|
||||
throw new ArgumentException("You MUST set ClientSecret or ClientSecretStream on the initializer");
|
||||
}
|
||||
|
||||
using (initializer.ClientSecretsStream)
|
||||
{
|
||||
clientSecrets = GoogleClientSecrets.Load(initializer.ClientSecretsStream).Secrets;
|
||||
}
|
||||
}
|
||||
else if (initializer.ClientSecretsStream != null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"You CAN'T set both ClientSecrets AND ClientSecretStream on the initializer");
|
||||
}
|
||||
|
||||
accessMethod = initializer.AccessMethod.ThrowIfNull("Initializer.AccessMethod");
|
||||
clock = initializer.Clock.ThrowIfNull("Initializer.Clock");
|
||||
tokenServerUrl = initializer.TokenServerUrl.ThrowIfNullOrEmpty("Initializer.TokenServerUrl");
|
||||
authorizationServerUrl = initializer.AuthorizationServerUrl.ThrowIfNullOrEmpty
|
||||
("Initializer.AuthorizationServerUrl");
|
||||
|
||||
dataStore = initializer.DataStore;
|
||||
if (dataStore == null)
|
||||
{
|
||||
Logger.Warning("Datastore is null, as a result the user's credential will not be stored");
|
||||
}
|
||||
scopes = initializer.Scopes;
|
||||
|
||||
// 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 IAuthorizationCodeFlow overrides
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAccessMethod AccessMethod { get { return accessMethod; } }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IClock Clock { get { return clock; } }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TokenResponse> LoadTokenAsync(string userId, CancellationToken taskCancellationToken)
|
||||
{
|
||||
taskCancellationToken.ThrowIfCancellationRequested();
|
||||
if (DataStore == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await DataStore.GetAsync<TokenResponse>(userId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task DeleteTokenAsync(string userId, CancellationToken taskCancellationToken)
|
||||
{
|
||||
taskCancellationToken.ThrowIfCancellationRequested();
|
||||
if (DataStore != null)
|
||||
{
|
||||
await DataStore.DeleteAsync<TokenResponse>(userId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
|
||||
{
|
||||
return new AuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
|
||||
{
|
||||
ClientId = ClientSecrets.ClientId,
|
||||
Scope = string.Join(" ", Scopes),
|
||||
RedirectUri = redirectUri
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TokenResponse> ExchangeCodeForTokenAsync(string userId, string code, string redirectUri,
|
||||
CancellationToken taskCancellationToken)
|
||||
{
|
||||
var authorizationCodeTokenReq = new AuthorizationCodeTokenRequest
|
||||
{
|
||||
Scope = string.Join(" ", Scopes),
|
||||
RedirectUri = redirectUri,
|
||||
Code = code,
|
||||
};
|
||||
|
||||
var token = await FetchTokenAsync(userId, authorizationCodeTokenReq, taskCancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await StoreTokenAsync(userId, token, taskCancellationToken).ConfigureAwait(false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TokenResponse> RefreshTokenAsync(string userId, string refreshToken,
|
||||
CancellationToken taskCancellationToken)
|
||||
{
|
||||
var refreshTokenReq = new RefreshTokenRequest
|
||||
{
|
||||
RefreshToken = refreshToken,
|
||||
};
|
||||
var token = await FetchTokenAsync(userId, refreshTokenReq, taskCancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The new token may not contain a refresh token, so set it with the given refresh token.
|
||||
if (token.RefreshToken == null)
|
||||
{
|
||||
token.RefreshToken = refreshToken;
|
||||
}
|
||||
|
||||
await StoreTokenAsync(userId, token, taskCancellationToken).ConfigureAwait(false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Task RevokeTokenAsync(string userId, string token, CancellationToken taskCancellationToken)
|
||||
{
|
||||
throw new NotImplementedException("The OAuth 2.0 protocol does not support token revocation.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool ShouldForceTokenRetrieval() { return false; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>Stores the token in the <see cref="DataStore"/>.</summary>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="token">Token to store.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
private async Task StoreTokenAsync(string userId, TokenResponse token, CancellationToken taskCancellationToken)
|
||||
{
|
||||
taskCancellationToken.ThrowIfCancellationRequested();
|
||||
if (DataStore != null)
|
||||
{
|
||||
await DataStore.StoreAsync<TokenResponse>(userId, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Retrieve a new token from the server using the specified request.</summary>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="request">Token request.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
/// <returns>Token response with the new access token.</returns>
|
||||
[VisibleForTestOnly]
|
||||
public async Task<TokenResponse> FetchTokenAsync(string userId, TokenRequest request,
|
||||
CancellationToken taskCancellationToken)
|
||||
{
|
||||
// Add client id and client secret to requests.
|
||||
request.ClientId = ClientSecrets.ClientId;
|
||||
request.ClientSecret = ClientSecrets.ClientSecret;
|
||||
|
||||
try
|
||||
{
|
||||
var tokenResponse = await request.ExecuteAsync
|
||||
(httpClient, TokenServerUrl, taskCancellationToken, Clock).ConfigureAwait(false);
|
||||
return tokenResponse;
|
||||
}
|
||||
catch (TokenResponseException ex)
|
||||
{
|
||||
// In case there is an exception during getting the token, we delete any user's token information from
|
||||
// the data store if it's not a server-side error.
|
||||
int statusCode = (int)(ex.StatusCode ?? (HttpStatusCode)0);
|
||||
bool serverError = statusCode >= 500 && statusCode < 600;
|
||||
if (!serverError)
|
||||
{
|
||||
// If not a server error, then delete the user token information.
|
||||
// This is to guard against suspicious client-side behaviour.
|
||||
await DeleteTokenAsync(userId, taskCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (HttpClient != null)
|
||||
{
|
||||
HttpClient.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
Copyright 2013 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.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Json;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Flows
|
||||
{
|
||||
/// <summary>
|
||||
/// Google specific authorization code flow which inherits from <see cref="AuthorizationCodeFlow"/>.
|
||||
/// </summary>
|
||||
public class GoogleAuthorizationCodeFlow : AuthorizationCodeFlow
|
||||
{
|
||||
private readonly string revokeTokenUrl;
|
||||
|
||||
/// <summary>Gets the token revocation URL.</summary>
|
||||
public string RevokeTokenUrl { get { return revokeTokenUrl; } }
|
||||
|
||||
/// <summary>Gets or sets the include granted scopes indicator.
|
||||
/// Do not use, use <see cref="IncludeGrantedScopes"/> instead.</summary>
|
||||
public readonly bool? includeGrantedScopes;
|
||||
|
||||
/// <summary>Gets or sets the include granted scopes indicator.</summary>
|
||||
public bool? IncludeGrantedScopes { get { return includeGrantedScopes; } }
|
||||
|
||||
private readonly IEnumerable<KeyValuePair<string, string>> userDefinedQueryParams;
|
||||
|
||||
/// <summary>Gets the user defined query parameters.</summary>
|
||||
public IEnumerable<KeyValuePair<string, string>> UserDefinedQueryParams
|
||||
{
|
||||
get { return userDefinedQueryParams; }
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new Google authorization code flow.</summary>
|
||||
public GoogleAuthorizationCodeFlow(Initializer initializer)
|
||||
: base(initializer)
|
||||
{
|
||||
revokeTokenUrl = initializer.RevokeTokenUrl;
|
||||
includeGrantedScopes = initializer.IncludeGrantedScopes;
|
||||
userDefinedQueryParams = initializer.UserDefinedQueryParams;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
|
||||
{
|
||||
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
|
||||
{
|
||||
ClientId = ClientSecrets.ClientId,
|
||||
Scope = string.Join(" ", Scopes),
|
||||
RedirectUri = redirectUri,
|
||||
IncludeGrantedScopes = IncludeGrantedScopes.HasValue
|
||||
? IncludeGrantedScopes.Value.ToString().ToLower() : null,
|
||||
UserDefinedQueryParams = UserDefinedQueryParams
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task RevokeTokenAsync(string userId, string token,
|
||||
CancellationToken taskCancellationToken)
|
||||
{
|
||||
GoogleRevokeTokenRequest request = new GoogleRevokeTokenRequest(new Uri(RevokeTokenUrl))
|
||||
{
|
||||
Token = token
|
||||
};
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, request.Build());
|
||||
|
||||
var response = await HttpClient.SendAsync(httpRequest, taskCancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var error = NewtonsoftJsonSerializer.Instance.Deserialize<TokenErrorResponse>(content);
|
||||
throw new TokenResponseException(error, response.StatusCode);
|
||||
}
|
||||
|
||||
await DeleteTokenAsync(userId, taskCancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ShouldForceTokenRetrieval()
|
||||
{
|
||||
return IncludeGrantedScopes.HasValue && IncludeGrantedScopes.Value;
|
||||
}
|
||||
|
||||
/// <summary>An initializer class for Google authorization code flow. </summary>
|
||||
public new class Initializer : AuthorizationCodeFlow.Initializer
|
||||
{
|
||||
/// <summary>Gets or sets the token revocation URL.</summary>
|
||||
public string RevokeTokenUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the optional indicator for including granted scopes for incremental authorization.
|
||||
/// </summary>
|
||||
public bool? IncludeGrantedScopes { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the optional user defined query parameters.</summary>
|
||||
public IEnumerable<KeyValuePair<string, string>> UserDefinedQueryParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new initializer. Sets Authorization server URL to
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.GoogleAuthConsts.OidcAuthorizationUrl"/>, and Token server URL to
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.GoogleAuthConsts.OidcTokenUrl"/>.
|
||||
/// </summary>
|
||||
public Initializer() : this(
|
||||
GoogleAuthConsts.OidcAuthorizationUrl, GoogleAuthConsts.OidcTokenUrl, GoogleAuthConsts.RevokeTokenUrl)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new initializer.</summary>
|
||||
/// <param name="authorizationServerUrl">Authorization server URL</param>
|
||||
/// <param name="tokenServerUrl">Token server URL</param>
|
||||
/// <param name="revokeTokenUrl">Revocation server URL</param>
|
||||
/// <remarks>
|
||||
/// This is mainly for internal testing at Google, where we occasionally need
|
||||
/// to use alternative oauth endpoints. This is not for general use.
|
||||
/// </remarks>
|
||||
protected Initializer(string authorizationServerUrl, string tokenServerUrl, string revokeTokenUrl)
|
||||
: base(authorizationServerUrl, tokenServerUrl)
|
||||
{
|
||||
RevokeTokenUrl = revokeTokenUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Util;
|
||||
using Google.Apis.Util.Store;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Flows
|
||||
{
|
||||
/// <summary>OAuth 2.0 authorization code flow that manages and persists end-user credentials.</summary>
|
||||
public interface IAuthorizationCodeFlow : IDisposable
|
||||
{
|
||||
/// <summary>Gets the method for presenting the access token to the resource server.</summary>
|
||||
IAccessMethod AccessMethod { get; }
|
||||
|
||||
/// <summary>Gets the clock.</summary>
|
||||
IClock Clock { get; }
|
||||
|
||||
/// <summary>Gets the data store used to store the credentials.</summary>
|
||||
IDataStore DataStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously loads the user's token using the flow's
|
||||
/// <see cref="Google.Apis.Util.Store.IDataStore"/>.
|
||||
/// </summary>
|
||||
/// <param name="userId">User identifier</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation</param>
|
||||
/// <returns>Token response</returns>
|
||||
Task<TokenResponse> LoadTokenAsync(string userId, CancellationToken taskCancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes the user's token using the flow's
|
||||
/// <see cref="Google.Apis.Util.Store.IDataStore"/>.
|
||||
/// </summary>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
Task DeleteTokenAsync(string userId, CancellationToken taskCancellationToken);
|
||||
|
||||
/// <summary>Creates an authorization code request with the specified redirect URI.</summary>
|
||||
AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri);
|
||||
|
||||
/// <summary>Asynchronously exchanges code with a token.</summary>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="code">Authorization code received from the authorization server.</param>
|
||||
/// <param name="redirectUri">Redirect URI which is used in the token request.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
/// <returns>Token response which contains the access token.</returns>
|
||||
Task<TokenResponse> ExchangeCodeForTokenAsync(string userId, string code, string redirectUri,
|
||||
CancellationToken taskCancellationToken);
|
||||
|
||||
/// <summary>Asynchronously refreshes an access token using a refresh token.</summary>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="refreshToken">Refresh token which is used to get a new access token.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
/// <returns>Token response which contains the access token and the input refresh token.</returns>
|
||||
Task<TokenResponse> RefreshTokenAsync(string userId, string refreshToken,
|
||||
CancellationToken taskCancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously revokes the specified token. This method disconnects the user's account from the OAuth 2.0
|
||||
/// application. It should be called upon removing the user account from the site.</summary>
|
||||
/// <remarks>
|
||||
/// If revoking the token succeeds, the user's credential is removed from the data store and the user MUST
|
||||
/// authorize the application again before the application can access the user's private resources.
|
||||
/// </remarks>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="token">Access token to be revoked.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
/// <returns><c>true</c> if the token was revoked successfully.</returns>
|
||||
Task RevokeTokenAsync(string userId, string token, CancellationToken taskCancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if a new token needs to be retrieved and stored regardless of normal circumstances.
|
||||
/// </summary>
|
||||
bool ShouldForceTokenRetrieval();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Google OAuth2 constants.
|
||||
/// Canonical source for these URLs is: https://accounts.google.com/.well-known/openid-configuration
|
||||
/// </summary>
|
||||
public static class GoogleAuthConsts
|
||||
{
|
||||
/// <summary>The authorization code server URL.</summary>
|
||||
public const string AuthorizationUrl = "https://accounts.google.com/o/oauth2/auth";
|
||||
|
||||
/// <summary>The OpenID Connect authorization code server URL.</summary>
|
||||
/// <remarks>
|
||||
/// Use of this <see cref="OidcAuthorizationUrl"/> is not 100% compatible with using
|
||||
/// <see cref="AuthorizationUrl"/>, so they are two distinct URLs.
|
||||
/// Internally within this library only this more up-to-date <see cref="OidcAuthorizationUrl"/> is used.
|
||||
/// </remarks>
|
||||
public const string OidcAuthorizationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
|
||||
|
||||
/// <summary>The approval URL (used in the Windows solution as a callback).</summary>
|
||||
public const string ApprovalUrl = "https://accounts.google.com/o/oauth2/approval";
|
||||
|
||||
/// <summary>The authorization token server URL.</summary>
|
||||
public const string TokenUrl = "https://accounts.google.com/o/oauth2/token";
|
||||
|
||||
/// <summary>The OpenID Connect authorization token server URL.</summary>
|
||||
/// <remarks>
|
||||
/// Use of this <see cref="OidcTokenUrl"/> is not 100% compatible with using
|
||||
/// <see cref="TokenUrl"/>, so they are two distinct URLs.
|
||||
/// Internally within this library only this more up-to-date <see cref="OidcTokenUrl"/> is used.
|
||||
/// </remarks>
|
||||
public const string OidcTokenUrl = "https://www.googleapis.com/oauth2/v4/token";
|
||||
|
||||
/// <summary>The Compute Engine authorization token server URL</summary>
|
||||
public const string ComputeTokenUrl =
|
||||
"http://metadata/computeMetadata/v1/instance/service-accounts/default/token";
|
||||
|
||||
/// <summary>The path to the Google revocation endpoint.</summary>
|
||||
public const string RevokeTokenUrl = "https://accounts.google.com/o/oauth2/revoke";
|
||||
|
||||
/// <summary>The OpenID Connect Json Web Key Set (jwks) URL.</summary>
|
||||
public const string JsonWebKeySetUrl = "https://www.googleapis.com/oauth2/v3/certs";
|
||||
|
||||
/// <summary>Installed application redirect URI.</summary>
|
||||
public const string InstalledAppRedirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
||||
|
||||
/// <summary>Installed application localhost redirect URI.</summary>
|
||||
public const string LocalhostRedirectUri = "http://localhost";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2013 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.IO;
|
||||
|
||||
using Google.Apis.Json;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 client secrets model as specified in https://cloud.google.com/console/.
|
||||
/// </summary>
|
||||
public sealed class GoogleClientSecrets
|
||||
{
|
||||
/// <summary>Gets or sets the details for installed applications.</summary>
|
||||
[Newtonsoft.Json.JsonProperty("installed")]
|
||||
private ClientSecrets Installed { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the details for web applications.</summary>
|
||||
[Newtonsoft.Json.JsonProperty("web")]
|
||||
private ClientSecrets Web { get; set; }
|
||||
|
||||
/// <summary>Gets the client secrets which contains the client identifier and client secret. </summary>
|
||||
public ClientSecrets Secrets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Installed == null && Web == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"At least one client secrets (Installed or Web) should be set");
|
||||
}
|
||||
return Installed ?? Web;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Loads the Google client secret from the input stream.</summary>
|
||||
public static GoogleClientSecrets Load(Stream stream)
|
||||
{
|
||||
return NewtonsoftJsonSerializer.Instance.Deserialize<GoogleClientSecrets>(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
Copyright 2015 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Http;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Credential for authorizing calls using OAuth 2.0.
|
||||
/// It is a convenience wrapper that allows handling of different types of
|
||||
/// credentials (like <see cref="ServiceAccountCredential"/>, <see cref="ComputeCredential"/>
|
||||
/// or <see cref="UserCredential"/>) in a unified way.
|
||||
/// <para>
|
||||
/// See <see cref="GetApplicationDefaultAsync"/> for the credential retrieval logic.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class GoogleCredential : ICredential
|
||||
{
|
||||
/// <summary>Provider implements the logic for creating the application default credential.</summary>
|
||||
private static DefaultCredentialProvider defaultCredentialProvider = new DefaultCredentialProvider();
|
||||
|
||||
/// <summary>The underlying credential being wrapped by this object.</summary>
|
||||
protected readonly ICredential credential;
|
||||
|
||||
/// <summary>Creates a new <c>GoogleCredential</c>.</summary>
|
||||
internal GoogleCredential(ICredential credential)
|
||||
{
|
||||
this.credential = credential;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Returns the Application Default Credentials which are ambient credentials that identify and authorize
|
||||
/// the whole application.</para>
|
||||
/// <para>The ambient credentials are determined as following order:</para>
|
||||
/// <list type="number">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// The environment variable GOOGLE_APPLICATION_CREDENTIALS is checked. If this variable is specified, it
|
||||
/// should point to a file that defines the credentials. The simplest way to get a credential for this purpose
|
||||
/// is to create a service account using the
|
||||
/// <a href="https://console.developers.google.com">Google Developers Console</a> in the section APIs &
|
||||
/// Auth, in the sub-section Credentials. Create a service account or choose an existing one and select
|
||||
/// Generate new JSON key. Set the environment variable to the path of the JSON file downloaded.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If you have installed the Google Cloud SDK on your machine and have run the command
|
||||
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">GCloud Auth Login</a>, your identity can
|
||||
/// be used as a proxy to test code calling APIs from that machine.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If you are running in Google Compute Engine production, the built-in service account associated with the
|
||||
/// virtual machine instance will be used.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// If all previous steps have failed, <c>InvalidOperationException</c> is thrown.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <returns>A task which completes with the application default credentials.</returns>
|
||||
public static Task<GoogleCredential> GetApplicationDefaultAsync()
|
||||
{
|
||||
return defaultCredentialProvider.GetDefaultCredentialAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Synchronously returns the Application Default Credentials which are ambient credentials that identify and authorize
|
||||
/// the whole application. See <see cref="GetApplicationDefaultAsync"/> for details on application default credentials.</para>
|
||||
/// <para>This method will block until the credentials are available (or an exception is thrown).
|
||||
/// It is highly preferable to call <see cref="GetApplicationDefaultAsync"/> where possible.</para>
|
||||
/// </summary>
|
||||
/// <returns>The application default credentials.</returns>
|
||||
public static GoogleCredential GetApplicationDefault() => Task.Run(() => GetApplicationDefaultAsync()).Result;
|
||||
|
||||
/// <summary>
|
||||
/// Loads credential from stream containing JSON credential data.
|
||||
/// <para>
|
||||
/// The stream can contain a Service Account key file in JSON format from the Google Developers
|
||||
/// Console or a stored user credential using the format supported by the Cloud SDK.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static GoogleCredential FromStream(Stream stream)
|
||||
{
|
||||
return defaultCredentialProvider.CreateDefaultCredentialFromStream(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads credential from a string containing JSON credential data.
|
||||
/// <para>
|
||||
/// The string can contain a Service Account key file in JSON format from the Google Developers
|
||||
/// Console or a stored user credential using the format supported by the Cloud SDK.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static GoogleCredential FromJson(string json)
|
||||
{
|
||||
return defaultCredentialProvider.CreateDefaultCredentialFromJson(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Returns <c>true</c> only if this credential type has no scopes by default and requires
|
||||
/// a call to <see cref="o:CreateScoped"/> before use.</para>
|
||||
///
|
||||
/// <para>Credentials need to have scopes in them before they can be used to access Google services.
|
||||
/// Some Credential types have scopes built-in, and some don't. This property indicates whether
|
||||
/// the Credential type has scopes built-in.</para>
|
||||
///
|
||||
/// <list type="number">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <see cref="ComputeCredential"/> has scopes built-in. Nothing additional is required.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <see cref="UserCredential"/> has scopes built-in, as they were obtained during the consent
|
||||
/// screen. Nothing additional is required.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <see cref="ServiceAccountCredential"/> does not have scopes built-in by default. Caller should
|
||||
/// invoke <see cref="o:CreateScoped"/> to add scopes to the credential.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public virtual bool IsCreateScopedRequired
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the credential supports scopes, creates a copy with the specified scopes. Otherwise, it returns the same
|
||||
/// instance.
|
||||
/// </summary>
|
||||
public virtual GoogleCredential CreateScoped(IEnumerable<string> scopes)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the credential supports scopes, creates a copy with the specified scopes. Otherwise, it returns the same
|
||||
/// instance.
|
||||
/// </summary>
|
||||
public GoogleCredential CreateScoped(params string[] scopes)
|
||||
{
|
||||
return CreateScoped((IEnumerable<string>) scopes);
|
||||
}
|
||||
|
||||
void IConfigurableHttpClientInitializer.Initialize(ConfigurableHttpClient httpClient)
|
||||
{
|
||||
credential.Initialize(httpClient);
|
||||
}
|
||||
|
||||
Task<string> ITokenAccess.GetAccessTokenForRequestAsync(string authUri, CancellationToken cancellationToken)
|
||||
{
|
||||
return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying credential instance being wrapped.
|
||||
/// </summary>
|
||||
public ICredential UnderlyingCredential => credential;
|
||||
|
||||
/// <summary>Creates a <c>GoogleCredential</c> wrapping a <see cref="ServiceAccountCredential"/>.</summary>
|
||||
internal static GoogleCredential FromCredential(ServiceAccountCredential credential)
|
||||
{
|
||||
return new ServiceAccountGoogleCredential(credential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <c>ServiceAccountCredential</c> as <c>GoogleCredential</c>.
|
||||
/// We need this subclass because wrapping <c>ServiceAccountCredential</c> (unlike other wrapped credential
|
||||
/// types) requires special handling for <c>IsCreateScopedRequired</c> and <c>CreateScoped</c> members.
|
||||
/// </summary>
|
||||
internal class ServiceAccountGoogleCredential : GoogleCredential
|
||||
{
|
||||
public ServiceAccountGoogleCredential(ServiceAccountCredential credential)
|
||||
: base(credential) { }
|
||||
|
||||
public override bool IsCreateScopedRequired
|
||||
{
|
||||
get { return !(credential as ServiceAccountCredential).HasScopes; }
|
||||
}
|
||||
|
||||
public override GoogleCredential CreateScoped(IEnumerable<string> scopes)
|
||||
{
|
||||
var serviceAccountCredential = credential as ServiceAccountCredential;
|
||||
var initializer = new ServiceAccountCredential.Initializer(serviceAccountCredential.Id)
|
||||
{
|
||||
User = serviceAccountCredential.User,
|
||||
Key = serviceAccountCredential.Key,
|
||||
Scopes = scopes
|
||||
};
|
||||
return new ServiceAccountGoogleCredential(new ServiceAccountCredential(initializer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
Copyright 2017 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
using Google.Apis.Util.Store;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>A helper utility to manage the authorization code flow.</summary>
|
||||
public class GoogleWebAuthorizationBroker
|
||||
{
|
||||
// It's unforunate this is a public field. But it cannot be changed due to backward compatibility.
|
||||
/// <summary>The folder which is used by the <see cref="Google.Apis.Util.Store.FileDataStore"/>.</summary>
|
||||
/// <remarks>
|
||||
/// The reason that this is not 'private const' is that a user can change it and store the credentials in a
|
||||
/// different location.
|
||||
/// </remarks>
|
||||
public static string Folder = "Google.Apis.Auth";
|
||||
|
||||
/// <summary>Asynchronously authorizes the specified user.</summary>
|
||||
/// <remarks>
|
||||
/// In case no data store is specified, <see cref="Google.Apis.Util.Store.FileDataStore"/> will be used by
|
||||
/// default.
|
||||
/// </remarks>
|
||||
/// <param name="clientSecrets">The client secrets.</param>
|
||||
/// <param name="scopes">
|
||||
/// The scopes which indicate the Google API access your application is requesting.
|
||||
/// </param>
|
||||
/// <param name="user">The user to authorize.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation.</param>
|
||||
/// <param name="dataStore">The data store, if not specified a file data store will be used.</param>
|
||||
/// <param name="codeReceiver">The code receiver, if not specified a local server code receiver will be used.</param>
|
||||
/// <returns>User credential.</returns>
|
||||
public static async Task<UserCredential> AuthorizeAsync(ClientSecrets clientSecrets,
|
||||
IEnumerable<string> scopes, string user, CancellationToken taskCancellationToken,
|
||||
IDataStore dataStore = null, ICodeReceiver codeReceiver = null)
|
||||
{
|
||||
var initializer = new GoogleAuthorizationCodeFlow.Initializer
|
||||
{
|
||||
ClientSecrets = clientSecrets,
|
||||
};
|
||||
return await AuthorizeAsync(initializer, scopes, user, taskCancellationToken, dataStore, codeReceiver)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Asynchronously authorizes the specified user.</summary>
|
||||
/// <remarks>
|
||||
/// In case no data store is specified, <see cref="Google.Apis.Util.Store.FileDataStore"/> will be used by
|
||||
/// default.
|
||||
/// </remarks>
|
||||
/// <param name="clientSecretsStream">
|
||||
/// The client secrets stream. The authorization code flow constructor is responsible for disposing the stream.
|
||||
/// </param>
|
||||
/// <param name="scopes">
|
||||
/// The scopes which indicate the Google API access your application is requesting.
|
||||
/// </param>
|
||||
/// <param name="user">The user to authorize.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation.</param>
|
||||
/// <param name="dataStore">The data store, if not specified a file data store will be used.</param>
|
||||
/// <param name="codeReceiver">The code receiver, if not specified a local server code receiver will be used.</param>
|
||||
/// <returns>User credential.</returns>
|
||||
public static async Task<UserCredential> AuthorizeAsync(Stream clientSecretsStream,
|
||||
IEnumerable<string> scopes, string user, CancellationToken taskCancellationToken,
|
||||
IDataStore dataStore = null, ICodeReceiver codeReceiver = null)
|
||||
{
|
||||
var initializer = new GoogleAuthorizationCodeFlow.Initializer
|
||||
{
|
||||
ClientSecretsStream = clientSecretsStream,
|
||||
};
|
||||
return await AuthorizeAsync(initializer, scopes, user, taskCancellationToken, dataStore, codeReceiver)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously reauthorizes the user. This method should be called if the users want to authorize after
|
||||
/// they revoked the token.
|
||||
/// </summary>
|
||||
/// <param name="userCredential">The current user credential. Its <see cref="UserCredential.Token"/> will be
|
||||
/// updated. </param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation.</param>
|
||||
/// <param name="codeReceiver">The code receiver, if not specified a local server code receiver will be used.</param>
|
||||
public static async Task ReauthorizeAsync(UserCredential userCredential,
|
||||
CancellationToken taskCancellationToken, ICodeReceiver codeReceiver = null)
|
||||
{
|
||||
codeReceiver = codeReceiver ?? new LocalServerCodeReceiver();
|
||||
|
||||
// Create an authorization code installed app instance and authorize the user.
|
||||
UserCredential newUserCredential = await new AuthorizationCodeInstalledApp(userCredential.Flow,
|
||||
codeReceiver).AuthorizeAsync
|
||||
(userCredential.UserId, taskCancellationToken).ConfigureAwait(false);
|
||||
userCredential.Token = newUserCredential.Token;
|
||||
}
|
||||
|
||||
/// <summary>The core logic for asynchronously authorizing the specified user.</summary>
|
||||
/// <param name="initializer">The authorization code initializer.</param>
|
||||
/// <param name="scopes">
|
||||
/// The scopes which indicate the Google API access your application is requesting.
|
||||
/// </param>
|
||||
/// <param name="user">The user to authorize.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation.</param>
|
||||
/// <param name="dataStore">The data store, if not specified a file data store will be used.</param>
|
||||
/// <param name="codeReceiver">The code receiver, if not specified a local server code receiver will be used.</param>
|
||||
/// <returns>User credential.</returns>
|
||||
public static async Task<UserCredential> AuthorizeAsync(
|
||||
GoogleAuthorizationCodeFlow.Initializer initializer, IEnumerable<string> scopes, string user,
|
||||
CancellationToken taskCancellationToken, IDataStore dataStore = null,
|
||||
ICodeReceiver codeReceiver = null)
|
||||
{
|
||||
initializer.Scopes = scopes;
|
||||
initializer.DataStore = dataStore ?? new FileDataStore(Folder);
|
||||
|
||||
var flow = new GoogleAuthorizationCodeFlow(initializer);
|
||||
codeReceiver = codeReceiver ?? new LocalServerCodeReceiver();
|
||||
|
||||
// Create an authorization code installed app instance and authorize the user.
|
||||
return await new AuthorizationCodeInstalledApp(flow, codeReceiver).AuthorizeAsync
|
||||
(user, taskCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright 2015 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.Net.Http;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Method of presenting the access token to the resource server as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-7
|
||||
/// </summary>
|
||||
public interface IAccessMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Intercepts a HTTP request right before the HTTP request executes by providing the access token.
|
||||
/// </summary>
|
||||
void Intercept(HttpRequestMessage request, string accessToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the original access token in the HTTP request, as provided in the <see cref=" Intercept"/>
|
||||
/// method.
|
||||
/// </summary>
|
||||
string GetAccessToken(HttpRequestMessage request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization code flow for an installed application that persists end-user credentials.
|
||||
/// </summary>
|
||||
public interface IAuthorizationCodeInstalledApp
|
||||
{
|
||||
/// <summary>Gets the authorization code flow.</summary>
|
||||
IAuthorizationCodeFlow Flow { get; }
|
||||
|
||||
/// <summary>Gets the code receiver.</summary>
|
||||
ICodeReceiver CodeReceiver { get; }
|
||||
|
||||
/// <summary>Asynchronously authorizes the installed application to access user's protected data.</summary>
|
||||
/// <param name="userId">User identifier</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation</param>
|
||||
/// <returns>The user's credential</returns>
|
||||
Task<UserCredential> AuthorizeAsync(string userId, CancellationToken taskCancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>OAuth 2.0 verification code receiver.</summary>
|
||||
public interface ICodeReceiver
|
||||
{
|
||||
/// <summary>Gets the redirected URI.</summary>
|
||||
string RedirectUri { get; }
|
||||
|
||||
/// <summary>Receives the authorization code.</summary>
|
||||
/// <param name="url">The authorization code request URL</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token</param>
|
||||
/// <returns>The authorization code response</returns>
|
||||
Task<AuthorizationCodeResponseUrl> ReceiveCodeAsync(AuthorizationCodeRequestUrl url,
|
||||
CancellationToken taskCancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2015 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 Google.Apis.Http;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// The main interface to represent credential in the client library.
|
||||
/// Service account, User account and Compute credential inherit from this interface
|
||||
/// to provide access token functionality. In addition this interface inherits from
|
||||
/// <see cref="IConfigurableHttpClientInitializer"/> to be able to hook to http requests.
|
||||
/// More details are available in the specific implementations.
|
||||
/// </summary>
|
||||
public interface ICredential : IConfigurableHttpClientInitializer, ITokenAccess
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2015 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows direct retrieval of access tokens to authenticate requests.
|
||||
/// This is necessary for workflows where you don't want to use
|
||||
/// <see cref="T:Google.Apis.Services.BaseClientService"/> to access the API.
|
||||
/// (e.g. gRPC that implemenents the entire HTTP2 stack internally).
|
||||
/// </summary>
|
||||
public interface ITokenAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an access token to authorize a request.
|
||||
/// Implementations should handle automatic refreshes of the token
|
||||
/// if they are supported.
|
||||
/// The <paramref name="authUri"/> might be required by some credential types
|
||||
/// (e.g. the JWT access token) while other credential types
|
||||
/// migth just ignore it.
|
||||
/// </summary>
|
||||
/// <param name="authUri">The URI the returned token will grant access to.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The access token.</returns>
|
||||
Task<string> GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Holder for credential parameters read from JSON credential file.
|
||||
/// Fields are union of parameters for all supported credential types.
|
||||
/// </summary>
|
||||
public class JsonCredentialParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// UserCredential is created by the GCloud SDK tool when the user runs
|
||||
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">GCloud Auth Login</a>.
|
||||
/// </summary>
|
||||
public const string AuthorizedUserCredentialType = "authorized_user";
|
||||
|
||||
/// <summary>
|
||||
/// ServiceAccountCredential is downloaded by the user from
|
||||
/// <a href="https://console.developers.google.com">Google Developers Console</a>.
|
||||
/// </summary>
|
||||
public const string ServiceAccountCredentialType = "service_account";
|
||||
|
||||
/// <summary>Type of the credential.</summary>
|
||||
[Newtonsoft.Json.JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Client Id associated with UserCredential created by
|
||||
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">GCloud Auth Login</a>.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Client Secret associated with UserCredential created by
|
||||
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">GCloud Auth Login</a>.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("client_secret")]
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Client Email associated with ServiceAccountCredential obtained from
|
||||
/// <a href="https://console.developers.google.com">Google Developers Console</a>
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("client_email")]
|
||||
public string ClientEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Private Key associated with ServiceAccountCredential obtained from
|
||||
/// <a href="https://console.developers.google.com">Google Developers Console</a>.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("private_key")]
|
||||
public string PrivateKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Refresh Token associated with UserCredential created by
|
||||
/// <a href="https://cloud.google.com/sdk/gcloud/reference/auth/login">GCloud Auth Login</a>.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
Copyright 2017 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 Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 verification code receiver that runs a local server on a free port and waits for a call with the
|
||||
/// authorization verification code.
|
||||
/// </summary>
|
||||
public class LocalServerCodeReceiver : ICodeReceiver
|
||||
{
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<LocalServerCodeReceiver>();
|
||||
|
||||
/// <summary>The call back request path.</summary>
|
||||
internal const string LoopbackCallbackPath = "/authorize/";
|
||||
|
||||
/// <summary>The call back format. Expects one port parameter.</summary>
|
||||
internal static readonly string LoopbackCallback = $"http://{IPAddress.Loopback}:{{0}}{LoopbackCallbackPath}";
|
||||
|
||||
/// <summary>Close HTML tag to return the browser so it will close itself.</summary>
|
||||
internal const string ClosePageResponse =
|
||||
@"<html>
|
||||
<head><title>OAuth 2.0 Authentication Token Received</title></head>
|
||||
<body>
|
||||
Received verification code. You may now close this window.
|
||||
<script type='text/javascript'>
|
||||
// This doesn't work on every browser.
|
||||
window.setTimeout(function() {
|
||||
this.focus();
|
||||
window.opener = this;
|
||||
window.open('', '_self', '');
|
||||
window.close();
|
||||
}, 1000);
|
||||
//if (window.opener) { window.opener.checkToken(); }
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
// Not required in NET45, but present for testing.
|
||||
/// <summary>
|
||||
/// An extremely limited HTTP server that can only do exactly what is required
|
||||
/// for this use-case.
|
||||
/// It can only serve localhost; receive a single GET request; read only the query paremters;
|
||||
/// send back a fixed response. Nothing else.
|
||||
/// </summary>
|
||||
internal class LimitedLocalhostHttpServer : IDisposable
|
||||
{
|
||||
private const int MaxRequestLineLength = 256;
|
||||
private const int MaxHeadersLength = 8192;
|
||||
private const int NetworkReadBufferSize = 1024;
|
||||
|
||||
private static ILogger Logger = ApplicationContext.Logger.ForType<LimitedLocalhostHttpServer>();
|
||||
|
||||
public class ServerException : Exception
|
||||
{
|
||||
public ServerException(string msg) : base(msg) { }
|
||||
}
|
||||
|
||||
public static LimitedLocalhostHttpServer Start(string url)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
if (!uri.IsLoopback)
|
||||
{
|
||||
throw new ArgumentException($"Url must be loopback, but given: '{url}'", nameof(url));
|
||||
}
|
||||
var listener = new TcpListener(IPAddress.Loopback, uri.Port);
|
||||
return new LimitedLocalhostHttpServer(listener);
|
||||
}
|
||||
|
||||
private LimitedLocalhostHttpServer(TcpListener listener)
|
||||
{
|
||||
_listener = listener;
|
||||
_cts = new CancellationTokenSource();
|
||||
_listener.Start();
|
||||
Port = ((IPEndPoint)_listener.LocalEndpoint).Port;
|
||||
}
|
||||
|
||||
private readonly TcpListener _listener;
|
||||
private readonly CancellationTokenSource _cts;
|
||||
|
||||
public int Port { get; }
|
||||
|
||||
public async Task<Dictionary<string, string>> GetQueryParamsAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var ct = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, cancellationToken).Token;
|
||||
using (TcpClient client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetQueryParamsFromClientAsync(client, ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (ServerException e)
|
||||
{
|
||||
Logger.Warning("{0}", e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> GetQueryParamsFromClientAsync(TcpClient client, CancellationToken cancellationToken)
|
||||
{
|
||||
var stream = client.GetStream();
|
||||
|
||||
var buffer = new byte[NetworkReadBufferSize];
|
||||
int bufferOfs = 0;
|
||||
int bufferSize = 0;
|
||||
Func<Task<char?>> getChar = async () =>
|
||||
{
|
||||
if (bufferOfs == bufferSize)
|
||||
{
|
||||
bufferSize = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (bufferSize == 0)
|
||||
{
|
||||
// End of stream
|
||||
return null;
|
||||
}
|
||||
bufferOfs = 0;
|
||||
}
|
||||
byte b = buffer[bufferOfs++];
|
||||
// HTTP headers are generally ASCII, but historically allowed ISO-8859-1.
|
||||
// Non-ASCII bytes should be treated opaquely, not further processed (e.g. as UTF8).
|
||||
return (char)b;
|
||||
};
|
||||
|
||||
string requestLine = await ReadRequestLine(getChar).ConfigureAwait(false);
|
||||
var requestParams = ValidateAndGetRequestParams(requestLine);
|
||||
await WaitForAllHeaders(getChar).ConfigureAwait(false);
|
||||
await WriteResponse(stream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
private async Task<string> ReadRequestLine(Func<Task<char?>> getChar)
|
||||
{
|
||||
var requestLine = new StringBuilder(MaxRequestLineLength);
|
||||
do
|
||||
{
|
||||
if (requestLine.Length >= MaxRequestLineLength)
|
||||
{
|
||||
throw new ServerException($"Request line too long: > {MaxRequestLineLength} bytes.");
|
||||
}
|
||||
char? c = await getChar().ConfigureAwait(false);
|
||||
if (c == null)
|
||||
{
|
||||
throw new ServerException("Unexpected end of network stream reading request line.");
|
||||
}
|
||||
requestLine.Append(c);
|
||||
} while (requestLine.Length < 2 || requestLine[requestLine.Length - 2] != '\r' || requestLine[requestLine.Length - 1] != '\n');
|
||||
requestLine.Length -= 2; // Remove \r\n
|
||||
return requestLine.ToString();
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ValidateAndGetRequestParams(string requestLine)
|
||||
{
|
||||
var requestLineParts = requestLine.Split(' ');
|
||||
if (requestLineParts.Length != 3)
|
||||
{
|
||||
throw new ServerException("Request line ill-formatted. Should be '<request-method> <request-path> HTTP/1.1'");
|
||||
}
|
||||
string requestVerb = requestLineParts[0];
|
||||
if (requestVerb != "GET")
|
||||
{
|
||||
throw new ServerException($"Expected 'GET' request, got '{requestVerb}'");
|
||||
}
|
||||
string requestPath = requestLineParts[1];
|
||||
if (!requestPath.StartsWith(LoopbackCallbackPath))
|
||||
{
|
||||
throw new ServerException($"Expected request path to start '{LoopbackCallbackPath}', got '{requestPath}'");
|
||||
}
|
||||
var pathParts = requestPath.Split('?');
|
||||
if (pathParts.Length == 1)
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
if (pathParts.Length != 2)
|
||||
{
|
||||
throw new ServerException($"Expected a single '?' in request path, got '{requestPath}'");
|
||||
}
|
||||
var queryParams = pathParts[1];
|
||||
var result = queryParams.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries).Select(param =>
|
||||
{
|
||||
var keyValue = param.Split('=');
|
||||
if (keyValue.Length > 2)
|
||||
{
|
||||
throw new ServerException($"Invalid query parameter: '{param}'");
|
||||
}
|
||||
var key = WebUtility.UrlDecode(keyValue[0]);
|
||||
var value = keyValue.Length == 2 ? WebUtility.UrlDecode(keyValue[1]) : "";
|
||||
return new { key, value };
|
||||
}).ToDictionary(x => x.key, x => x.value);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task WaitForAllHeaders(Func<Task<char?>> getChar)
|
||||
{
|
||||
// Looking for an empty line, terminated by \r\n
|
||||
int byteCount = 0;
|
||||
int lineLength = 0;
|
||||
char c0 = '\0';
|
||||
char c1 = '\0';
|
||||
while (true)
|
||||
{
|
||||
if (byteCount > MaxHeadersLength)
|
||||
{
|
||||
throw new ServerException($"Headers too long: > {MaxHeadersLength} bytes.");
|
||||
}
|
||||
char? c = await getChar().ConfigureAwait(false);
|
||||
if (c == null)
|
||||
{
|
||||
throw new ServerException("Unexpected end of network stream waiting for headers.");
|
||||
}
|
||||
c0 = c1;
|
||||
c1 = (char)c;
|
||||
lineLength += 1;
|
||||
byteCount += 1;
|
||||
if (c0 == '\r' && c1 == '\n')
|
||||
{
|
||||
// End of line
|
||||
if (lineLength == 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lineLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteResponse(NetworkStream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
string fullResponse = $"HTTP/1.1 200 OK\r\n\r\n{ClosePageResponse}";
|
||||
var response = Encoding.ASCII.GetBytes(fullResponse);
|
||||
await stream.WriteAsync(response, 0, response.Length, cancellationToken).ConfigureAwait(false);
|
||||
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_listener.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// There is a race condition on the port used for the loopback callback.
|
||||
// This is not good, but is now difficult to change due to RedirecrUri and ReceiveCodeAsync
|
||||
// being public methods.
|
||||
|
||||
private string redirectUri;
|
||||
/// <inheritdoc />
|
||||
public string RedirectUri
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(redirectUri))
|
||||
{
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
return redirectUri = string.Format(LoopbackCallback, GetRandomUnusedPort());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<AuthorizationCodeResponseUrl> ReceiveCodeAsync(AuthorizationCodeRequestUrl url,
|
||||
CancellationToken taskCancellationToken)
|
||||
{
|
||||
var authorizationUrl = url.Build().ToString();
|
||||
// The listener type depends on platform:
|
||||
// * .NET desktop: System.Net.HttpListener
|
||||
// * .NET Core: LimitedLocalhostHttpServer (above, HttpListener is not available in any version of netstandard)
|
||||
using (var listener = StartListener())
|
||||
{
|
||||
Logger.Debug("Open a browser with \"{0}\" URL", authorizationUrl);
|
||||
bool browserOpenedOk;
|
||||
try
|
||||
{
|
||||
browserOpenedOk = OpenBrowser(authorizationUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Failed to launch browser with \"{0}\" for authorization", authorizationUrl);
|
||||
throw new NotSupportedException(
|
||||
$"Failed to launch browser with \"{authorizationUrl}\" for authorization. See inner exception for details.", e);
|
||||
}
|
||||
if (!browserOpenedOk)
|
||||
{
|
||||
Logger.Error("Failed to launch browser with \"{0}\" for authorization; platform not supported.", authorizationUrl);
|
||||
throw new NotSupportedException(
|
||||
$"Failed to launch browser with \"{authorizationUrl}\" for authorization; platform not supported.");
|
||||
}
|
||||
|
||||
return await GetResponseFromListener(listener, taskCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a random, unused port.</summary>
|
||||
private static int GetRandomUnusedPort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
try
|
||||
{
|
||||
listener.Start();
|
||||
return ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
}
|
||||
finally
|
||||
{
|
||||
listener.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
private LimitedLocalhostHttpServer StartListener() => LimitedLocalhostHttpServer.Start(RedirectUri);
|
||||
|
||||
private async Task<AuthorizationCodeResponseUrl> GetResponseFromListener(LimitedLocalhostHttpServer server, CancellationToken ct)
|
||||
{
|
||||
var queryParams = await server.GetQueryParamsAsync(ct).ConfigureAwait(false);
|
||||
|
||||
// Create a new response URL with a dictionary that contains all the response query parameters.
|
||||
return new AuthorizationCodeResponseUrl(queryParams);
|
||||
}
|
||||
|
||||
private bool OpenBrowser(string url)
|
||||
{
|
||||
// See https://github.com/dotnet/corefx/issues/10361
|
||||
// This is best-effort only, but should work most of the time.
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url.Replace("&", "^&")}") { CreateNoWindow = true });
|
||||
return true;
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Process.Start("xdg-open", url);
|
||||
return true;
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
Process.Start("open", url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
private HttpListener StartListener()
|
||||
{
|
||||
var listener = new HttpListener();
|
||||
listener.Prefixes.Add(RedirectUri);
|
||||
listener.Start();
|
||||
return listener;
|
||||
}
|
||||
|
||||
private async Task<AuthorizationCodeResponseUrl> GetResponseFromListener(HttpListener listener, CancellationToken ct)
|
||||
{
|
||||
HttpListenerContext context;
|
||||
// Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token,
|
||||
// the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call.
|
||||
using (ct.Register(listener.Stop))
|
||||
{
|
||||
// Wait to get the authorization code response.
|
||||
try
|
||||
{
|
||||
context = await listener.GetContextAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception) when (ct.IsCancellationRequested)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
// Next line will never be reached because cancellation will always have been requested in this catch block.
|
||||
// But it's required to satisfy compiler.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
NameValueCollection coll = context.Request.QueryString;
|
||||
|
||||
// Write a "close" response.
|
||||
using (var writer = new StreamWriter(context.Response.OutputStream))
|
||||
{
|
||||
writer.WriteLine(ClosePageResponse);
|
||||
writer.Flush();
|
||||
}
|
||||
context.Response.OutputStream.Close();
|
||||
|
||||
// Create a new response URL with a dictionary that contains all the response query parameters.
|
||||
return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k]));
|
||||
}
|
||||
|
||||
private bool OpenBrowser(string url)
|
||||
{
|
||||
Process.Start(url);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
287
Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Pkcs8.cs
Normal file
287
Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Pkcs8.cs
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
Copyright 2016 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 Google.Apis.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
internal class Pkcs8
|
||||
{
|
||||
// PKCS#8 specification: https://www.ietf.org/rfc/rfc5208.txt
|
||||
// ASN.1 specification: https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
|
||||
|
||||
/// <summary>
|
||||
/// An incomplete ASN.1 decoder, only implements what's required
|
||||
/// to decode a Service Credential.
|
||||
/// </summary>
|
||||
internal class Asn1
|
||||
{
|
||||
internal enum Tag
|
||||
{
|
||||
Integer = 2,
|
||||
OctetString = 4,
|
||||
Null = 5,
|
||||
ObjectIdentifier = 6,
|
||||
Sequence = 16,
|
||||
}
|
||||
|
||||
internal class Decoder
|
||||
{
|
||||
public Decoder(byte[] bytes)
|
||||
{
|
||||
_bytes = bytes;
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
private byte[] _bytes;
|
||||
private int _index;
|
||||
|
||||
public object Decode()
|
||||
{
|
||||
Tag tag = ReadTag();
|
||||
switch (tag)
|
||||
{
|
||||
case Tag.Integer:
|
||||
return ReadInteger();
|
||||
case Tag.OctetString:
|
||||
return ReadOctetString();
|
||||
case Tag.Null:
|
||||
return ReadNull();
|
||||
case Tag.ObjectIdentifier:
|
||||
return ReadOid();
|
||||
case Tag.Sequence:
|
||||
return ReadSequence();
|
||||
default:
|
||||
throw new NotSupportedException($"Tag '{tag}' not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private byte NextByte() => _bytes[_index++];
|
||||
|
||||
private byte[] ReadLengthPrefixedBytes()
|
||||
{
|
||||
int length = ReadLength();
|
||||
return ReadBytes(length);
|
||||
}
|
||||
|
||||
private byte[] ReadInteger() => ReadLengthPrefixedBytes();
|
||||
|
||||
private object ReadOctetString()
|
||||
{
|
||||
byte[] bytes = ReadLengthPrefixedBytes();
|
||||
return new Decoder(bytes).Decode();
|
||||
}
|
||||
|
||||
private object ReadNull()
|
||||
{
|
||||
int length = ReadLength();
|
||||
if (length != 0)
|
||||
{
|
||||
throw new InvalidDataException("Invalid data, Null length must be 0.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int[] ReadOid()
|
||||
{
|
||||
byte[] oidBytes = ReadLengthPrefixedBytes();
|
||||
List<int> result = new List<int>();
|
||||
bool first = true;
|
||||
int index = 0;
|
||||
while (index < oidBytes.Length)
|
||||
{
|
||||
int subId = 0;
|
||||
byte b;
|
||||
do
|
||||
{
|
||||
b = oidBytes[index++];
|
||||
if ((subId & 0xff000000) != 0)
|
||||
{
|
||||
throw new NotSupportedException("Oid subId > 2^31 not supported.");
|
||||
}
|
||||
subId = (subId << 7) | (b & 0x7f);
|
||||
} while ((b & 0x80) != 0);
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
result.Add(subId / 40);
|
||||
result.Add(subId % 40);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(subId);
|
||||
}
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private object[] ReadSequence()
|
||||
{
|
||||
int length = ReadLength();
|
||||
int endOffset = _index + length;
|
||||
if (endOffset < 0 || endOffset > _bytes.Length)
|
||||
{
|
||||
throw new InvalidDataException("Invalid sequence, too long.");
|
||||
}
|
||||
List<object> sequence = new List<object>();
|
||||
while (_index < endOffset)
|
||||
{
|
||||
sequence.Add(Decode());
|
||||
}
|
||||
return sequence.ToArray();
|
||||
}
|
||||
|
||||
private byte[] ReadBytes(int length)
|
||||
{
|
||||
if (length <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "length must be positive.");
|
||||
}
|
||||
if (_bytes.Length - length < 0)
|
||||
{
|
||||
throw new ArgumentException("Cannot read past end of buffer.");
|
||||
}
|
||||
byte[] result = new byte[length];
|
||||
Array.Copy(_bytes, _index, result, 0, length);
|
||||
_index += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
private Tag ReadTag()
|
||||
{
|
||||
byte b = NextByte();
|
||||
int tag = b & 0x1f;
|
||||
if (tag == 0x1f)
|
||||
{
|
||||
// A tag value of 0x1f (31) indicates a tag value of >30 (spec section 8.1.2.4)
|
||||
throw new NotSupportedException("Tags of value > 30 not supported.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Tag)tag;
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadLength()
|
||||
{
|
||||
byte b0 = NextByte();
|
||||
if ((b0 & 0x80) == 0)
|
||||
{
|
||||
return b0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b0 == 0xff)
|
||||
{
|
||||
throw new InvalidDataException("Invalid length byte: 0xff");
|
||||
}
|
||||
int byteCount = b0 & 0x7f;
|
||||
if (byteCount == 0)
|
||||
{
|
||||
throw new NotSupportedException("Lengths in Indefinite Form not supported.");
|
||||
}
|
||||
int result = 0;
|
||||
for (int i = 0; i < byteCount; i++)
|
||||
{
|
||||
if ((result & 0xff800000) != 0)
|
||||
{
|
||||
throw new NotSupportedException("Lengths > 2^31 not supported.");
|
||||
}
|
||||
result = (result << 8) | NextByte();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static object Decode(byte[] bs) => new Decoder(bs).Decode();
|
||||
|
||||
}
|
||||
|
||||
public static RSAParameters DecodeRsaParameters(string pkcs8PrivateKey)
|
||||
{
|
||||
const string PrivateKeyPrefix = "-----BEGIN PRIVATE KEY-----";
|
||||
const string PrivateKeySuffix = "-----END PRIVATE KEY-----";
|
||||
|
||||
Utilities.ThrowIfNullOrEmpty(pkcs8PrivateKey, nameof(pkcs8PrivateKey));
|
||||
pkcs8PrivateKey = pkcs8PrivateKey.Trim();
|
||||
if (!pkcs8PrivateKey.StartsWith(PrivateKeyPrefix) || !pkcs8PrivateKey.EndsWith(PrivateKeySuffix))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"PKCS8 data must be contained within '{PrivateKeyPrefix}' and '{PrivateKeySuffix}'.", nameof(pkcs8PrivateKey));
|
||||
}
|
||||
string base64PrivateKey =
|
||||
pkcs8PrivateKey.Substring(PrivateKeyPrefix.Length, pkcs8PrivateKey.Length - PrivateKeyPrefix.Length - PrivateKeySuffix.Length);
|
||||
// FromBase64String() ignores whitespace, so further Trim()ing isn't required.
|
||||
byte[] pkcs8Bytes = Convert.FromBase64String(base64PrivateKey);
|
||||
|
||||
object ans1 = Asn1.Decode(pkcs8Bytes);
|
||||
object[] parameters = (object[])((object[])ans1)[2];
|
||||
|
||||
var rsaParmeters = new RSAParameters
|
||||
{
|
||||
Modulus = TrimLeadingZeroes((byte[])parameters[1]),
|
||||
Exponent = TrimLeadingZeroes((byte[])parameters[2], alignTo8Bytes: false),
|
||||
D = TrimLeadingZeroes((byte[])parameters[3]),
|
||||
P = TrimLeadingZeroes((byte[])parameters[4]),
|
||||
Q = TrimLeadingZeroes((byte[])parameters[5]),
|
||||
DP = TrimLeadingZeroes((byte[])parameters[6]),
|
||||
DQ = TrimLeadingZeroes((byte[])parameters[7]),
|
||||
InverseQ = TrimLeadingZeroes((byte[])parameters[8]),
|
||||
};
|
||||
|
||||
return rsaParmeters;
|
||||
}
|
||||
|
||||
internal static byte[] TrimLeadingZeroes(byte[] bs, bool alignTo8Bytes = true)
|
||||
{
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < bs.Length && bs[zeroCount] == 0) zeroCount += 1;
|
||||
|
||||
int newLength = bs.Length - zeroCount;
|
||||
if (alignTo8Bytes)
|
||||
{
|
||||
int remainder = newLength & 0x07;
|
||||
if (remainder != 0)
|
||||
{
|
||||
newLength += 8 - remainder;
|
||||
}
|
||||
}
|
||||
|
||||
if (newLength == bs.Length)
|
||||
{
|
||||
return bs;
|
||||
}
|
||||
|
||||
byte[] result = new byte[newLength];
|
||||
if (newLength < bs.Length)
|
||||
{
|
||||
Buffer.BlockCopy(bs, bs.Length - newLength, result, 0, newLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(bs, 0, result, newLength - bs.Length, bs.Length);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Logging;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>OAuth 2.0 verification code receiver that reads the authorization code from the user input.</summary>
|
||||
public class PromptCodeReceiver : ICodeReceiver
|
||||
{
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<PromptCodeReceiver>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string RedirectUri
|
||||
{
|
||||
get { return GoogleAuthConsts.InstalledAppRedirectUri; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<AuthorizationCodeResponseUrl> ReceiveCodeAsync(AuthorizationCodeRequestUrl url,
|
||||
CancellationToken taskCancellationToken)
|
||||
{
|
||||
var authorizationUrl = url.Build().ToString();
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
Logger.Debug("Requested user open a browser with \"{0}\" URL", authorizationUrl);
|
||||
Console.WriteLine("Please visit the following URL in a web browser, then enter the code shown after authorization:");
|
||||
Console.WriteLine(authorizationUrl);
|
||||
Console.WriteLine();
|
||||
#elif NET45
|
||||
Logger.Debug("Open a browser with \"{0}\" URL", authorizationUrl);
|
||||
System.Diagnostics.Process.Start(authorizationUrl);
|
||||
|
||||
#elif DNX451
|
||||
Logger.Debug("Open a browser with \"{0}\" URL", authorizationUrl);
|
||||
System.Diagnostics.Process.Start(authorizationUrl);
|
||||
#else
|
||||
#error Unsupported target
|
||||
#endif
|
||||
|
||||
string code = string.Empty;
|
||||
while (string.IsNullOrEmpty(code))
|
||||
{
|
||||
Console.WriteLine("Please enter code: ");
|
||||
code = Console.ReadLine();
|
||||
}
|
||||
|
||||
Logger.Debug("Code is: \"{0}\"", code);
|
||||
|
||||
return Task.FromResult(new AuthorizationCodeResponseUrl { Code = code });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright 2013 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 Google.Apis.Requests;
|
||||
using Google.Apis.Requests.Parameters;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 request URL for an authorization web page to allow the end user to authorize the application to
|
||||
/// access their protected resources and that returns an authorization code, as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-4.1.
|
||||
/// </summary>
|
||||
public class AuthorizationCodeRequestUrl : AuthorizationRequestUrl
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new authorization code request with the specified URI and sets response_type to <c>code</c>.
|
||||
/// </summary>
|
||||
public AuthorizationCodeRequestUrl(Uri authorizationServerUrl)
|
||||
: base(authorizationServerUrl)
|
||||
{
|
||||
ResponseType = "code";
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="System.Uri"/> which is used to request the authorization code.</summary>
|
||||
public Uri Build()
|
||||
{
|
||||
var builder = new RequestBuilder()
|
||||
{
|
||||
BaseUri = AuthorizationServerUrl
|
||||
};
|
||||
ParameterUtils.InitParameters(builder, this);
|
||||
return builder.BuildUri();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 request for an access token using an authorization code as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-4.1.3.
|
||||
/// </summary>
|
||||
public class AuthorizationCodeTokenRequest : TokenRequest
|
||||
{
|
||||
/// <summary>Gets or sets the authorization code received from the authorization server.</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("code")]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the redirect URI parameter matching the redirect URI parameter in the authorization request.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("redirect_uri")]
|
||||
public string RedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new authorization code token request and sets grant_type to <c>authorization_code</c>.
|
||||
/// </summary>
|
||||
public AuthorizationCodeTokenRequest()
|
||||
{
|
||||
GrantType = "authorization_code";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2013 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;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 request URL for an authorization web page to allow the end user to authorize the application to
|
||||
/// access their protected resources, as specified in http://tools.ietf.org/html/rfc6749#section-3.1.
|
||||
/// </summary>
|
||||
public class AuthorizationRequestUrl
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the response type which must be <c>code</c> for requesting an authorization code or
|
||||
/// <c>token</c> for requesting an access token (implicit grant), or space separated registered extension
|
||||
/// values. See http://tools.ietf.org/html/rfc6749#section-3.1.1 for more details
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("response_type", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string ResponseType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the client identifier.</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("client_id", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URI that the authorization server directs the resource owner's user-agent back to the
|
||||
/// client after a successful authorization grant, as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-3.1.2 or <c>null</c> for none.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("redirect_uri", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string RedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets space-separated list of scopes, as specified in http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
/// or <c>null</c> for none.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("scope", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state (an opaque value used by the client to maintain state between the request and
|
||||
/// callback, as mentioned in http://tools.ietf.org/html/rfc6749#section-3.1.2.2 or <c>null</c> for none.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("state", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string State { get; set; }
|
||||
|
||||
private readonly Uri authorizationServerUrl;
|
||||
/// <summary>Gets the authorization server URI.</summary>
|
||||
public Uri AuthorizationServerUrl
|
||||
{
|
||||
get { return authorizationServerUrl; }
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new authorization request with the specified URI.</summary>
|
||||
/// <param name="authorizationServerUrl">Authorization server URI</param>
|
||||
public AuthorizationRequestUrl(Uri authorizationServerUrl)
|
||||
{
|
||||
this.authorizationServerUrl = authorizationServerUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// Service account assertion token request as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#makingrequest.
|
||||
/// </summary>
|
||||
public class GoogleAssertionTokenRequest : TokenRequest
|
||||
{
|
||||
/// <summary>Gets or sets the JWT (including signature).</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("assertion")]
|
||||
public string Assertion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new refresh code token request and sets grant_type to
|
||||
/// <c>urn:ietf:params:oauth:grant-type:jwt-bearer</c>.
|
||||
/// </summary>
|
||||
public GoogleAssertionTokenRequest()
|
||||
{
|
||||
GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2013 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.Collections.Generic;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// Google-specific implementation of the OAuth 2.0 URL for an authorization web page to allow the end user to
|
||||
/// authorize the application to access their protected resources and that returns an authorization code, as
|
||||
/// specified in https://developers.google.com/accounts/docs/OAuth2WebServer.
|
||||
/// </summary>
|
||||
public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the access type. Set <c>online</c> to request on-line access or <c>offline</c> to request
|
||||
/// off-line access or <c>null</c> for the default behavior. The default value is <c>offline</c>.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("access_type", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string AccessType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets prompt for consent behavior <c>auto</c> to request auto-approval or<c>force</c> to force the
|
||||
/// approval UI to show, or <c>null</c> for the default behavior.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("approval_prompt", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string ApprovalPrompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the login hint. Sets <c>email address</c> or sub <c>identifier</c>.
|
||||
/// When your application knows which user it is trying to authenticate, it may provide this parameter as a
|
||||
/// hint to the Authentication Server. Passing this hint will either pre-fill the email box on the sign-in form
|
||||
/// or select the proper multi-login session, thereby simplifying the login flow.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("login_hint", Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string LoginHint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the include granted scopes to determine if this authorization request should use
|
||||
/// incremental authorization (https://developers.google.com/+/web/api/rest/oauth#incremental-auth).
|
||||
/// If true and the authorization request is granted, the authorization will include any previous
|
||||
/// authorizations granted to this user/application combination for other scopes.
|
||||
/// </summary>
|
||||
/// <remarks>Currently unsupported for installed apps.</remarks>
|
||||
[Google.Apis.Util.RequestParameterAttribute("include_granted_scopes",
|
||||
Google.Apis.Util.RequestParameterType.Query)]
|
||||
public string IncludeGrantedScopes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of user defined query parameters to facilitate any not explicitly supported
|
||||
/// by the library which will be included in the resultant authentication URL.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The name of this parameter is used only for the constructor and will not end up in the resultant query
|
||||
/// string.
|
||||
/// </remarks>
|
||||
[Google.Apis.Util.RequestParameterAttribute("user_defined_query_params",
|
||||
Google.Apis.Util.RequestParameterType.UserDefinedQueries)]
|
||||
public IEnumerable<KeyValuePair<string, string>> UserDefinedQueryParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new authorization code request with the given authorization server URL. This constructor sets
|
||||
/// the <see cref="AccessType"/> to <c>offline</c>.
|
||||
/// </summary>
|
||||
public GoogleAuthorizationCodeRequestUrl(Uri authorizationServerUrl)
|
||||
: base(authorizationServerUrl)
|
||||
{
|
||||
AccessType = "offline";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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 Google.Apis.Requests;
|
||||
using Google.Apis.Requests.Parameters;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// Google OAuth 2.0 request to revoke an access token as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2WebServer#tokenrevoke.
|
||||
/// </summary>
|
||||
class GoogleRevokeTokenRequest
|
||||
{
|
||||
private readonly Uri revokeTokenUrl;
|
||||
/// <summary>Gets the URI for token revocation.</summary>
|
||||
public Uri RevokeTokenUrl
|
||||
{
|
||||
get { return revokeTokenUrl; }
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the token to revoke.</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("token")]
|
||||
public string Token { get; set; }
|
||||
|
||||
public GoogleRevokeTokenRequest(Uri revokeTokenUrl)
|
||||
{
|
||||
this.revokeTokenUrl = revokeTokenUrl;
|
||||
}
|
||||
|
||||
/// <summary>Creates a <see cref="System.Uri"/> which is used to request the authorization code.</summary>
|
||||
public Uri Build()
|
||||
{
|
||||
var builder = new RequestBuilder()
|
||||
{
|
||||
BaseUri = revokeTokenUrl
|
||||
};
|
||||
ParameterUtils.InitParameters(builder, this);
|
||||
return builder.BuildUri();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 request to refresh an access token using a refresh token as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-6.
|
||||
/// </summary>
|
||||
public class RefreshTokenRequest : TokenRequest
|
||||
{
|
||||
/// <summary>Gets or sets the Refresh token issued to the client.</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new refresh code token request and sets grant_type to <c>refresh_token</c>.
|
||||
/// </summary>
|
||||
public RefreshTokenRequest()
|
||||
{
|
||||
GrantType = "refresh_token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 request for an access token as specified in http://tools.ietf.org/html/rfc6749#section-4.
|
||||
/// </summary>
|
||||
public class TokenRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets space-separated list of scopes as specified in http://tools.ietf.org/html/rfc6749#section-3.3.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("scope")]
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Grant type. Sets <c>authorization_code</c> or <c>password</c> or <c>client_credentials</c>
|
||||
/// or <c>refresh_token</c> or absolute URI of the extension grant type.
|
||||
/// </summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("grant_type")]
|
||||
public string GrantType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the client Identifier.</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the client Secret.</summary>
|
||||
[Google.Apis.Util.RequestParameterAttribute("client_secret")]
|
||||
public string ClientSecret { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2013 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.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Json;
|
||||
using Google.Apis.Requests.Parameters;
|
||||
using Google.Apis.Util;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Requests
|
||||
{
|
||||
/// <summary>Extension methods to <see cref="TokenRequest"/>.</summary>
|
||||
public static class TokenRequestExtenstions
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes the token request in order to receive a
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.Responses.TokenResponse"/>. In case the token server returns an
|
||||
/// error, a <see cref="Google.Apis.Auth.OAuth2.Responses.TokenResponseException"/> is thrown.
|
||||
/// </summary>
|
||||
/// <param name="request">The token request.</param>
|
||||
/// <param name="httpClient">The HTTP client used to create an HTTP request.</param>
|
||||
/// <param name="tokenServerUrl">The token server URL.</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
/// <param name="clock">
|
||||
/// The clock which is used to set the
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.Responses.TokenResponse.Issued"/> property.
|
||||
/// </param>
|
||||
/// <returns>Token response with the new access token.</returns>
|
||||
public static async Task<TokenResponse> ExecuteAsync(this TokenRequest request, HttpClient httpClient,
|
||||
string tokenServerUrl, CancellationToken taskCancellationToken, IClock clock)
|
||||
{
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post, tokenServerUrl);
|
||||
httpRequest.Content = ParameterUtils.CreateFormUrlEncodedContent(request);
|
||||
|
||||
var response = await httpClient.SendAsync(httpRequest, taskCancellationToken).ConfigureAwait(false);
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = NewtonsoftJsonSerializer.Instance.Deserialize<TokenErrorResponse>(content);
|
||||
throw new TokenResponseException(error, response.StatusCode);
|
||||
}
|
||||
|
||||
// Gets the token and sets its issued time.
|
||||
var newToken = NewtonsoftJsonSerializer.Instance.Deserialize<TokenResponse>(content);
|
||||
newToken.IssuedUtc = clock.UtcNow;
|
||||
return newToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2013 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.Collections.Generic;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Responses
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization Code response for the redirect URL after end user grants or denies authorization as specified
|
||||
/// in http://tools.ietf.org/html/rfc6749#section-4.1.2.
|
||||
/// <para>
|
||||
/// Check that <see cref="Code"/> is not <c>null</c> or empty to verify the end-user granted authorization.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class AuthorizationCodeResponseUrl
|
||||
{
|
||||
/// <summary>Gets or sets the authorization code generated by the authorization server.</summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state parameter matching the state parameter in the authorization request.
|
||||
/// </summary>
|
||||
public string State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error code (e.g. "invalid_request", "unauthorized_client", "access_denied",
|
||||
/// "unsupported_response_type", "invalid_scope", "server_error", "temporarily_unavailable") as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-4.1.2.1.
|
||||
/// </summary>
|
||||
public string Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the human-readable text which provides additional information used to assist the client
|
||||
/// developer in understanding the error occurred.
|
||||
/// </summary>
|
||||
public string ErrorDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URI identifying a human-readable web page with provides information about the error.
|
||||
/// </summary>
|
||||
public string ErrorUri { get; set; }
|
||||
|
||||
/// <summary>Constructs a new authorization code response URL from the specified dictionary.</summary>
|
||||
public AuthorizationCodeResponseUrl(IDictionary<string, string> queryString)
|
||||
{
|
||||
InitFromDictionary(queryString);
|
||||
}
|
||||
|
||||
#region Constructs
|
||||
|
||||
/// <summary>Constructs a new authorization code response URL from the specified query string.</summary>
|
||||
public AuthorizationCodeResponseUrl(string query)
|
||||
{
|
||||
var pairs = query.Split('&');
|
||||
var queryString = new Dictionary<string, string>();
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var keyValue = pair.Split('=');
|
||||
queryString[keyValue[0]] = keyValue[1];
|
||||
}
|
||||
|
||||
InitFromDictionary(queryString);
|
||||
}
|
||||
|
||||
/// <summary>Initializes this instance from the input dictionary.</summary>
|
||||
private void InitFromDictionary(IDictionary<string, string> queryString)
|
||||
{
|
||||
//TODO(peleyal): improve the following code and make it a utility
|
||||
IDictionary<string, Action<string>> setters = new Dictionary<string, Action<string>>();
|
||||
setters["code"] = v => Code = v;
|
||||
setters["state"] = v => State = v;
|
||||
setters["error"] = v => Error = v;
|
||||
setters["error_description"] = v => ErrorDescription = v;
|
||||
setters["error_uri"] = v => ErrorUri = v;
|
||||
|
||||
Action<string> setter;
|
||||
foreach (var pair in queryString)
|
||||
{
|
||||
if (setters.TryGetValue(pair.Key, out setter))
|
||||
{
|
||||
setter(pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new empty authorization code response URL.</summary>
|
||||
public AuthorizationCodeResponseUrl()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2013 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.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Responses
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 model for a unsuccessful access token response as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-5.2.
|
||||
/// </summary>
|
||||
public class TokenErrorResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets error code (e.g. "invalid_request", "invalid_client", "invalid_grant", "unauthorized_client",
|
||||
/// "unsupported_grant_type", "invalid_scope") as specified in http://tools.ietf.org/html/rfc6749#section-5.2.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("error")]
|
||||
public string Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a human-readable text which provides additional information used to assist the client
|
||||
/// developer in understanding the error occurred.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("error_description")]
|
||||
public string ErrorDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URI identifying a human-readable web page with provides information about the error.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("error_uri")]
|
||||
public string ErrorUri { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Error:\"{0}\", Description:\"{1}\", Uri:\"{2}\"", Error, ErrorDescription, ErrorUri);
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new empty token error response.</summary>
|
||||
public TokenErrorResponse()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new token error response from the given authorization code response.</summary>
|
||||
public TokenErrorResponse(AuthorizationCodeResponseUrl authorizationCode)
|
||||
{
|
||||
Error = authorizationCode.Error;
|
||||
ErrorDescription = authorizationCode.ErrorDescription;
|
||||
ErrorUri = authorizationCode.ErrorUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
Copyright 2013 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 Google.Apis.Util;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Google.Apis.Json;
|
||||
using Google.Apis.Logging;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Responses
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 model for a successful access token response as specified in
|
||||
/// http://tools.ietf.org/html/rfc6749#section-5.1.
|
||||
/// </summary>
|
||||
public class TokenResponse
|
||||
{
|
||||
private const int TokenExpiryTimeWindowSeconds = 60 * 5; // Refresh token 5 minutes before it expires.
|
||||
|
||||
/// <summary>Gets or sets the access token issued by the authorization server.</summary>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the token type as specified in http://tools.ietf.org/html/rfc6749#section-7.1.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the lifetime in seconds of the access token.</summary>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute("expires_in")]
|
||||
public Nullable<long> ExpiresInSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the refresh token which can be used to obtain a new access token.
|
||||
/// For example, the value "3600" denotes that the access token will expire in one hour from the time the
|
||||
/// response was generated.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scope of the access token as specified in http://tools.ietf.org/html/rfc6749#section-3.3.
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute("scope")]
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id_token, which is a JSON Web Token (JWT) as specified in http://tools.ietf.org/html/draft-ietf-oauth-json-web-token
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute("id_token")]
|
||||
public string IdToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date and time that this token was issued, expressed in the system time zone.
|
||||
/// This property only exists for backward compatibility; it can cause inappropriate behavior around
|
||||
/// time zone transitions (e.g. daylight saving transitions).
|
||||
/// </summary>
|
||||
[Obsolete("Use IssuedUtc instead")]
|
||||
[Newtonsoft.Json.JsonPropertyAttribute(Order = 1)] // Serialize this before IssuedUtc, so that IssuedUtc takes priority when deserializing
|
||||
public DateTime Issued
|
||||
{
|
||||
get { return IssuedUtc.ToLocalTime(); }
|
||||
set { IssuedUtc = value.ToUniversalTime(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The date and time that this token was issued, expressed in UTC.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be set by the CLIENT after the token was received from the server.
|
||||
/// </remarks>
|
||||
[Newtonsoft.Json.JsonPropertyAttribute(Order = 2)]
|
||||
public DateTime IssuedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> if the token is expired or it's going to be expired in the next minute.
|
||||
/// </summary>
|
||||
public bool IsExpired(IClock clock)
|
||||
{
|
||||
if (AccessToken == null || !ExpiresInSeconds.HasValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IssuedUtc.AddSeconds(ExpiresInSeconds.Value - TokenExpiryTimeWindowSeconds) <= clock.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously parses a <see cref="TokenResponse"/> instance from the specified <see cref="HttpResponseMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The http response from which to parse the token.</param>
|
||||
/// <param name="clock">The clock used to set the <see cref="Issued"/> value of the token.</param>
|
||||
/// <param name="logger">The logger used to output messages incase of error.</param>
|
||||
/// <exception cref="TokenResponseException">
|
||||
/// The response was not successful or there is an error parsing the response into valid <see cref="TokenResponse"/> instance.
|
||||
/// </exception>
|
||||
/// <returns>
|
||||
/// A task containing the <see cref="TokenResponse"/> parsed form the response message.
|
||||
/// </returns>
|
||||
public static async Task<TokenResponse> FromHttpResponseAsync(HttpResponseMessage response, Util.IClock clock, ILogger logger)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var typeName = "";
|
||||
try
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
typeName = nameof(TokenErrorResponse);
|
||||
var error = NewtonsoftJsonSerializer.Instance.Deserialize<TokenErrorResponse>(content);
|
||||
throw new TokenResponseException(error, response.StatusCode);
|
||||
}
|
||||
|
||||
// Gets the token and sets its issued time.
|
||||
typeName = nameof(TokenResponse);
|
||||
var newToken = NewtonsoftJsonSerializer.Instance.Deserialize<TokenResponse>(content);
|
||||
newToken.IssuedUtc = clock.UtcNow;
|
||||
return newToken;
|
||||
}
|
||||
catch (Newtonsoft.Json.JsonException ex)
|
||||
{
|
||||
logger.Error(ex, $"Exception was caught when deserializing {typeName}. Content is: {content}");
|
||||
throw new TokenResponseException(new TokenErrorResponse
|
||||
{
|
||||
Error = "Server response does not contain a JSON object. Status code is: " + response.StatusCode
|
||||
}, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2013 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;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Responses
|
||||
{
|
||||
/// <summary>
|
||||
/// Token response exception which is thrown in case of receiving a token error when an authorization code or an
|
||||
/// access token is expected.
|
||||
/// </summary>
|
||||
public class TokenResponseException : Exception
|
||||
{
|
||||
/// <summary>The error information.</summary>
|
||||
public TokenErrorResponse Error { get; }
|
||||
|
||||
/// <summary>HTTP status code of error, or null if unknown.</summary>
|
||||
public HttpStatusCode? StatusCode { get; }
|
||||
|
||||
/// <summary>Constructs a new token response exception from the given error.</summary>
|
||||
public TokenResponseException(TokenErrorResponse error)
|
||||
: this(error, null) { }
|
||||
|
||||
/// <summary>Constructs a new token response exception from the given error nad optional HTTP status code.</summary>
|
||||
public TokenResponseException(TokenErrorResponse error, HttpStatusCode? statusCode)
|
||||
: base(error.ToString())
|
||||
{
|
||||
Error = error;
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
Copyright 2013 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Json;
|
||||
using Google.Apis.Util;
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
using RsaKey = System.Security.Cryptography.RSA;
|
||||
#elif NET45
|
||||
using RsaKey = System.Security.Cryptography.RSACryptoServiceProvider;
|
||||
#elif DNX451
|
||||
using RsaKey = System.Security.Cryptography.RSACryptoServiceProvider;
|
||||
#else
|
||||
#error Unsupported target
|
||||
#endif
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// Google OAuth 2.0 credential for accessing protected resources using an access token. The Google OAuth 2.0
|
||||
/// Authorization Server supports server-to-server interactions such as those between a web application and Google
|
||||
/// Cloud Storage. The requesting application has to prove its own identity to gain access to an API, and an
|
||||
/// end-user doesn't have to be involved.
|
||||
/// <para>
|
||||
/// Take a look in https://developers.google.com/accounts/docs/OAuth2ServiceAccount for more details.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Since version 1.9.3, service account credential also supports JSON Web Token access token scenario.
|
||||
/// In this scenario, instead of sending a signed JWT claim to a token server and exchanging it for
|
||||
/// an access token, a locally signed JWT claim bound to an appropriate URI is used as an access token
|
||||
/// directly.
|
||||
/// See <see cref="GetAccessTokenForRequestAsync"/> for explanation when JWT access token
|
||||
/// is used and when regular OAuth2 token is used.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class ServiceAccountCredential : ServiceCredential
|
||||
{
|
||||
private const string Sha256Oid = "2.16.840.1.101.3.4.2.1";
|
||||
/// <summary>An initializer class for the service account credential. </summary>
|
||||
new public class Initializer : ServiceCredential.Initializer
|
||||
{
|
||||
/// <summary>Gets the service account ID (typically an e-mail address).</summary>
|
||||
public string Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the email address of the user the application is trying to impersonate in the service
|
||||
/// account flow or <c>null</c>.
|
||||
/// </summary>
|
||||
public string User { get; set; }
|
||||
|
||||
/// <summary>Gets the scopes which indicate API access your application is requesting.</summary>
|
||||
public IEnumerable<string> Scopes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key which is used to sign the request, as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature.
|
||||
/// </summary>
|
||||
public RsaKey Key { get; set; }
|
||||
|
||||
/// <summary>Constructs a new initializer using the given id.</summary>
|
||||
public Initializer(string id)
|
||||
: this(id, GoogleAuthConsts.OidcTokenUrl) { }
|
||||
|
||||
/// <summary>Constructs a new initializer using the given id and the token server URL.</summary>
|
||||
public Initializer(string id, string tokenServerUrl) : base(tokenServerUrl)
|
||||
{
|
||||
Id = id;
|
||||
Scopes = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>Extracts the <see cref="Key"/> from the given PKCS8 private key.</summary>
|
||||
public Initializer FromPrivateKey(string privateKey)
|
||||
{
|
||||
RSAParameters rsaParameters = Pkcs8.DecodeRsaParameters(privateKey);
|
||||
Key = (RsaKey)RSA.Create();
|
||||
Key.ImportParameters(rsaParameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Extracts a <see cref="Key"/> from the given certificate.</summary>
|
||||
public Initializer FromCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
Key = certificate.GetRSAPrivateKey();
|
||||
#elif NET45
|
||||
// Workaround to correctly cast the private key as a RSACryptoServiceProvider type 24.
|
||||
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) certificate.PrivateKey;
|
||||
byte[] privateKeyBlob = rsa.ExportCspBlob(true);
|
||||
Key = new RSACryptoServiceProvider();
|
||||
Key.ImportCspBlob(privateKeyBlob);
|
||||
#elif DNX451
|
||||
// Workaround to correctly cast the private key as a RSACryptoServiceProvider type 24.
|
||||
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
|
||||
byte[] privateKeyBlob = rsa.ExportCspBlob(true);
|
||||
Key = new RSACryptoServiceProvider();
|
||||
Key.ImportCspBlob(privateKeyBlob);
|
||||
#else
|
||||
#error Unsupported target
|
||||
#endif
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Unix epoch as a <c>DateTime</c></summary>
|
||||
protected static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private readonly string id;
|
||||
private readonly string user;
|
||||
private readonly IEnumerable<string> scopes;
|
||||
private readonly RsaKey key;
|
||||
|
||||
/// <summary>Gets the service account ID (typically an e-mail address).</summary>
|
||||
public string Id { get { return id; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email address of the user the application is trying to impersonate in the service account flow
|
||||
/// or <c>null</c>.
|
||||
/// </summary>
|
||||
public string User { get { return user; } }
|
||||
|
||||
/// <summary>Gets the service account scopes.</summary>
|
||||
public IEnumerable<string> Scopes { get { return scopes; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key which is used to sign the request, as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature.
|
||||
/// </summary>
|
||||
public RsaKey Key { get { return key; } }
|
||||
|
||||
/// <summary><c>true</c> if this credential has any scopes associated with it.</summary>
|
||||
internal bool HasScopes { get { return scopes != null && scopes.Any(); } }
|
||||
|
||||
/// <summary>Constructs a new service account credential using the given initializer.</summary>
|
||||
public ServiceAccountCredential(Initializer initializer) : base(initializer)
|
||||
{
|
||||
id = initializer.Id.ThrowIfNullOrEmpty("initializer.Id");
|
||||
user = initializer.User;
|
||||
scopes = initializer.Scopes;
|
||||
key = initializer.Key.ThrowIfNull("initializer.Key");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ServiceAccountCredential"/> instance from JSON credential data.
|
||||
/// </summary>
|
||||
/// <param name="credentialData">The stream from which to read the JSON key data for a service account. Must not be null.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The <paramref name="credentialData"/> does not contain valid JSON service account key data.
|
||||
/// </exception>
|
||||
/// <returns>The credentials parsed from the service account key data.</returns>
|
||||
public static ServiceAccountCredential FromServiceAccountData(Stream credentialData)
|
||||
{
|
||||
var credential = GoogleCredential.FromStream(credentialData);
|
||||
var result = credential.UnderlyingCredential as ServiceAccountCredential;
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException("JSON data does not represent a valid service account credential.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests a new token as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#makingrequest.
|
||||
/// </summary>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
|
||||
/// <returns><c>true</c> if a new token was received successfully.</returns>
|
||||
public override async Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken)
|
||||
{
|
||||
// Create the request.
|
||||
var request = new GoogleAssertionTokenRequest()
|
||||
{
|
||||
Assertion = CreateAssertionFromPayload(CreatePayload())
|
||||
};
|
||||
|
||||
Logger.Debug("Request a new access token. Assertion data is: " + request.Assertion);
|
||||
|
||||
var newToken = await request.ExecuteAsync(HttpClient, TokenServerUrl, taskCancellationToken, Clock)
|
||||
.ConfigureAwait(false);
|
||||
Token = newToken;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an access token to authorize a request.
|
||||
/// If <paramref name="authUri"/> is set and this credential has no scopes associated
|
||||
/// with it, a locally signed JWT access token for given <paramref name="authUri"/>
|
||||
/// is returned. Otherwise, an OAuth2 access token obtained from token server will be returned.
|
||||
/// A cached token is used if possible and the token is only refreshed once it's close to its expiry.
|
||||
/// </summary>
|
||||
/// <param name="authUri">The URI the returned token will grant access to.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The access token.</returns>
|
||||
public override async Task<string> GetAccessTokenForRequestAsync(string authUri = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (!HasScopes && authUri != null)
|
||||
{
|
||||
// TODO(jtattermusch): support caching of JWT access tokens per authUri, currently a new
|
||||
// JWT access token is created each time, which can hurt performance.
|
||||
return CreateJwtAccessToken(authUri);
|
||||
}
|
||||
return await base.GetAccessTokenForRequestAsync(authUri, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JWT access token than can be used in request headers instead of an OAuth2 token.
|
||||
/// This is achieved by signing a special JWT using this service account's private key.
|
||||
/// <param name="authUri">The URI for which the access token will be valid.</param>
|
||||
/// </summary>
|
||||
private string CreateJwtAccessToken(string authUri)
|
||||
{
|
||||
var issuedDateTime = Clock.UtcNow;
|
||||
var issued = (int)(issuedDateTime - UnixEpoch).TotalSeconds;
|
||||
var payload = new JsonWebSignature.Payload()
|
||||
{
|
||||
Issuer = Id,
|
||||
Subject = Id,
|
||||
Audience = authUri,
|
||||
IssuedAtTimeSeconds = issued,
|
||||
ExpirationTimeSeconds = issued + 3600,
|
||||
};
|
||||
|
||||
return CreateAssertionFromPayload(payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs JWT token using the private key and returns the serialized assertion.
|
||||
/// </summary>
|
||||
/// <param name="payload">the JWT payload to sign.</param>
|
||||
private string CreateAssertionFromPayload(JsonWebSignature.Payload payload)
|
||||
{
|
||||
string serializedHeader = CreateSerializedHeader();
|
||||
string serializedPayload = NewtonsoftJsonSerializer.Instance.Serialize(payload);
|
||||
|
||||
var assertion = new StringBuilder();
|
||||
assertion.Append(UrlSafeBase64Encode(serializedHeader))
|
||||
.Append('.')
|
||||
.Append(UrlSafeBase64Encode(serializedPayload));
|
||||
var signature = CreateSignature(Encoding.ASCII.GetBytes(assertion.ToString()));
|
||||
assertion.Append('.') .Append(UrlSafeEncode(signature));
|
||||
return assertion.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a base64 encoded signature for the SHA-256 hash of the specified data.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to hash and sign. Must not be null.</param>
|
||||
/// <returns>The base-64 encoded signature.</returns>
|
||||
public string CreateSignature(byte[] data)
|
||||
{
|
||||
data.ThrowIfNull(nameof(data));
|
||||
|
||||
using (var hashAlg = SHA256.Create())
|
||||
{
|
||||
byte[] assertionHash = hashAlg.ComputeHash(data);
|
||||
#if NETSTANDARD1_3
|
||||
var sigBytes = key.SignHash(assertionHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
#elif NET45
|
||||
var sigBytes = key.SignHash(assertionHash, Sha256Oid);
|
||||
#elif DNX451
|
||||
var sigBytes = key.SignHash(assertionHash, Sha256Oid);
|
||||
#else
|
||||
#error Unsupported target
|
||||
#endif
|
||||
return Convert.ToBase64String(sigBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a serialized header as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingheader.
|
||||
/// </summary>
|
||||
private static string CreateSerializedHeader()
|
||||
{
|
||||
var header = new GoogleJsonWebSignature.Header()
|
||||
{
|
||||
Algorithm = "RS256",
|
||||
Type = "JWT"
|
||||
};
|
||||
|
||||
return NewtonsoftJsonSerializer.Instance.Serialize(header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a claim set as specified in
|
||||
/// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset.
|
||||
/// </summary>
|
||||
private GoogleJsonWebSignature.Payload CreatePayload()
|
||||
{
|
||||
var issued = (int)(Clock.UtcNow - UnixEpoch).TotalSeconds;
|
||||
return new GoogleJsonWebSignature.Payload()
|
||||
{
|
||||
Issuer = Id,
|
||||
Audience = TokenServerUrl,
|
||||
IssuedAtTimeSeconds = issued,
|
||||
ExpirationTimeSeconds = issued + 3600,
|
||||
Subject = User,
|
||||
Scope = String.Join(" ", Scopes)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Encodes the provided UTF8 string into an URL safe base64 string.</summary>
|
||||
/// <param name="value">Value to encode.</param>
|
||||
/// <returns>The URL safe base64 string.</returns>
|
||||
private string UrlSafeBase64Encode(string value)
|
||||
{
|
||||
return UrlSafeBase64Encode(Encoding.UTF8.GetBytes(value));
|
||||
}
|
||||
|
||||
/// <summary>Encodes the byte array into an URL safe base64 string.</summary>
|
||||
/// <param name="bytes">Byte array to encode.</param>
|
||||
/// <returns>The URL safe base64 string.</returns>
|
||||
private string UrlSafeBase64Encode(byte[] bytes)
|
||||
{
|
||||
return UrlSafeEncode(Convert.ToBase64String(bytes));
|
||||
}
|
||||
|
||||
/// <summary>Encodes the base64 string into an URL safe string.</summary>
|
||||
/// <param name="base64Value">The base64 string to make URL safe.</param>
|
||||
/// <returns>The URL safe base64 string.</returns>
|
||||
private string UrlSafeEncode(string base64Value)
|
||||
{
|
||||
return base64Value.Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
199
Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/UserCredential.cs
Normal file
199
Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/UserCredential.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
Copyright 2013 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.Flows;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Http;
|
||||
using Google.Apis.Logging;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 credential for accessing protected resources using an access token, as well as optionally refreshing
|
||||
/// the access token when it expires using a refresh token.
|
||||
/// </summary>
|
||||
public class UserCredential : ICredential, IHttpExecuteInterceptor, IHttpUnsuccessfulResponseHandler
|
||||
{
|
||||
/// <summary>Logger for this class.</summary>
|
||||
protected static readonly ILogger Logger = ApplicationContext.Logger.ForType<UserCredential>();
|
||||
|
||||
private TokenResponse token;
|
||||
private object lockObject = new object();
|
||||
|
||||
/// <summary>Gets or sets the token response which contains the access token.</summary>
|
||||
public TokenResponse Token
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
token = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the authorization code flow.</summary>
|
||||
public IAuthorizationCodeFlow Flow
|
||||
{
|
||||
get { return flow; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the user identity.</summary>
|
||||
public string UserId
|
||||
{
|
||||
get { return userId; }
|
||||
}
|
||||
|
||||
private readonly IAuthorizationCodeFlow flow;
|
||||
private readonly string userId;
|
||||
|
||||
/// <summary>Constructs a new credential instance.</summary>
|
||||
/// <param name="flow">Authorization code flow.</param>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="token">An initial token for the user.</param>
|
||||
public UserCredential(IAuthorizationCodeFlow flow, string userId, TokenResponse token)
|
||||
{
|
||||
this.flow = flow;
|
||||
this.userId = userId;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
#region IHttpExecuteInterceptor
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation is to try to refresh the access token if there is no access token or if we are 1
|
||||
/// minute away from expiration. If token server is unavailable, it will try to use the access token even if
|
||||
/// has expired. If successful, it will call <see cref="IAccessMethod.Intercept"/>.
|
||||
/// </summary>
|
||||
public async Task InterceptAsync(HttpRequestMessage request, CancellationToken taskCancellationToken)
|
||||
{
|
||||
var accessToken = await GetAccessTokenForRequestAsync(request.RequestUri.ToString(), taskCancellationToken).ConfigureAwait(false);
|
||||
flow.AccessMethod.Intercept(request, Token.AccessToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IHttpUnsuccessfulResponseHandler
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args)
|
||||
{
|
||||
// TODO(peleyal): check WWW-Authenticate header.
|
||||
if (args.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
return !Object.Equals(Token.AccessToken, flow.AccessMethod.GetAccessToken(args.Request))
|
||||
|| await RefreshTokenAsync(args.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IConfigurableHttpClientInitializer
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(ConfigurableHttpClient httpClient)
|
||||
{
|
||||
httpClient.MessageHandler.AddExecuteInterceptor(this);
|
||||
httpClient.MessageHandler.AddUnsuccessfulResponseHandler(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITokenAccess implementation
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<string> GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (Token.IsExpired(flow.Clock))
|
||||
{
|
||||
Logger.Debug("Token has expired, trying to refresh it.");
|
||||
if (!await RefreshTokenAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
throw new InvalidOperationException("The access token has expired but we can't refresh it");
|
||||
}
|
||||
}
|
||||
return token.AccessToken;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the token by calling to
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.Flows.IAuthorizationCodeFlow.RefreshTokenAsync"/>.
|
||||
/// Then it updates the <see cref="TokenResponse"/> with the new token instance.
|
||||
/// </summary>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation.</param>
|
||||
/// <returns><c>true</c> if the token was refreshed.</returns>
|
||||
public async Task<bool> RefreshTokenAsync(CancellationToken taskCancellationToken)
|
||||
{
|
||||
if (Token.RefreshToken == null)
|
||||
{
|
||||
Logger.Warning("Refresh token is null, can't refresh the token!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// It's possible that two concurrent calls will be made to refresh the token, in that case the last one
|
||||
// will win.
|
||||
var newToken = await flow.RefreshTokenAsync(userId, Token.RefreshToken, taskCancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Logger.Info("Access token was refreshed successfully");
|
||||
|
||||
if (newToken.RefreshToken == null)
|
||||
{
|
||||
newToken.RefreshToken = Token.RefreshToken;
|
||||
}
|
||||
|
||||
Token = newToken;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously revokes the token by calling
|
||||
/// <see cref="Google.Apis.Auth.OAuth2.Flows.IAuthorizationCodeFlow.RevokeTokenAsync"/>.
|
||||
/// </summary>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation.</param>
|
||||
/// <returns><c>true</c> if the token was revoked successfully.</returns>
|
||||
public async Task<bool> RevokeTokenAsync(CancellationToken taskCancellationToken)
|
||||
{
|
||||
if (Token == null)
|
||||
{
|
||||
Logger.Warning("Token is already null, no need to revoke it.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await flow.RevokeTokenAsync(userId, Token.AccessToken, taskCancellationToken).ConfigureAwait(false);
|
||||
Logger.Info("Access token was revoked successfully");
|
||||
// We don't set the token to null, cause we want that the next request (without reauthorizing) will fail).
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Util.Store;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Web
|
||||
{
|
||||
/// <summary>Auth Utility methods for web development.</summary>
|
||||
public class AuthWebUtility
|
||||
{
|
||||
/// <summary>Extracts the redirect URI from the state OAuth2 parameter.</summary>
|
||||
/// <remarks>
|
||||
/// If the data store is not <c>null</c>, this method verifies that the state parameter which was returned
|
||||
/// from the authorization server is the same as the one we set before redirecting to the authorization server.
|
||||
/// </remarks>
|
||||
/// <param name="dataStore">The data store which contains the original state parameter.</param>
|
||||
/// <param name="userId">User identifier.</param>
|
||||
/// <param name="state">
|
||||
/// The authorization state parameter which we got back from the authorization server.
|
||||
/// </param>
|
||||
/// <returns>Redirect URI to the address which initializes the authorization code flow.</returns>
|
||||
public static async Task<string> ExtracRedirectFromState(IDataStore dataStore, string userId, string state)
|
||||
{
|
||||
var oauthState = state;
|
||||
if (dataStore != null)
|
||||
{
|
||||
var userKey = AuthorizationCodeWebApp.StateKey + userId;
|
||||
var expectedState = await dataStore.GetAsync<string>(userKey).ConfigureAwait(false);
|
||||
|
||||
// Verify that the stored state is equal to the one we got back from the authorization server.
|
||||
if (!Object.Equals(oauthState, expectedState))
|
||||
{
|
||||
throw new TokenResponseException(new TokenErrorResponse
|
||||
{
|
||||
Error = "State is invalid"
|
||||
});
|
||||
}
|
||||
await dataStore.DeleteAsync<string>(userKey).ConfigureAwait(false);
|
||||
oauthState = oauthState.Substring(0, oauthState.Length - AuthorizationCodeWebApp.StateRandomLength);
|
||||
}
|
||||
|
||||
return oauthState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
Copyright 2013 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
using Google.Apis.Auth.OAuth2.Requests;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
|
||||
namespace Google.Apis.Auth.OAuth2.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread safe OAuth 2.0 authorization code flow for a web application that persists end-user credentials.
|
||||
/// </summary>
|
||||
public class AuthorizationCodeWebApp
|
||||
{
|
||||
/// <summary>
|
||||
/// The state key. As part of making the request for authorization code we save the original request to verify
|
||||
/// that this server create the original request.
|
||||
/// </summary>
|
||||
public const string StateKey = "oauth_";
|
||||
|
||||
/// <summary>The length of the random number which will be added to the end of the state parameter.</summary>
|
||||
public const int StateRandomLength = 8;
|
||||
|
||||
/// <summary>
|
||||
/// AuthResult which contains the user's credentials if it was loaded successfully from the store. Otherwise
|
||||
/// it contains the redirect URI for the authorization server.
|
||||
/// </summary>
|
||||
public class AuthResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the user's credentials or <c>null</c> in case the end user needs to authorize.
|
||||
/// </summary>
|
||||
public UserCredential Credential { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the redirect URI to for the user to authorize against the authorization server or
|
||||
/// <c>null</c> in case the <see cref="Google.Apis.Auth.OAuth2.UserCredential"/> was loaded from the data
|
||||
/// store.
|
||||
/// </summary>
|
||||
public string RedirectUri { get; set; }
|
||||
}
|
||||
|
||||
private readonly IAuthorizationCodeFlow flow;
|
||||
private readonly string redirectUri;
|
||||
private readonly string state;
|
||||
|
||||
/// <summary>Gets the authorization code flow.</summary>
|
||||
public IAuthorizationCodeFlow Flow
|
||||
{
|
||||
get { return flow; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the OAuth2 callback redirect URI.</summary>
|
||||
public string RedirectUri
|
||||
{
|
||||
get { return redirectUri; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the state which is used to navigate back to the page that started the OAuth flow.</summary>
|
||||
public string State
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new authorization code installed application with the given flow and code receiver.
|
||||
/// </summary>
|
||||
public AuthorizationCodeWebApp(IAuthorizationCodeFlow flow, string redirectUri, string state)
|
||||
{
|
||||
// TODO(peleyal): Provide a way to disable to random number in the end of the state parameter.
|
||||
this.flow = flow;
|
||||
this.redirectUri = redirectUri;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/// <summary>Asynchronously authorizes the web application to access user's protected data.</summary>
|
||||
/// <param name="userId">User identifier</param>
|
||||
/// <param name="taskCancellationToken">Cancellation token to cancel an operation</param>
|
||||
/// <returns>
|
||||
/// Auth result object which contains the user's credential or redirect URI for the authorization server
|
||||
/// </returns>
|
||||
public async Task<AuthResult> AuthorizeAsync(string userId, CancellationToken taskCancellationToken)
|
||||
{
|
||||
// Try to load a token from the data store.
|
||||
var token = await Flow.LoadTokenAsync(userId, taskCancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Check if a new authorization code is needed.
|
||||
if (ShouldRequestAuthorizationCode(token))
|
||||
{
|
||||
// Create an authorization code request.
|
||||
AuthorizationCodeRequestUrl codeRequest = Flow.CreateAuthorizationCodeRequest(redirectUri);
|
||||
|
||||
// Add a random number to the end of the state so we can indicate the original request was made by this
|
||||
// call.
|
||||
var oauthState = state;
|
||||
if (Flow.DataStore != null)
|
||||
{
|
||||
var rndString = new string('9', StateRandomLength);
|
||||
var random = new Random().Next(int.Parse(rndString)).ToString("D" + StateRandomLength);
|
||||
oauthState += random;
|
||||
await Flow.DataStore.StoreAsync(StateKey + userId, oauthState).ConfigureAwait(false);
|
||||
}
|
||||
codeRequest.State = oauthState;
|
||||
|
||||
return new AuthResult { RedirectUri = codeRequest.Build().ToString() };
|
||||
}
|
||||
|
||||
return new AuthResult { Credential = new UserCredential(flow, userId, token) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the need for retrieval of a new authorization code, based on the given token and the
|
||||
/// authorization code flow.
|
||||
/// </summary>
|
||||
public bool ShouldRequestAuthorizationCode(TokenResponse token)
|
||||
{
|
||||
// TODO: This code should be shared between this class and AuthorizationCodeInstalledApp.
|
||||
// If the flow includes a parameter that requires a new token, if the stored token is null or it doesn't
|
||||
// have a refresh token and the access token is expired we need to retrieve a new authorization code.
|
||||
return Flow.ShouldForceTokenRetrieval() || token == null || (token.RefreshToken == null
|
||||
&& token.IsExpired(flow.Clock));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue