<commit_before>﻿using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Util.Store;
using log4net;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace OutlookGoogleCalendarSync.GoogleOgcs {
    public class Authenticator {
        private static readonly ILog log = LogManager.GetLogger(typeof(Authenticator));

        private Boolean authenticated = false;
        public Boolean Authenticated { get { return authenticated; } }

        public const String TokenFile = "Google.Apis.Auth.OAuth2.Responses.TokenResponse-user";
        String tokenFullPath;
        Boolean tokenFileExists { get { return File.Exists(tokenFullPath); } }

        public System.Threading.CancellationTokenSource CancelTokenSource;

        private Boolean checkedOgcsUserStatus = false;
        private static String hashedGmailAccount = null;
        public static String HashedGmailAccount {
            get {
                if (string.IsNullOrEmpty(hashedGmailAccount)) {
                    if (!string.IsNullOrEmpty(Settings.Instance.GaccountEmail))
                        hashedGmailAccount = getMd5(Settings.Instance.GaccountEmail);
                }
                return hashedGmailAccount;
            }
        }

        public Authenticator() {
            CancelTokenSource = new System.Threading.CancellationTokenSource();
        }

        public void GetAuthenticated() {
            if (this.authenticated) return;

            Forms.Main.Instance.Console.Update("Authenticating with Google...", verbose: true);

            System.Threading.Thread oAuth = new System.Threading.Thread(() => { spawnOauth(); });
            oAuth.Start();
            while (oAuth.IsAlive) {
                System.Windows.Forms.Application.DoEvents();
                System.Threading.Thread.Sleep(100);
            }
        }

        private void spawnOauth() {
            ClientSecrets cs = getCalendarClientSecrets();
            //Calling an async function from a static constructor needs to be called like this, else it deadlocks:-
            var task = System.Threading.Tasks.Task.Run(async () => { await getAuthenticated(cs); });
            try {
                task.Wait(CancelTokenSource.Token);
            } catch (System.OperationCanceledException) {
                Forms.Main.Instance.Console.Update("Authorisation to allow OGCS to manage your Google calendar was cancelled.", Console.Markup.warning);
            } catch (System.Exception ex) {
                OGCSexception.Analyse(ex);
            }
        }

        private static ClientSecrets getCalendarClientSecrets() {
            ClientSecrets provider = new ClientSecrets();
            if (Settings.Instance.UsingPersonalAPIkeys()) {
                provider.ClientId = Settings.Instance.PersonalClientIdentifier;
                provider.ClientSecret = Settings.Instance.PersonalClientSecret;
            } else {
                ApiKeyring apiKeyring = new ApiKeyring();

                if (Settings.Instance.Subscribed != null && Settings.Instance.Subscribed != DateTime.Parse("01-Jan-2000")) {
                    if (apiKeyring.PickKey(ApiKeyring.KeyType.Subscriber) && apiKeyring.Key != null) {
                        provider.ClientId = apiKeyring.Key.ClientId;
                        provider.ClientSecret = apiKeyring.Key.ClientSecret;
                    } else {
                        provider.ClientId = "550071650559-44lnvhdu5liq5kftj5t8k0aasgei5g7t.apps.googleusercontent.com";
                        provider.ClientSecret = "MGUFapefXClJa2ysS4WNGS4k";
                    }
                } else {
                    if (apiKeyring.PickKey(ApiKeyring.KeyType.Standard) && apiKeyring.Key != null) {
                        provider.ClientId = apiKeyring.Key.ClientId;
                        provider.ClientSecret = apiKeyring.Key.ClientSecret;
                    } else {
                        provider.ClientId = "653617509806-2nq341ol8ejgqhh2ku4j45m7q2bgdimv.apps.googleusercontent.com";
                        provider.ClientSecret = "<KEY>";
                    }
                }
            }
            return provider;
        }

        private async Task getAuthenticated(ClientSecrets cs) {
            log.Debug("Authenticating with Google calendar service...");

            FileDataStore tokenStore = new FileDataStore(Program.UserFilePath);
            tokenFullPath = Path.Combine(tokenStore.FolderPath, TokenFile);

            log.Debug("Google credential file location: " + tokenFullPath);
            if (!tokenFileExists)
                log.Info("No Google credentials file available - need user authorisation for OGCS to manage their calendar.");
            
            string[] scopes = new[] { "https://www.googleapis.com/auth/calendar", "email" };

            UserCredential credential = null;
            try {
                //This will open the authorisation process in a browser, if required
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(cs, scopes, "user", CancelTokenSource.Token, tokenStore);
                if (tokenFileExists)
                    log.Debug("User has provided authorisation and credential file saved.");

            } catch (Google.Apis.Auth.OAuth2.Responses.TokenResponseException ex) {
                //OGCSexception.AnalyseTokenResponse(ex);
                if (ex.Error.Error == "access_denied") {
                    String noAuthGiven = "Sorry, but this application will not work if you don't allow it access to your Google Calendar :(";
                    log.Warn("User did not provide authorisation code. Sync will not be able to work.");
                    MessageBox.Show(noAuthGiven, "Authorisation not given", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    throw new ApplicationException(noAuthGiven);
                } else {
                    Forms.Main.Instance.Console.UpdateWithError("Unable to authenticate with Google. The following error occurred:", ex);
                }

            } catch (System.Exception ex) {
                OGCSexception.Analyse(ex);
                Forms.Main.Instance.Console.UpdateWithError("Unable to authenticate with Google. The following error occurred:", ex);
            }

            if (credential.Token.AccessToken != "" && credential.Token.RefreshToken != "") {
                log.Info("Refresh and Access token successfully retrieved.");
                log.Debug("Access token expires " + credential.Token.IssuedUtc.AddSeconds(credential.Token.ExpiresInSeconds.Value).ToLocalTime().ToString());
            }

            GoogleOgcs.Calendar.Instance.Service = new CalendarService(new Google.Apis.Services.BaseClientService.Initializer() { HttpClientInitializer = credential });

            if (credential.Token.IssuedUtc.AddSeconds(credential.Token.ExpiresInSeconds.Value) < DateTime.Now.AddMinutes(-1)) {
                log.Debug("Access token needs refreshing.");
                //This will happen automatically when using the calendar service
                //But we need a valid token before we call getGaccountEmail() which doesn't use the service
                try {
                    GoogleOgcs.Calendar.Instance.Service.Settings.Get("useKeyboardShortcuts").Execute();
                } catch (System.Exception ex) {
                    if (ex is Google.Apis.Auth.OAuth2.Responses.TokenResponseException)<commit_msg>Bugfix: Google token expiry. Fixes #530<commit_after>﻿using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Util.Store;
using log4net;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace OutlookGoogleCalendarSync.GoogleOgcs {
    public class Authenticator {
        private static readonly ILog log = LogManager.GetLogger(typeof(Authenticator));

        private Boolean authenticated = false;
        public Boolean Authenticated { get { return authenticated; } }

        public const String TokenFile = "Google.Apis.Auth.OAuth2.Responses.TokenResponse-user";
        String tokenFullPath;
        Boolean tokenFileExists { get { return File.Exists(tokenFullPath); } }

        public System.Threading.CancellationTokenSource CancelTokenSource;

        private Boolean checkedOgcsUserStatus = false;
        private static String hashedGmailAccount = null;
        public static String HashedGmailAccount {
            get {
                if (string.IsNullOrEmpty(hashedGmailAccount)) {
                    if (!string.IsNullOrEmpty(Settings.Instance.GaccountEmail))
                        hashedGmailAccount = getMd5(Settings.Instance.GaccountEmail);
                }
                return hashedGmailAccount;
            }
        }

        public Authenticator() {
            CancelTokenSource = new System.Threading.CancellationTokenSource();
        }

        public void GetAuthenticated() {
            if (this.authenticated) return;

            Forms.Main.Instance.Console.Update("Authenticating with Google...", verbose: true);

            System.Threading.Thread oAuth = new System.Threading.Thread(() => { spawnOauth(); });
            oAuth.Start();
            while (oAuth.IsAlive) {
                System.Windows.Forms.Application.DoEvents();
                System.Threading.Thread.Sleep(100);
            }
        }

        private void spawnOauth() {
            ClientSecrets cs = getCalendarClientSecrets();
            //Calling an async function from a static constructor needs to be called like this, else it deadlocks:-
            var task = System.Threading.Tasks.Task.Run(async () => { await getAuthenticated(cs); });
            try {
                task.Wait(CancelTokenSource.Token);
            } catch (System.OperationCanceledException) {
                Forms.Main.Instance.Console.Update("Authorisation to allow OGCS to manage your Google calendar was cancelled.", Console.Markup.warning);
            } catch (System.Exception ex) {
                OGCSexception.Analyse(ex);
            }
        }

        private static ClientSecrets getCalendarClientSecrets() {
            ClientSecrets provider = new ClientSecrets();
            if (Settings.Instance.UsingPersonalAPIkeys()) {
                provider.ClientId = Settings.Instance.PersonalClientIdentifier;
                provider.ClientSecret = Settings.Instance.PersonalClientSecret;
            } else {
                ApiKeyring apiKeyring = new ApiKeyring();

                if (Settings.Instance.Subscribed != null && Settings.Instance.Subscribed != DateTime.Parse("01-Jan-2000")) {
                    if (apiKeyring.PickKey(ApiKeyring.KeyType.Subscriber) && apiKeyring.Key != null) {
                        provider.ClientId = apiKeyring.Key.ClientId;
                        provider.ClientSecret = apiKeyring.Key.ClientSecret;
                    } else {
                        provider.ClientId = "550071650559-44lnvhdu5liq5kftj5t8k0aasgei5g7t.apps.googleusercontent.com";
                        provider.ClientSecret = "MGUFapefXClJa2ysS4WNGS4k";
                    }
                } else {
                    if (apiKeyring.PickKey(ApiKeyring.KeyType.Standard) && apiKeyring.Key != null) {
                        provider.ClientId = apiKeyring.Key.ClientId;
                        provider.ClientSecret = apiKeyring.Key.ClientSecret;
                    } else {
                        provider.ClientId = "653617509806-2nq341ol8ejgqhh2ku4j45m7q2bgdimv.apps.googleusercontent.com";
                        provider.ClientSecret = "<KEY>";
                    }
                }
            }
            return provider;
        }

        private async Task getAuthenticated(ClientSecrets cs) {
            log.Debug("Authenticating with Google calendar service...");

            FileDataStore tokenStore = new FileDataStore(Program.UserFilePath);
            tokenFullPath = Path.Combine(tokenStore.FolderPath, TokenFile);

            log.Debug("Google credential file location: " + tokenFullPath);
            if (!tokenFileExists)
                log.Info("No Google credentials file available - need user authorisation for OGCS to manage their calendar.");
            
            string[] scopes = new[] { "https://www.googleapis.com/auth/calendar", "email" };

            UserCredential credential = null;
            try {
                //This will open the authorisation process in a browser, if required
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(cs, scopes, "user", CancelTokenSource.Token, tokenStore);
                if (tokenFileExists)
                    log.Debug("User has provided authorisation and credential file saved.");

            } catch (Google.Apis.Auth.OAuth2.Responses.TokenResponseException ex) {
                //OGCSexception.AnalyseTokenResponse(ex);
                if (ex.Error.Error == "access_denied") {
                    String noAuthGiven = "Sorry, but this application will not work if you don't allow it access to your Google Calendar :(";
                    log.Warn("User did not provide authorisation code. Sync will not be able to work.");
                    MessageBox.Show(noAuthGiven, "Authorisation not given", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    throw new ApplicationException(noAuthGiven);
                } else {
                    Forms.Main.Instance.Console.UpdateWithError("Unable to authenticate with Google. The following error occurred:", ex);
                }

            } catch (System.Exception ex) {
                OGCSexception.Analyse(ex);
                Forms.Main.Instance.Console.UpdateWithError("Unable to authenticate with Google. The following error occurred:", ex);
            }

            if (credential.Token.AccessToken != "" && credential.Token.RefreshToken != "") {
                log.Info("Refresh and Access token successfully retrieved.");
                log.Debug("Access token expires " + credential.Token.IssuedUtc.AddSeconds(credential.Token.ExpiresInSeconds.Value).ToLocalTime().ToString());
            }

            GoogleOgcs.Calendar.Instance.Service = new CalendarService(new Google.Apis.Services.BaseClientService.Initializer() { HttpClientInitializer = credential });

            if (credential.Token.IssuedUtc.AddSeconds(credential.Token.ExpiresInSeconds.Value) < DateTime.UtcNow.AddMinutes(-1)) {
                log.Debug("Access token needs refreshing.");
                //This will happen automatically when using the calendar service
                //But we need a valid token before we call getGaccountEmail() which doesn't use the service
                try {
                    GoogleOgcs.Calendar.Instance.Service.Settings.Get("useKeyboardShortcuts").Execute();
                } catch (System.Exception ex) {
                    if (ex is Google.Apis.Auth.OAuth2.Responses.TokenResponseException)
{'fraction_non_alphanumeric': 0.08958456389052963, 'fraction_numerical': 0.008728752379077246, 'mean_word_length': 2.1542123783895675, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3136700', 'n_tokens_mistral': 3956, 'n_tokens_neox': 3722, 'n_words': 836}