diff --git a/Yavsc/ApiControllers/ActivityApiController.cs b/Yavsc/ApiControllers/ActivityApiController.cs
index 0b6394d4..fea1771c 100644
--- a/Yavsc/ApiControllers/ActivityApiController.cs
+++ b/Yavsc/ApiControllers/ActivityApiController.cs
@@ -9,6 +9,7 @@ using Microsoft.Data.Entity;
using Yavsc.Models;
using Yavsc.Models.Workflow;
+
namespace Yavsc.Controllers
{
[Produces("application/json")]
diff --git a/Yavsc/Controllers/HomeController.cs b/Yavsc/Controllers/HomeController.cs
index 84692ad1..42b8ff65 100644
--- a/Yavsc/Controllers/HomeController.cs
+++ b/Yavsc/Controllers/HomeController.cs
@@ -120,6 +120,8 @@ namespace Yavsc.Controllers
}
public IActionResult Todo()
{
+ User.GetUserId();
+
return View();
}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/GoogleJsonWebSignature.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/GoogleJsonWebSignature.cs
new file mode 100644
index 00000000..b1921499
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/GoogleJsonWebSignature.cs
@@ -0,0 +1,236 @@
+/*
+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;
+using Google.Apis.Json;
+using Google.Apis.Util;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Google.Apis.Auth
+{
+ ///
+ /// Google JSON Web Signature as specified in https://developers.google.com/accounts/docs/OAuth2ServiceAccount.
+ ///
+ public class GoogleJsonWebSignature
+ {
+
+ internal const int MaxJwtLength = 10000;
+ internal readonly static TimeSpan CertCacheRefreshInterval = TimeSpan.FromHours(1);
+
+ // See http://oid-info.com/get/2.16.840.1.101.3.4.2.1
+ private const string Sha256Oid = "2.16.840.1.101.3.4.2.1";
+
+ private const string SupportedJwtAlgorithm = "RS256";
+
+ private static readonly IEnumerable ValidJwtIssuers = new[]
+ {
+ "https://accounts.google.com",
+ "accounts.google.com"
+ };
+
+ private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ ///
+ /// Validates a Google-issued Json Web Token (JWT).
+ /// With throw a if the passed value is not valid JWT signed by Google.
+ ///
+ ///
+ /// Follows the procedure to
+ /// validate a JWT ID token.
+ ///
+ /// Google certificates are cached, and refreshed once per hour. This can be overridden by setting
+ /// to true.
+ ///
+ /// The JWT to validate.
+ /// Optional. The to use for JWT expiration verification. Defaults to the system clock.
+ /// Optional. If true forces new certificates to be downloaded from Google. Defaults to false.
+ /// The JWT payload, if the JWT is valid. Throws an otherwise.
+ /// Thrown when passed a JWT that is not a valid JWT signed by Google.
+ public static Task ValidateAsync(string jwt, IClock clock = null, bool forceGoogleCertRefresh = false) =>
+ ValidateInternalAsync(jwt, clock ?? SystemClock.Default, forceGoogleCertRefresh, null);
+
+ // internal for testing
+ internal static async Task ValidateInternalAsync(string jwt, IClock clock, bool forceGoogleCertRefresh, string certsJson)
+ {
+ // Check arguments
+ jwt.ThrowIfNull(nameof(jwt));
+ jwt.ThrowIfNullOrEmpty(nameof(jwt));
+ if (jwt.Length > MaxJwtLength)
+ {
+ throw new InvalidJwtException($"JWT exceeds maximum allowed length of {MaxJwtLength}");
+ }
+ var parts = jwt.Split('.');
+ if (parts.Length != 3)
+ {
+ throw new InvalidJwtException($"JWT must consist of Header, Payload, and Signature");
+ }
+
+ // Decode the three parts of the JWT: header.payload.signature
+ Header header = NewtonsoftJsonSerializer.Instance.Deserialize(Base64UrlToString(parts[0]));
+ Payload payload = NewtonsoftJsonSerializer.Instance.Deserialize(Base64UrlToString(parts[1]));
+ byte[] signature = Base64UrlDecode(parts[2]);
+
+ // Verify algorithm in JWT
+ if (header.Algorithm != SupportedJwtAlgorithm)
+ {
+ throw new InvalidJwtException($"JWT algorithm must be '{SupportedJwtAlgorithm}'");
+ }
+
+ // Verify signature
+ byte[] hash;
+ using (var hashAlg = SHA256.Create())
+ {
+ hash = hashAlg.ComputeHash(Encoding.ASCII.GetBytes($"{parts[0]}.{parts[1]}"));
+ }
+ bool verifiedOk = false;
+ foreach (var googleCert in await GetGoogleCertsAsync(clock, forceGoogleCertRefresh, certsJson))
+ {
+#if NET45
+ verifiedOk = ((RSACryptoServiceProvider)googleCert).VerifyHash(hash, Sha256Oid, signature);
+#elif DNX451
+ verifiedOk = ((RSACryptoServiceProvider)googleCert).VerifyHash(hash, Sha256Oid, signature);
+#elif NETSTANDARD1_3
+ verifiedOk = googleCert.VerifyHash(hash, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+#else
+#error Unsupported platform
+#endif
+ if (verifiedOk)
+ {
+ break;
+ }
+ }
+ if (!verifiedOk)
+ {
+ throw new InvalidJwtException("JWT invalid: unable to verify signature.");
+ }
+
+ // Verify iss, iat and exp claims
+ if (!ValidJwtIssuers.Contains(payload.Issuer))
+ {
+ var validList = string.Join(", ", ValidJwtIssuers.Select(x => $"'{x}'"));
+ throw new InvalidJwtException($"JWT issuer incorrect. Must be one of: {validList}");
+ }
+ if (payload.IssuedAtTimeSeconds == null || payload.ExpirationTimeSeconds == null)
+ {
+ throw new InvalidJwtException("JWT must contain 'iat' and 'exp' claims");
+ }
+ var nowSeconds = (clock.UtcNow - UnixEpoch).TotalSeconds;
+ if (nowSeconds < payload.IssuedAtTimeSeconds.Value)
+ {
+ throw new InvalidJwtException("JWT is not yet valid.");
+ }
+ if (nowSeconds > payload.ExpirationTimeSeconds.Value)
+ {
+ throw new InvalidJwtException("JWT has expired.");
+ }
+
+ // All verification passed, return payload.
+ return payload;
+ }
+
+ private static string Base64UrlToString(string base64Url) => Encoding.UTF8.GetString(Base64UrlDecode(base64Url));
+
+ private static byte[] Base64UrlDecode(string base64Url)
+ {
+ var base64 = base64Url.Replace('-', '+').Replace('_', '/');
+ switch (base64.Length % 4)
+ {
+ case 2: base64 += "=="; break;
+ case 3: base64 += "="; break;
+ }
+ return Convert.FromBase64String(base64);
+ }
+
+ private static SemaphoreSlim _certCacheLock = new SemaphoreSlim(1);
+ private static DateTime _certCacheDownloadTime;
+ private static List _certCache;
+
+ // internal for testing
+ internal static async Task> GetGoogleCertsAsync(IClock clock, bool forceGoogleCertRefresh, string certsJson)
+ {
+ var now = clock.UtcNow;
+ await _certCacheLock.WaitAsync();
+ try
+ {
+ if (forceGoogleCertRefresh || _certCache == null || (_certCacheDownloadTime + CertCacheRefreshInterval) < now)
+ {
+ using (var httpClient = new HttpClient())
+ {
+ // certsJson used for unit tests
+ if (certsJson == null)
+ {
+ certsJson = await httpClient.GetStringAsync(GoogleAuthConsts.JsonWebKeySetUrl);
+ }
+ }
+ _certCache = GetGoogleCertsFromJson(certsJson);
+ _certCacheDownloadTime = now;
+ }
+ return _certCache;
+ }
+ finally
+ {
+ _certCacheLock.Release();
+ }
+ }
+
+ private static List GetGoogleCertsFromJson(string json) =>
+ JToken.Parse(json)["keys"].AsEnumerable().Select(key =>
+ {
+ var rsa = RSA.Create();
+ rsa.ImportParameters(new RSAParameters
+ {
+ Modulus = Base64UrlDecode((string)key["n"]),
+ Exponent = Base64UrlDecode((string)key["e"]),
+ });
+ return rsa;
+ })
+ .ToList();
+
+ ///
+ /// The header as specified in https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingheader.
+ ///
+ public class Header : JsonWebSignature.Header
+ {
+ }
+
+ ///
+ /// The payload as specified in
+ /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset.
+ ///
+ public class Payload : JsonWebSignature.Payload
+ {
+ ///
+ /// a space-delimited list of the permissions the application requests or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("scope")]
+ public string Scope { get; set; }
+
+ ///
+ /// The email address of the user for which the application is requesting delegated access.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("prn")]
+ public string Prn { get; set; }
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/InvalidJwtException.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/InvalidJwtException.cs
new file mode 100644
index 00000000..ef5879c2
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/InvalidJwtException.cs
@@ -0,0 +1,32 @@
+/*
+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;
+
+namespace Google.Apis.Auth
+{
+ ///
+ /// An exception that is thrown when a Json Web Token (JWT) is invalid.
+ ///
+ public class InvalidJwtException : Exception
+ {
+ ///
+ /// Initializes a new InvalidJwtException instanc e with the specified error message.
+ ///
+ /// The error message that explains why the JWT was invalid.
+ public InvalidJwtException(string message) : base(message) { }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/JsonWebSignature.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/JsonWebSignature.cs
new file mode 100644
index 00000000..61e7fd2d
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/JsonWebSignature.cs
@@ -0,0 +1,100 @@
+/*
+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.Collections.Generic;
+
+namespace Google.Apis.Auth
+{
+ ///
+ /// JSON Web Signature (JWS) implementation as specified in
+ /// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-11.
+ ///
+ public class JsonWebSignature
+ {
+ // TODO(peleyal): Implement some verify method:
+ // http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-08#section-7
+
+ ///
+ /// Header as specified in http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-11#section-4.1.
+ ///
+ public class Header : JsonWebToken.Header
+ {
+ ///
+ /// Gets or set the algorithm header parameter that identifies the cryptographic algorithm used to secure
+ /// the JWS or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("alg")]
+ public string Algorithm { get; set; }
+
+ ///
+ /// Gets or sets the JSON Web Key URL header parameter that is an absolute URL that refers to a resource
+ /// for a set of JSON-encoded public keys, one of which corresponds to the key that was used to digitally
+ /// sign the JWS or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("jku")]
+ public string JwkUrl { get; set; }
+
+ ///
+ /// Gets or sets JSON Web Key header parameter that is a public key that corresponds to the key used to
+ /// digitally sign the JWS or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("jwk")]
+ public string Jwk { get; set; }
+
+ ///
+ /// Gets or sets key ID header parameter that is a hint indicating which specific key owned by the signer
+ /// should be used to validate the digital signature or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("kid")]
+ public string KeyId { get; set; }
+
+ ///
+ /// Gets or sets X.509 URL header parameter that is an absolute URL that refers to a resource for the X.509
+ /// public key certificate or certificate chain corresponding to the key used to digitally sign the JWS or
+ /// null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("x5u")]
+ public string X509Url { get; set; }
+
+ ///
+ /// Gets or sets X.509 certificate thumb print header parameter that provides a base64url encoded SHA-1
+ /// thumb-print (a.k.a. digest) of the DER encoding of an X.509 certificate that can be used to match the
+ /// certificate or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("x5t")]
+ public string X509Thumbprint { get; set; }
+
+ ///
+ /// Gets or sets X.509 certificate chain header parameter contains the X.509 public key certificate or
+ /// certificate chain corresponding to the key used to digitally sign the JWS or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("x5c")]
+ public string X509Certificate { get; set; }
+
+ ///
+ /// Gets or sets array listing the header parameter names that define extensions that are used in the JWS
+ /// header that MUST be understood and processed or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("crit")]
+ public IList critical { get; set; }
+ }
+
+ /// JWS Payload.
+ public class Payload : JsonWebToken.Payload
+ {
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/JsonWebToken.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/JsonWebToken.cs
new file mode 100644
index 00000000..64b1a8f5
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/JsonWebToken.cs
@@ -0,0 +1,127 @@
+/*
+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.Collections.Generic;
+
+namespace Google.Apis.Auth
+{
+ ///
+ /// JSON Web Token (JWT) implementation as specified in
+ /// http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-08.
+ ///
+ public class JsonWebToken
+ {
+ ///
+ /// JWT Header as specified in http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-08#section-5.
+ ///
+ public class Header
+ {
+ ///
+ /// Gets or sets type header parameter used to declare the type of this object or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("typ")]
+ public string Type { get; set; }
+
+ ///
+ /// Gets or sets content type header parameter used to declare structural information about the JWT or
+ /// null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("cty")]
+ public string ContentType { get; set; }
+ }
+
+ ///
+ /// JWT Payload as specified in http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-08#section-4.1.
+ ///
+ public class Payload
+ {
+ ///
+ /// Gets or sets issuer claim that identifies the principal that issued the JWT or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("iss")]
+ public string Issuer { get; set; }
+
+ ///
+ /// Gets or sets subject claim identifying the principal that is the subject of the JWT or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("sub")]
+ public string Subject { get; set; }
+
+ ///
+ /// Gets or sets audience claim that identifies the audience that the JWT is intended for (should either be
+ /// a string or list) or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("aud")]
+ public object Audience { get; set; }
+
+ ///
+ /// Gets or sets expiration time claim that identifies the expiration time (in seconds) on or after which
+ /// the token MUST NOT be accepted for processing or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("exp")]
+ public long? ExpirationTimeSeconds { get; set; }
+
+ ///
+ /// Gets or sets not before claim that identifies the time (in seconds) before which the token MUST NOT be
+ /// accepted for processing or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("nbf")]
+ public long? NotBeforeTimeSeconds { get; set; }
+
+ ///
+ /// Gets or sets issued at claim that identifies the time (in seconds) at which the JWT was issued or
+ /// null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("iat")]
+ public long? IssuedAtTimeSeconds { get; set; }
+
+ ///
+ /// Gets or sets JWT ID claim that provides a unique identifier for the JWT or null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("jti")]
+ public string JwtId { get; set; }
+
+ ///
+ /// Gets or sets type claim that is used to declare a type for the contents of this JWT Claims Set or
+ /// null.
+ ///
+ [Newtonsoft.Json.JsonPropertyAttribute("typ")]
+ public string Type { get; set; }
+
+ /// Gets the audience property as a list.
+ [Newtonsoft.Json.JsonIgnoreAttribute]
+ public IEnumerable AudienceAsList
+ {
+ get
+ {
+ var asList = Audience as List;
+ if (asList != null)
+ {
+ return asList;
+ }
+ var list = new List();
+ var asString = Audience as string;
+ if (asString != null)
+ {
+ list.Add(asString);
+ }
+
+ return list;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/AuthorizationCodeInstalledApp.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/AuthorizationCodeInstalledApp.cs
new file mode 100644
index 00000000..cab74391
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/AuthorizationCodeInstalledApp.cs
@@ -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
+{
+ ///
+ /// Thread-safe OAuth 2.0 authorization code flow for an installed application that persists end-user credentials.
+ ///
+ ///
+ /// Incremental authorization (https://developers.google.com/+/web/api/rest/oauth) is currently not supported
+ /// for Installed Apps.
+ ///
+ public class AuthorizationCodeInstalledApp : IAuthorizationCodeInstalledApp
+ {
+ private static readonly ILogger Logger = ApplicationContext.Logger.ForType();
+
+ private readonly IAuthorizationCodeFlow flow;
+ private readonly ICodeReceiver codeReceiver;
+
+ ///
+ /// Constructs a new authorization code installed application with the given flow and code receiver.
+ ///
+ public AuthorizationCodeInstalledApp(IAuthorizationCodeFlow flow, ICodeReceiver codeReceiver)
+ {
+ this.flow = flow;
+ this.codeReceiver = codeReceiver;
+ }
+
+ #region IAuthorizationCodeInstalledApp Members
+
+ /// Gets the authorization code flow.
+ public IAuthorizationCodeFlow Flow
+ {
+ get { return flow; }
+ }
+
+ /// Gets the code receiver which is responsible for receiving the authorization code.
+ public ICodeReceiver CodeReceiver
+ {
+ get { return codeReceiver; }
+ }
+
+ ///
+ public async Task 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);
+ }
+
+ ///
+ /// Determines the need for retrieval of a new authorization code, based on the given token and the
+ /// authorization code flow.
+ ///
+ 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
+ }
+}
\ No newline at end of file
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/BearerToken.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/BearerToken.cs
new file mode 100644
index 00000000..5507188d
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/BearerToken.cs
@@ -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
+{
+ ///
+ /// OAuth 2.0 helper for accessing protected resources using the Bearer token as specified in
+ /// http://tools.ietf.org/html/rfc6750.
+ ///
+ public class BearerToken
+ {
+ ///
+ /// 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.
+ ///
+ public class AuthorizationHeaderAccessMethod : IAccessMethod
+ {
+ const string Schema = "Bearer";
+
+ ///
+ public void Intercept(HttpRequestMessage request, string accessToken)
+ {
+ request.Headers.Authorization = new AuthenticationHeaderValue(Schema, accessToken);
+ }
+
+ ///
+ public string GetAccessToken(HttpRequestMessage request)
+ {
+ if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme == Schema)
+ {
+ return request.Headers.Authorization.Parameter;
+ }
+ return null;
+ }
+ }
+
+ ///
+ /// Thread-safe OAuth 2.0 method for accessing protected resources using an access_token query parameter
+ /// as specified in http://tools.ietf.org/html/rfc6750#section-2.3.
+ ///
+ public class QueryParameterAccessMethod : IAccessMethod
+ {
+ const string AccessTokenKey = "access_token";
+
+ ///
+ 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)));
+ }
+
+ ///
+ 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;
+ }
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ClientSecrets.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ClientSecrets.cs
new file mode 100644
index 00000000..f3afa5b2
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ClientSecrets.cs
@@ -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
+{
+ /// Client credential details for installed and web applications.
+ public sealed class ClientSecrets
+ {
+ /// Gets or sets the client identifier.
+ [Newtonsoft.Json.JsonProperty("client_id")]
+ public string ClientId { get; set; }
+
+ /// Gets or sets the client Secret.
+ [Newtonsoft.Json.JsonProperty("client_secret")]
+ public string ClientSecret { get; set; }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ComputeCredential.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ComputeCredential.cs
new file mode 100644
index 00000000..30728348
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ComputeCredential.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ /// More details about Compute Engine authentication is available at:
+ /// https://cloud.google.com/compute/docs/authentication.
+ ///
+ ///
+ public class ComputeCredential : ServiceCredential
+ {
+ /// The metadata server url.
+ public const string MetadataServerUrl = "http://metadata.google.internal";
+
+ /// Caches result from first call to IsRunningOnComputeEngine
+ private readonly static Lazy> isRunningOnComputeEngineCached = new Lazy>(
+ () => IsRunningOnComputeEngineNoCache());
+
+ ///
+ /// Experimentally, 200ms was found to be 99.9999% reliable.
+ /// This is a conservative timeout to minimize hanging on some troublesome network.
+ ///
+ private const int MetadataServerPingTimeoutInMilliseconds = 1000;
+
+ /// The Metadata flavor header name.
+ private const string MetadataFlavor = "Metadata-Flavor";
+
+ /// The Metadata header response indicating Google.
+ 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.";
+
+ ///
+ /// An initializer class for the Compute credential. It uses
+ /// as the token server URL.
+ ///
+ new public class Initializer : ServiceCredential.Initializer
+ {
+ /// Constructs a new initializer using the default compute token URL.
+ public Initializer()
+ : this(GoogleAuthConsts.ComputeTokenUrl) {}
+
+ /// Constructs a new initializer using the given token URL.
+ public Initializer(string tokenUrl)
+ : base(tokenUrl) {}
+ }
+
+ /// Constructs a new Compute credential instance.
+ public ComputeCredential() : this(new Initializer()) { }
+
+ /// Constructs a new Compute credential instance.
+ public ComputeCredential(Initializer initializer) : base(initializer) { }
+
+ #region ServiceCredential overrides
+
+ ///
+ public override async Task 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
+
+ ///
+ /// 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.
+ ///
+ public static Task IsRunningOnComputeEngine()
+ {
+ return isRunningOnComputeEngineCached.Value;
+ }
+
+ private static async Task 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 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;
+ }
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs
new file mode 100644
index 00000000..3240a36d
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs
@@ -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.
+
+ ///
+ /// 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.
+ ///
+ internal class DefaultCredentialProvider
+ {
+ private static readonly ILogger Logger = ApplicationContext.Logger.ForType();
+
+ ///
+ /// Environment variable override which stores the default application credentials file path.
+ ///
+ public const string CredentialEnvironmentVariable = "GOOGLE_APPLICATION_CREDENTIALS";
+
+ /// Well known file which stores the default application credentials.
+ private const string WellKnownCredentialsFile = "application_default_credentials.json";
+
+ /// Environment variable which contains the Application Data settings.
+ private const string AppdataEnvironmentVariable = "APPDATA";
+
+ /// Environment variable which contains the location of home directory on UNIX systems.
+ private const string HomeEnvironmentVariable = "HOME";
+
+ /// GCloud configuration directory in Windows, relative to %APPDATA%.
+ private const string CloudSDKConfigDirectoryWindows = "gcloud";
+
+ /// Help link to the application default credentials feature.
+ private const string HelpPermalink =
+ "https://developers.google.com/accounts/docs/application-default-credentials";
+
+ /// GCloud configuration directory on Linux/Mac, relative to $HOME.
+ private static readonly string CloudSDKConfigDirectoryUnix = Path.Combine(".config", "gcloud");
+
+ /// Caches result from first call to GetApplicationDefaultCredentialAsync
+ private readonly Lazy> cachedCredentialTask;
+
+ /// Constructs a new default credential provider.
+ public DefaultCredentialProvider()
+ {
+ cachedCredentialTask = new Lazy>(CreateDefaultCredentialAsync);
+ }
+
+ ///
+ /// Returns the Application Default Credentials. Subsequent invocations return cached value from
+ /// first invocation.
+ /// See for details.
+ ///
+ public Task GetDefaultCredentialAsync()
+ {
+ return cachedCredentialTask.Value;
+ }
+
+ /// Creates a new default credential.
+ private async Task 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));
+ }
+
+ /// Creates a default credential from a JSON file.
+ private GoogleCredential CreateDefaultCredentialFromFile(string credentialPath)
+ {
+ Logger.Debug("Loading Credential from file {0}", credentialPath);
+
+ using (Stream stream = GetStream(credentialPath))
+ {
+ return CreateDefaultCredentialFromStream(stream);
+ }
+ }
+
+ /// Creates a default credential from a stream that contains JSON credential data.
+ internal GoogleCredential CreateDefaultCredentialFromStream(Stream stream)
+ {
+ JsonCredentialParameters credentialParameters;
+ try
+ {
+ credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize(stream);
+ }
+ catch (Exception e)
+ {
+ throw new InvalidOperationException("Error deserializing JSON credential data.", e);
+ }
+ return CreateDefaultCredentialFromParameters(credentialParameters);
+ }
+
+ /// Creates a default credential from a string that contains JSON credential data.
+ internal GoogleCredential CreateDefaultCredentialFromJson(string json)
+ {
+ JsonCredentialParameters credentialParameters;
+ try
+ {
+ credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize(json);
+ }
+ catch (Exception e)
+ {
+ throw new InvalidOperationException("Error deserializing JSON credential data.", e);
+ }
+ return CreateDefaultCredentialFromParameters(credentialParameters);
+ }
+
+
+ /// Creates a default credential from JSON data.
+ 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));
+ }
+ }
+
+ /// Creates a user credential from JSON data.
+ 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);
+ }
+
+ /// Creates a from JSON data.
+ 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));
+ }
+
+ ///
+ /// Returns platform-specific well known credential file path. This file is created by
+ /// gcloud auth login
+ ///
+ 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);
+ }
+
+ ///
+ /// Gets the environment variable.
+ /// This method is protected so it could be overriden for testing purposes only.
+ ///
+ protected virtual string GetEnvironmentVariable(string variableName)
+ {
+ return Environment.GetEnvironmentVariable(variableName);
+ }
+
+ ///
+ /// Opens file as a stream.
+ /// This method is protected so it could be overriden for testing purposes only.
+ ///
+ protected virtual Stream GetStream(string filePath)
+ {
+ return new FileStream(filePath, FileMode.Open, FileAccess.Read);
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs
new file mode 100644
index 00000000..b2cef8b7
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs
@@ -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
+{
+ ///
+ /// Thread-safe OAuth 2.0 authorization code flow that manages and persists end-user credentials.
+ ///
+ /// 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.
+ ///
+ ///
+ public class AuthorizationCodeFlow : IAuthorizationCodeFlow
+ {
+ private static readonly ILogger Logger = ApplicationContext.Logger.ForType();
+
+ #region Initializer
+
+ /// An initializer class for the authorization code flow.
+ public class Initializer
+ {
+ ///
+ /// Gets or sets the method for presenting the access token to the resource server.
+ /// The default value is
+ /// .
+ ///
+ public IAccessMethod AccessMethod { get; set; }
+
+ /// Gets the token server URL.
+ public string TokenServerUrl { get; private set; }
+
+ /// Gets or sets the authorization server URL.
+ public string AuthorizationServerUrl { get; private set; }
+
+ /// Gets or sets the client secrets which includes the client identifier and its secret.
+ public ClientSecrets ClientSecrets { get; set; }
+
+ ///
+ /// Gets or sets the client secrets stream which contains the client identifier and its secret.
+ ///
+ /// The AuthorizationCodeFlow constructor is responsible for disposing the stream.
+ public Stream ClientSecretsStream { get; set; }
+
+ /// Gets or sets the data store used to store the token response.
+ public IDataStore DataStore { get; set; }
+
+ ///
+ /// Gets or sets the scopes which indicate the API access your application is requesting.
+ ///
+ public IEnumerable Scopes { get; set; }
+
+ ///
+ /// Gets or sets the factory for creating instance.
+ ///
+ public IHttpClientFactory HttpClientFactory { get; set; }
+
+ ///
+ /// Get or sets the exponential back-off policy. Default value is UnsuccessfulResponse503, which
+ /// means that exponential back-off is used on 503 abnormal HTTP responses.
+ /// If the value is set to None, no exponential back-off policy is used, and it's up to user to
+ /// configure the in an
+ /// to set a specific back-off
+ /// implementation (using ).
+ ///
+ public ExponentialBackOffPolicy DefaultExponentialBackOffPolicy { get; set; }
+
+ ///
+ /// 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 .
+ ///
+ public IClock Clock { get; set; }
+
+ /// Constructs a new initializer.
+ /// Authorization server URL
+ /// Token server URL
+ public Initializer(string authorizationServerUrl, string tokenServerUrl)
+ {
+ AuthorizationServerUrl = authorizationServerUrl;
+ TokenServerUrl = tokenServerUrl;
+
+ Scopes = new List();
+ 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 scopes;
+ private readonly ConfigurableHttpClient httpClient;
+ private readonly IClock clock;
+
+ #endregion
+
+ /// Gets the token server URL.
+ public string TokenServerUrl { get { return tokenServerUrl; } }
+
+ /// Gets the authorization code server URL.
+ public string AuthorizationServerUrl { get { return authorizationServerUrl; } }
+
+ /// Gets the client secrets which includes the client identifier and its secret.
+ public ClientSecrets ClientSecrets { get { return clientSecrets; } }
+
+ /// Gets the data store used to store the credentials.
+ public IDataStore DataStore { get { return dataStore; } }
+
+ /// Gets the scopes which indicate the API access your application is requesting.
+ public IEnumerable Scopes { get { return scopes; } }
+
+ /// Gets the HTTP client used to make authentication requests to the server.
+ public ConfigurableHttpClient HttpClient { get { return httpClient; } }
+
+ /// Constructs a new flow using the initializer's properties.
+ 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
+
+ ///
+ public IAccessMethod AccessMethod { get { return accessMethod; } }
+
+ ///
+ public IClock Clock { get { return clock; } }
+
+ ///
+ public async Task LoadTokenAsync(string userId, CancellationToken taskCancellationToken)
+ {
+ taskCancellationToken.ThrowIfCancellationRequested();
+ if (DataStore == null)
+ {
+ return null;
+ }
+ return await DataStore.GetAsync(userId).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task DeleteTokenAsync(string userId, CancellationToken taskCancellationToken)
+ {
+ taskCancellationToken.ThrowIfCancellationRequested();
+ if (DataStore != null)
+ {
+ await DataStore.DeleteAsync(userId).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public virtual AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
+ {
+ return new AuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
+ {
+ ClientId = ClientSecrets.ClientId,
+ Scope = string.Join(" ", Scopes),
+ RedirectUri = redirectUri
+ };
+ }
+
+ ///
+ public async Task 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;
+ }
+
+ ///
+ public async Task 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;
+ }
+
+ ///
+ public virtual Task RevokeTokenAsync(string userId, string token, CancellationToken taskCancellationToken)
+ {
+ throw new NotImplementedException("The OAuth 2.0 protocol does not support token revocation.");
+ }
+
+ ///
+ public virtual bool ShouldForceTokenRetrieval() { return false; }
+
+ #endregion
+
+ /// Stores the token in the .
+ /// User identifier.
+ /// Token to store.
+ /// Cancellation token to cancel operation.
+ private async Task StoreTokenAsync(string userId, TokenResponse token, CancellationToken taskCancellationToken)
+ {
+ taskCancellationToken.ThrowIfCancellationRequested();
+ if (DataStore != null)
+ {
+ await DataStore.StoreAsync(userId, token).ConfigureAwait(false);
+ }
+ }
+
+ /// Retrieve a new token from the server using the specified request.
+ /// User identifier.
+ /// Token request.
+ /// Cancellation token to cancel operation.
+ /// Token response with the new access token.
+ [VisibleForTestOnly]
+ public async Task 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;
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (HttpClient != null)
+ {
+ HttpClient.Dispose();
+ }
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs
new file mode 100644
index 00000000..b6d9489f
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs
@@ -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
+{
+ ///
+ /// Google specific authorization code flow which inherits from .
+ ///
+ public class GoogleAuthorizationCodeFlow : AuthorizationCodeFlow
+ {
+ private readonly string revokeTokenUrl;
+
+ /// Gets the token revocation URL.
+ public string RevokeTokenUrl { get { return revokeTokenUrl; } }
+
+ /// Gets or sets the include granted scopes indicator.
+ /// Do not use, use instead.
+ public readonly bool? includeGrantedScopes;
+
+ /// Gets or sets the include granted scopes indicator.
+ public bool? IncludeGrantedScopes { get { return includeGrantedScopes; } }
+
+ private readonly IEnumerable> userDefinedQueryParams;
+
+ /// Gets the user defined query parameters.
+ public IEnumerable> UserDefinedQueryParams
+ {
+ get { return userDefinedQueryParams; }
+ }
+
+ /// Constructs a new Google authorization code flow.
+ public GoogleAuthorizationCodeFlow(Initializer initializer)
+ : base(initializer)
+ {
+ revokeTokenUrl = initializer.RevokeTokenUrl;
+ includeGrantedScopes = initializer.IncludeGrantedScopes;
+ userDefinedQueryParams = initializer.UserDefinedQueryParams;
+ }
+
+ ///
+ 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
+ };
+ }
+
+ ///
+ 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(content);
+ throw new TokenResponseException(error, response.StatusCode);
+ }
+
+ await DeleteTokenAsync(userId, taskCancellationToken);
+ }
+
+ ///
+ public override bool ShouldForceTokenRetrieval()
+ {
+ return IncludeGrantedScopes.HasValue && IncludeGrantedScopes.Value;
+ }
+
+ /// An initializer class for Google authorization code flow.
+ public new class Initializer : AuthorizationCodeFlow.Initializer
+ {
+ /// Gets or sets the token revocation URL.
+ public string RevokeTokenUrl { get; set; }
+
+ ///
+ /// Gets or sets the optional indicator for including granted scopes for incremental authorization.
+ ///
+ public bool? IncludeGrantedScopes { get; set; }
+
+ /// Gets or sets the optional user defined query parameters.
+ public IEnumerable> UserDefinedQueryParams { get; set; }
+
+ ///
+ /// Constructs a new initializer. Sets Authorization server URL to
+ /// , and Token server URL to
+ /// .
+ ///
+ public Initializer() : this(
+ GoogleAuthConsts.OidcAuthorizationUrl, GoogleAuthConsts.OidcTokenUrl, GoogleAuthConsts.RevokeTokenUrl)
+ {
+ }
+
+ /// Constructs a new initializer.
+ /// Authorization server URL
+ /// Token server URL
+ /// Revocation server URL
+ ///
+ /// This is mainly for internal testing at Google, where we occasionally need
+ /// to use alternative oauth endpoints. This is not for general use.
+ ///
+ protected Initializer(string authorizationServerUrl, string tokenServerUrl, string revokeTokenUrl)
+ : base(authorizationServerUrl, tokenServerUrl)
+ {
+ RevokeTokenUrl = revokeTokenUrl;
+ }
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/IAuthorizationCodeFlow.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/IAuthorizationCodeFlow.cs
new file mode 100644
index 00000000..0948ffd1
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Flows/IAuthorizationCodeFlow.cs
@@ -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
+{
+ /// OAuth 2.0 authorization code flow that manages and persists end-user credentials.
+ public interface IAuthorizationCodeFlow : IDisposable
+ {
+ /// Gets the method for presenting the access token to the resource server.
+ IAccessMethod AccessMethod { get; }
+
+ /// Gets the clock.
+ IClock Clock { get; }
+
+ /// Gets the data store used to store the credentials.
+ IDataStore DataStore { get; }
+
+ ///
+ /// Asynchronously loads the user's token using the flow's
+ /// .
+ ///
+ /// User identifier
+ /// Cancellation token to cancel operation
+ /// Token response
+ Task LoadTokenAsync(string userId, CancellationToken taskCancellationToken);
+
+ ///
+ /// Asynchronously deletes the user's token using the flow's
+ /// .
+ ///
+ /// User identifier.
+ /// Cancellation token to cancel operation.
+ Task DeleteTokenAsync(string userId, CancellationToken taskCancellationToken);
+
+ /// Creates an authorization code request with the specified redirect URI.
+ AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri);
+
+ /// Asynchronously exchanges code with a token.
+ /// User identifier.
+ /// Authorization code received from the authorization server.
+ /// Redirect URI which is used in the token request.
+ /// Cancellation token to cancel operation.
+ /// Token response which contains the access token.
+ Task ExchangeCodeForTokenAsync(string userId, string code, string redirectUri,
+ CancellationToken taskCancellationToken);
+
+ /// Asynchronously refreshes an access token using a refresh token.
+ /// User identifier.
+ /// Refresh token which is used to get a new access token.
+ /// Cancellation token to cancel operation.
+ /// Token response which contains the access token and the input refresh token.
+ Task RefreshTokenAsync(string userId, string refreshToken,
+ CancellationToken taskCancellationToken);
+
+ ///
+ /// 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.
+ ///
+ /// 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.
+ ///
+ /// User identifier.
+ /// Access token to be revoked.
+ /// Cancellation token to cancel operation.
+ /// true if the token was revoked successfully.
+ Task RevokeTokenAsync(string userId, string token, CancellationToken taskCancellationToken);
+
+ ///
+ /// Indicates if a new token needs to be retrieved and stored regardless of normal circumstances.
+ ///
+ bool ShouldForceTokenRetrieval();
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleAuthConsts.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleAuthConsts.cs
new file mode 100644
index 00000000..b6211668
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleAuthConsts.cs
@@ -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
+{
+ ///
+ /// Google OAuth2 constants.
+ /// Canonical source for these URLs is: https://accounts.google.com/.well-known/openid-configuration
+ ///
+ public static class GoogleAuthConsts
+ {
+ /// The authorization code server URL.
+ public const string AuthorizationUrl = "https://accounts.google.com/o/oauth2/auth";
+
+ /// The OpenID Connect authorization code server URL.
+ ///
+ /// Use of this is not 100% compatible with using
+ /// , so they are two distinct URLs.
+ /// Internally within this library only this more up-to-date is used.
+ ///
+ public const string OidcAuthorizationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
+
+ /// The approval URL (used in the Windows solution as a callback).
+ public const string ApprovalUrl = "https://accounts.google.com/o/oauth2/approval";
+
+ /// The authorization token server URL.
+ public const string TokenUrl = "https://accounts.google.com/o/oauth2/token";
+
+ /// The OpenID Connect authorization token server URL.
+ ///
+ /// Use of this is not 100% compatible with using
+ /// , so they are two distinct URLs.
+ /// Internally within this library only this more up-to-date is used.
+ ///
+ public const string OidcTokenUrl = "https://www.googleapis.com/oauth2/v4/token";
+
+ /// The Compute Engine authorization token server URL
+ public const string ComputeTokenUrl =
+ "http://metadata/computeMetadata/v1/instance/service-accounts/default/token";
+
+ /// The path to the Google revocation endpoint.
+ public const string RevokeTokenUrl = "https://accounts.google.com/o/oauth2/revoke";
+
+ /// The OpenID Connect Json Web Key Set (jwks) URL.
+ public const string JsonWebKeySetUrl = "https://www.googleapis.com/oauth2/v3/certs";
+
+ /// Installed application redirect URI.
+ public const string InstalledAppRedirectUri = "urn:ietf:wg:oauth:2.0:oob";
+
+ /// Installed application localhost redirect URI.
+ public const string LocalhostRedirectUri = "http://localhost";
+ }
+}
\ No newline at end of file
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs
new file mode 100644
index 00000000..98e28587
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs
@@ -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
+{
+ ///
+ /// OAuth 2.0 client secrets model as specified in https://cloud.google.com/console/.
+ ///
+ public sealed class GoogleClientSecrets
+ {
+ /// Gets or sets the details for installed applications.
+ [Newtonsoft.Json.JsonProperty("installed")]
+ private ClientSecrets Installed { get; set; }
+
+ /// Gets or sets the details for web applications.
+ [Newtonsoft.Json.JsonProperty("web")]
+ private ClientSecrets Web { get; set; }
+
+ /// Gets the client secrets which contains the client identifier and client secret.
+ 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;
+ }
+ }
+
+ /// Loads the Google client secret from the input stream.
+ public static GoogleClientSecrets Load(Stream stream)
+ {
+ return NewtonsoftJsonSerializer.Instance.Deserialize(stream);
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleCredential.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleCredential.cs
new file mode 100644
index 00000000..41a2c4d1
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleCredential.cs
@@ -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
+{
+ ///
+ /// Credential for authorizing calls using OAuth 2.0.
+ /// It is a convenience wrapper that allows handling of different types of
+ /// credentials (like ,
+ /// or ) in a unified way.
+ ///
+ /// See for the credential retrieval logic.
+ ///
+ ///
+ public class GoogleCredential : ICredential
+ {
+ /// Provider implements the logic for creating the application default credential.
+ private static DefaultCredentialProvider defaultCredentialProvider = new DefaultCredentialProvider();
+
+ /// The underlying credential being wrapped by this object.
+ protected readonly ICredential credential;
+
+ /// Creates a new GoogleCredential.
+ internal GoogleCredential(ICredential credential)
+ {
+ this.credential = credential;
+ }
+
+ ///
+ /// Returns the Application Default Credentials which are ambient credentials that identify and authorize
+ /// the whole application.
+ /// The ambient credentials are determined as following order:
+ ///
+ ///
+ ///
+ /// 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
+ /// Google Developers Console 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.
+ ///
+ ///
+ ///
+ ///
+ /// If you have installed the Google Cloud SDK on your machine and have run the command
+ /// GCloud Auth Login, your identity can
+ /// be used as a proxy to test code calling APIs from that machine.
+ ///
+ ///
+ ///
+ ///
+ /// If you are running in Google Compute Engine production, the built-in service account associated with the
+ /// virtual machine instance will be used.
+ ///
+ ///
+ ///
+ ///
+ /// If all previous steps have failed, InvalidOperationException is thrown.
+ ///
+ ///
+ ///
+ ///
+ /// A task which completes with the application default credentials.
+ public static Task GetApplicationDefaultAsync()
+ {
+ return defaultCredentialProvider.GetDefaultCredentialAsync();
+ }
+
+ ///
+ /// Synchronously returns the Application Default Credentials which are ambient credentials that identify and authorize
+ /// the whole application. See for details on application default credentials.
+ /// This method will block until the credentials are available (or an exception is thrown).
+ /// It is highly preferable to call where possible.
+ ///
+ /// The application default credentials.
+ public static GoogleCredential GetApplicationDefault() => Task.Run(() => GetApplicationDefaultAsync()).Result;
+
+ ///
+ /// Loads credential from stream containing JSON credential data.
+ ///
+ /// 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.
+ ///
+ ///
+ public static GoogleCredential FromStream(Stream stream)
+ {
+ return defaultCredentialProvider.CreateDefaultCredentialFromStream(stream);
+ }
+
+ ///
+ /// Loads credential from a string containing JSON credential data.
+ ///
+ /// 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.
+ ///
+ ///
+ public static GoogleCredential FromJson(string json)
+ {
+ return defaultCredentialProvider.CreateDefaultCredentialFromJson(json);
+ }
+
+ ///
+ /// Returns true only if this credential type has no scopes by default and requires
+ /// a call to before use.
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ ///
+ /// has scopes built-in. Nothing additional is required.
+ ///
+ ///
+ ///
+ ///
+ /// has scopes built-in, as they were obtained during the consent
+ /// screen. Nothing additional is required.
+ ///
+ ///
+ ///
+ /// does not have scopes built-in by default. Caller should
+ /// invoke to add scopes to the credential.
+ ///
+ ///
+ ///
+ ///
+ public virtual bool IsCreateScopedRequired
+ {
+ get { return false; }
+ }
+
+ ///
+ /// If the credential supports scopes, creates a copy with the specified scopes. Otherwise, it returns the same
+ /// instance.
+ ///
+ public virtual GoogleCredential CreateScoped(IEnumerable scopes)
+ {
+ return this;
+ }
+
+ ///
+ /// If the credential supports scopes, creates a copy with the specified scopes. Otherwise, it returns the same
+ /// instance.
+ ///
+ public GoogleCredential CreateScoped(params string[] scopes)
+ {
+ return CreateScoped((IEnumerable) scopes);
+ }
+
+ void IConfigurableHttpClientInitializer.Initialize(ConfigurableHttpClient httpClient)
+ {
+ credential.Initialize(httpClient);
+ }
+
+ Task ITokenAccess.GetAccessTokenForRequestAsync(string authUri, CancellationToken cancellationToken)
+ {
+ return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken);
+ }
+
+ ///
+ /// Gets the underlying credential instance being wrapped.
+ ///
+ public ICredential UnderlyingCredential => credential;
+
+ /// Creates a GoogleCredential wrapping a .
+ internal static GoogleCredential FromCredential(ServiceAccountCredential credential)
+ {
+ return new ServiceAccountGoogleCredential(credential);
+ }
+
+ ///
+ /// Wraps ServiceAccountCredential as GoogleCredential.
+ /// We need this subclass because wrapping ServiceAccountCredential (unlike other wrapped credential
+ /// types) requires special handling for IsCreateScopedRequired and CreateScoped members.
+ ///
+ 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 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));
+ }
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs
new file mode 100644
index 00000000..ff8c655b
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs
@@ -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
+{
+ /// A helper utility to manage the authorization code flow.
+ public class GoogleWebAuthorizationBroker
+ {
+ // It's unforunate this is a public field. But it cannot be changed due to backward compatibility.
+ /// The folder which is used by the .
+ ///
+ /// The reason that this is not 'private const' is that a user can change it and store the credentials in a
+ /// different location.
+ ///
+ public static string Folder = "Google.Apis.Auth";
+
+ /// Asynchronously authorizes the specified user.
+ ///
+ /// In case no data store is specified, will be used by
+ /// default.
+ ///
+ /// The client secrets.
+ ///
+ /// The scopes which indicate the Google API access your application is requesting.
+ ///
+ /// The user to authorize.
+ /// Cancellation token to cancel an operation.
+ /// The data store, if not specified a file data store will be used.
+ /// The code receiver, if not specified a local server code receiver will be used.
+ /// User credential.
+ public static async Task AuthorizeAsync(ClientSecrets clientSecrets,
+ IEnumerable 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);
+ }
+
+ /// Asynchronously authorizes the specified user.
+ ///
+ /// In case no data store is specified, will be used by
+ /// default.
+ ///
+ ///
+ /// The client secrets stream. The authorization code flow constructor is responsible for disposing the stream.
+ ///
+ ///
+ /// The scopes which indicate the Google API access your application is requesting.
+ ///
+ /// The user to authorize.
+ /// Cancellation token to cancel an operation.
+ /// The data store, if not specified a file data store will be used.
+ /// The code receiver, if not specified a local server code receiver will be used.
+ /// User credential.
+ public static async Task AuthorizeAsync(Stream clientSecretsStream,
+ IEnumerable 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);
+ }
+
+ ///
+ /// Asynchronously reauthorizes the user. This method should be called if the users want to authorize after
+ /// they revoked the token.
+ ///
+ /// The current user credential. Its will be
+ /// updated.
+ /// Cancellation token to cancel an operation.
+ /// The code receiver, if not specified a local server code receiver will be used.
+ 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;
+ }
+
+ /// The core logic for asynchronously authorizing the specified user.
+ /// The authorization code initializer.
+ ///
+ /// The scopes which indicate the Google API access your application is requesting.
+ ///
+ /// The user to authorize.
+ /// Cancellation token to cancel an operation.
+ /// The data store, if not specified a file data store will be used.
+ /// The code receiver, if not specified a local server code receiver will be used.
+ /// User credential.
+ public static async Task AuthorizeAsync(
+ GoogleAuthorizationCodeFlow.Initializer initializer, IEnumerable 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);
+ }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/IAccessMethod.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/IAccessMethod.cs
new file mode 100644
index 00000000..abc72d09
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/IAccessMethod.cs
@@ -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
+{
+ ///
+ /// Method of presenting the access token to the resource server as specified in
+ /// http://tools.ietf.org/html/rfc6749#section-7
+ ///
+ public interface IAccessMethod
+ {
+ ///
+ /// Intercepts a HTTP request right before the HTTP request executes by providing the access token.
+ ///
+ void Intercept(HttpRequestMessage request, string accessToken);
+
+ ///
+ /// Retrieves the original access token in the HTTP request, as provided in the
+ /// method.
+ ///
+ string GetAccessToken(HttpRequestMessage request);
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/IAuthorizationCodeInstalledApp.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/IAuthorizationCodeInstalledApp.cs
new file mode 100644
index 00000000..71109ab0
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/IAuthorizationCodeInstalledApp.cs
@@ -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
+{
+ ///
+ /// Authorization code flow for an installed application that persists end-user credentials.
+ ///
+ public interface IAuthorizationCodeInstalledApp
+ {
+ /// Gets the authorization code flow.
+ IAuthorizationCodeFlow Flow { get; }
+
+ /// Gets the code receiver.
+ ICodeReceiver CodeReceiver { get; }
+
+ /// Asynchronously authorizes the installed application to access user's protected data.
+ /// User identifier
+ /// Cancellation token to cancel an operation
+ /// The user's credential
+ Task AuthorizeAsync(string userId, CancellationToken taskCancellationToken);
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ICodeReceiver.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ICodeReceiver.cs
new file mode 100644
index 00000000..ff43617e
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ICodeReceiver.cs
@@ -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
+{
+ /// OAuth 2.0 verification code receiver.
+ public interface ICodeReceiver
+ {
+ /// Gets the redirected URI.
+ string RedirectUri { get; }
+
+ /// Receives the authorization code.
+ /// The authorization code request URL
+ /// Cancellation token
+ /// The authorization code response
+ Task ReceiveCodeAsync(AuthorizationCodeRequestUrl url,
+ CancellationToken taskCancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ICredential.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ICredential.cs
new file mode 100644
index 00000000..a97a434f
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ICredential.cs
@@ -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
+{
+ ///
+ /// 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
+ /// to be able to hook to http requests.
+ /// More details are available in the specific implementations.
+ ///
+ public interface ICredential : IConfigurableHttpClientInitializer, ITokenAccess
+ {
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ITokenAccess.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ITokenAccess.cs
new file mode 100644
index 00000000..28bda836
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/ITokenAccess.cs
@@ -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
+{
+ ///
+ /// Allows direct retrieval of access tokens to authenticate requests.
+ /// This is necessary for workflows where you don't want to use
+ /// to access the API.
+ /// (e.g. gRPC that implemenents the entire HTTP2 stack internally).
+ ///
+ public interface ITokenAccess
+ {
+ ///
+ /// Gets an access token to authorize a request.
+ /// Implementations should handle automatic refreshes of the token
+ /// if they are supported.
+ /// The might be required by some credential types
+ /// (e.g. the JWT access token) while other credential types
+ /// migth just ignore it.
+ ///
+ /// The URI the returned token will grant access to.
+ /// The cancellation token.
+ /// The access token.
+ Task GetAccessTokenForRequestAsync(string authUri = null, CancellationToken cancellationToken = default(CancellationToken));
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/JsonCredentialParameters.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/JsonCredentialParameters.cs
new file mode 100644
index 00000000..797a2fe5
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/JsonCredentialParameters.cs
@@ -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
+{
+ ///
+ /// Holder for credential parameters read from JSON credential file.
+ /// Fields are union of parameters for all supported credential types.
+ ///
+ public class JsonCredentialParameters
+ {
+ ///
+ /// UserCredential is created by the GCloud SDK tool when the user runs
+ /// GCloud Auth Login.
+ ///
+ public const string AuthorizedUserCredentialType = "authorized_user";
+
+ ///
+ /// ServiceAccountCredential is downloaded by the user from
+ /// Google Developers Console.
+ ///
+ public const string ServiceAccountCredentialType = "service_account";
+
+ /// Type of the credential.
+ [Newtonsoft.Json.JsonProperty("type")]
+ public string Type { get; set; }
+
+ ///
+ /// Client Id associated with UserCredential created by
+ /// GCloud Auth Login.
+ ///
+ [Newtonsoft.Json.JsonProperty("client_id")]
+ public string ClientId { get; set; }
+
+ ///
+ /// Client Secret associated with UserCredential created by
+ /// GCloud Auth Login.
+ ///
+ [Newtonsoft.Json.JsonProperty("client_secret")]
+ public string ClientSecret { get; set; }
+
+ ///
+ /// Client Email associated with ServiceAccountCredential obtained from
+ /// Google Developers Console
+ ///
+ [Newtonsoft.Json.JsonProperty("client_email")]
+ public string ClientEmail { get; set; }
+
+ ///
+ /// Private Key associated with ServiceAccountCredential obtained from
+ /// Google Developers Console.
+ ///
+ [Newtonsoft.Json.JsonProperty("private_key")]
+ public string PrivateKey { get; set; }
+
+ ///
+ /// Refresh Token associated with UserCredential created by
+ /// GCloud Auth Login.
+ ///
+ [Newtonsoft.Json.JsonProperty("refresh_token")]
+ public string RefreshToken { get; set; }
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/LocalServerCodeReceiver.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/LocalServerCodeReceiver.cs
new file mode 100644
index 00000000..9164adea
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/LocalServerCodeReceiver.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ public class LocalServerCodeReceiver : ICodeReceiver
+ {
+ private static readonly ILogger Logger = ApplicationContext.Logger.ForType();
+
+ /// The call back request path.
+ internal const string LoopbackCallbackPath = "/authorize/";
+
+ /// The call back format. Expects one port parameter.
+ internal static readonly string LoopbackCallback = $"http://{IPAddress.Loopback}:{{0}}{LoopbackCallbackPath}";
+
+ /// Close HTML tag to return the browser so it will close itself.
+ internal const string ClosePageResponse =
+@"
+ OAuth 2.0 Authentication Token Received
+
+ Received verification code. You may now close this window.
+
+
+";
+
+ // Not required in NET45, but present for testing.
+ ///
+ /// 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.
+ ///
+ 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();
+
+ 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> 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> GetQueryParamsFromClientAsync(TcpClient client, CancellationToken cancellationToken)
+ {
+ var stream = client.GetStream();
+
+ var buffer = new byte[NetworkReadBufferSize];
+ int bufferOfs = 0;
+ int bufferSize = 0;
+ Func> 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 ReadRequestLine(Func> 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 ValidateAndGetRequestParams(string requestLine)
+ {
+ var requestLineParts = requestLine.Split(' ');
+ if (requestLineParts.Length != 3)
+ {
+ throw new ServerException("Request line ill-formatted. Should be ' 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();
+ }
+ 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> 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;
+ ///
+ public string RedirectUri
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(redirectUri))
+ {
+ return redirectUri;
+ }
+
+ return redirectUri = string.Format(LoopbackCallback, GetRandomUnusedPort());
+ }
+ }
+
+ ///
+ public async Task 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);
+ }
+ }
+
+ /// Returns a random, unused port.
+ 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 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 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
+ }
+}
diff --git a/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Pkcs8.cs b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Pkcs8.cs
new file mode 100644
index 00000000..e66ff104
--- /dev/null
+++ b/Yavsc/GoogleApiSupport/Google.Apis.Auth/OAuth2/Pkcs8.cs
@@ -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
+
+ ///
+ /// An incomplete ASN.1 decoder, only implements what's required
+ /// to decode a Service Credential.
+ ///
+ 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 result = new List();
+ 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