From f968c95ab1ec165288d31053bf49d3a9ae6c7361 Mon Sep 17 00:00:00 2001 From: Michael Gordeev Date: Sun, 10 May 2020 13:21:01 +0300 Subject: [PATCH] Added UserCredential override with TokenUpdated event Added Success code parser Updated packages versions --- .../Authorization/AuthorizationHelpers.cs | 154 +++++++++--------- YouTube.API/Authorization/UserCredential.cs | 61 +++++++ YouTube.API/YouTube.API.csproj | 14 +- 3 files changed, 147 insertions(+), 82 deletions(-) create mode 100644 YouTube.API/Authorization/UserCredential.cs diff --git a/YouTube.API/Authorization/AuthorizationHelpers.cs b/YouTube.API/Authorization/AuthorizationHelpers.cs index d479d7c..6349db0 100644 --- a/YouTube.API/Authorization/AuthorizationHelpers.cs +++ b/YouTube.API/Authorization/AuthorizationHelpers.cs @@ -5,102 +5,106 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Net.Http; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace YouTube.Authorization { - public static class AuthorizationHelpers - { - public static Uri Endpoint => "https://accounts.google.com/o/oauth2/approval".ToUri(); - const string refreshEndpoint = "https://oauth2.googleapis.com/token"; - const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"; - const string redirectUrl = "urn:ietf:wg:oauth:2.0:oob"; + public static class AuthorizationHelpers + { + public static Uri Endpoint => "https://accounts.google.com/o/oauth2/approval".ToUri(); + private const string refreshEndpoint = "https://oauth2.googleapis.com/token"; + private const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"; + private const string redirectUrl = "urn:ietf:wg:oauth:2.0:oob"; - public static Uri FormQueryString(ClientSecrets clientSecrets, params string[] scopes) - { - string clientId = Uri.EscapeDataString(clientSecrets.ClientId); - string scopeStr = string.Join(" ", scopes); + public static Uri FormQueryString(ClientSecrets clientSecrets, params string[] scopes) + { + string clientId = Uri.EscapeDataString(clientSecrets.ClientId); + string scopeStr = string.Join(" ", scopes); - return $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={redirectUrl}&response_type=code&scope={scopeStr}".ToUri(); - } + return $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={redirectUrl}&response_type=code&scope={scopeStr}".ToUri(); + } - public static async Task ExchangeToken(ClientSecrets clientSecrets, string responseToken) - { - using HttpClient client = new HttpClient(); + public static string ParseSuccessCode(string responseData) => + new Regex(@"(?<=code=)(.*?)(?=&)").Match(responseData).Value; - Dictionary requestBody = new Dictionary - { - { "code", responseToken }, - { "redirect_uri", redirectUrl }, - { "grant_type", "authorization_code" }, - { "client_id", clientSecrets.ClientId }, - { "client_secret", clientSecrets.ClientSecret } - }; + public static async Task ExchangeToken(ClientSecrets clientSecrets, string responseToken) + { + using HttpClient client = new HttpClient(); - HttpResponseMessage response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(requestBody)); + Dictionary requestBody = new Dictionary + { + { "code", responseToken }, + { "redirect_uri", redirectUrl }, + { "grant_type", "authorization_code" }, + { "client_id", clientSecrets.ClientId }, + { "client_secret", clientSecrets.ClientSecret } + }; - if (!response.IsSuccessStatusCode) - throw new Exception(await response.Content.ReadAsStringAsync()); + HttpResponseMessage response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(requestBody)); - string responseString = await response.Content.ReadAsStringAsync(); - dynamic responseData = JsonConvert.DeserializeObject(responseString); + if (!response.IsSuccessStatusCode) + throw new Exception(await response.Content.ReadAsStringAsync()); - TokenResponse tokenResponse = new TokenResponse - { - AccessToken = responseData.access_token, - ExpiresInSeconds = responseData.expires_in, - RefreshToken = responseData.refresh_token, - Scope = responseData.scope, - TokenType = responseData.token_type - }; + string responseString = await response.Content.ReadAsStringAsync(); + dynamic responseData = JsonConvert.DeserializeObject(responseString); - AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer - { - ClientSecrets = clientSecrets, - Scopes = responseData.scope.ToString().Split(' ') - }); + TokenResponse tokenResponse = new TokenResponse + { + AccessToken = responseData.access_token, + ExpiresInSeconds = responseData.expires_in, + RefreshToken = responseData.refresh_token, + Scope = responseData.scope, + TokenType = responseData.token_type + }; - UserCredential credential = new UserCredential(authorizationCodeFlow, "user", tokenResponse); + AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer + { + ClientSecrets = clientSecrets, + Scopes = responseData.scope.ToString().Split(' ') + }); - return credential; - } + UserCredential credential = new UserCredential(authorizationCodeFlow, "user", tokenResponse); - public static async Task RestoreUser(ClientSecrets clientSecrets, string refreshToken) - { - using HttpClient client = new HttpClient(); + return credential; + } - Dictionary requestBody = new Dictionary - { - { "client_id", clientSecrets.ClientId }, - { "client_secret", clientSecrets.ClientSecret }, - { "refresh_token", refreshToken }, - { "grant_type", "refresh_token" } - }; + public static async Task RestoreUser(ClientSecrets clientSecrets, string refreshToken) + { + using HttpClient client = new HttpClient(); - HttpResponseMessage response = await client.PostAsync(refreshEndpoint, new FormUrlEncodedContent(requestBody)); + Dictionary requestBody = new Dictionary + { + { "client_id", clientSecrets.ClientId }, + { "client_secret", clientSecrets.ClientSecret }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; - if (!response.IsSuccessStatusCode) - throw new Exception(await response.Content.ReadAsStringAsync()); + HttpResponseMessage response = await client.PostAsync(refreshEndpoint, new FormUrlEncodedContent(requestBody)); - string responseString = await response.Content.ReadAsStringAsync(); - dynamic responseData = JsonConvert.DeserializeObject(responseString); + if (!response.IsSuccessStatusCode) + throw new Exception(await response.Content.ReadAsStringAsync()); - TokenResponse tokenResponse = new TokenResponse - { - AccessToken = responseData.access_token, - ExpiresInSeconds = responseData.expires_in, - RefreshToken = refreshToken, - TokenType = responseData.token_type - }; + string responseString = await response.Content.ReadAsStringAsync(); + dynamic responseData = JsonConvert.DeserializeObject(responseString); - AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer - { - ClientSecrets = clientSecrets - }); + TokenResponse tokenResponse = new TokenResponse + { + AccessToken = responseData.access_token, + ExpiresInSeconds = responseData.expires_in, + RefreshToken = refreshToken, + TokenType = responseData.token_type + }; - UserCredential credential = new UserCredential(authorizationCodeFlow, "user", tokenResponse); + AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer + { + ClientSecrets = clientSecrets + }); - return credential; - } - } -} + UserCredential credential = new UserCredential(authorizationCodeFlow, "user", tokenResponse); + + return credential; + } + } +} \ No newline at end of file diff --git a/YouTube.API/Authorization/UserCredential.cs b/YouTube.API/Authorization/UserCredential.cs new file mode 100644 index 0000000..b28f3d8 --- /dev/null +++ b/YouTube.API/Authorization/UserCredential.cs @@ -0,0 +1,61 @@ +using Google.Apis.Auth.OAuth2.Flows; +using Google.Apis.Auth.OAuth2.Responses; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace YouTube.Authorization +{ + public class UserCredential : Google.Apis.Auth.OAuth2.UserCredential + { + /// + /// Event is fired when new refresh token is recieved and the old one is no loger valid + /// + public event EventHandler RefreshTokenUpdated; + + /// Constructs a new credential instance. + /// Authorization code flow. + /// User identifier. + /// An initial token for the user. + public UserCredential(IAuthorizationCodeFlow flow, string userId, TokenResponse token) : base(flow, userId, token) { } + + /// Constructs a new credential instance. + /// Authorization code flow. + /// User identifier. + /// An initial token for the user. + /// The ID of the project associated + /// to this credential for the purposes of quota calculation and billing. Can be null. + public UserCredential(IAuthorizationCodeFlow flow, string userId, TokenResponse token, string quotaProjectId) : base(flow, userId, token, quotaProjectId) { } + + /// + /// Refreshes the token by calling to + /// . + /// Then it updates the with the new token instance. + /// + /// Cancellation token to cancel an operation. + /// true if the token was refreshed. + public new async Task RefreshTokenAsync(CancellationToken taskCancellationToken) + { + if (Token.RefreshToken == null) + { + Logger.Warning("Refresh token is null, can't refresh the token!"); + return false; + } + + // It's possible that two concurrent calls will be made to refresh the token, in that case the last one + // will win. + var newToken = await Flow.RefreshTokenAsync(UserId, Token.RefreshToken, taskCancellationToken) + .ConfigureAwait(false); + + Logger.Info("Access token was refreshed successfully"); + + if (newToken.RefreshToken == null) + newToken.RefreshToken = Token.RefreshToken; + + Token = newToken; + + RefreshTokenUpdated?.Invoke(this, null); + return true; + } + } +} \ No newline at end of file diff --git a/YouTube.API/YouTube.API.csproj b/YouTube.API/YouTube.API.csproj index 6b15cea..149dd95 100644 --- a/YouTube.API/YouTube.API.csproj +++ b/YouTube.API/YouTube.API.csproj @@ -21,15 +21,15 @@ - - - - - - + + + + + + - +