From 1d8a29da319c8aeca0b7b902325ffb4ea9e25257 Mon Sep 17 00:00:00 2001 From: NovaFox161 Date: Sat, 29 Aug 2020 13:12:49 -0500 Subject: [PATCH] Fix improper handling of revoked permissions for external calendar access --- .../discal/core/calendar/CalendarAuth.java | 51 +++++++++---------- .../core/network/google/Authorization.java | 35 +++++++++---- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/org/dreamexposure/discal/core/calendar/CalendarAuth.java b/core/src/main/java/org/dreamexposure/discal/core/calendar/CalendarAuth.java index 3ba2e8d3..b7dffcdf 100644 --- a/core/src/main/java/org/dreamexposure/discal/core/calendar/CalendarAuth.java +++ b/core/src/main/java/org/dreamexposure/discal/core/calendar/CalendarAuth.java @@ -46,7 +46,7 @@ public class CalendarAuth { /** * Application name. */ - private static final String APPLICATION_NAME = "DisCal"; + private final static String APPLICATION_NAME = "DisCal"; /** * Global instance of the scopes required by this quickstart. @@ -54,15 +54,15 @@ public class CalendarAuth { * If modifying these scopes, delete your previously saved credentials * at ~/.credentials/calendar-java-quickstart */ - private static final List SCOPES = Arrays.asList(CalendarScopes.CALENDAR, CalendarScopes.CALENDAR_EVENTS); + private final static List SCOPES = Arrays.asList(CalendarScopes.CALENDAR, CalendarScopes.CALENDAR_EVENTS); - private static final List CREDENTIALS; + private final static List CREDENTIALS; static { try { - final List credentials = new ArrayList<>(); + List credentials = new ArrayList<>(); - final int credCount = Integer.parseInt(BotSettings.CREDENTIALS_COUNT.get()); + int credCount = Integer.parseInt(BotSettings.CREDENTIALS_COUNT.get()); for (int i = 0; i < credCount; i++) { credentials.add(new DisCalCredential(i, new FileDataStoreFactory(getCredentialsFolder(i)), @@ -72,7 +72,7 @@ public class CalendarAuth { CREDENTIALS = Collections.unmodifiableList(credentials); - } catch (final Throwable t) { + } catch (Throwable t) { t.printStackTrace(); System.exit(1); throw new RuntimeException(t); //Never reached, makes compiler happy :) @@ -84,23 +84,23 @@ public class CalendarAuth { * * @return an authorized Credential object. */ - private static Mono authorize(final int credentialId) { + private static Mono authorize(int credentialId) { return Mono.fromCallable(() -> { // Load client secrets. - final InputStream in = new FileInputStream(new File("client_secret.json")); - final GoogleClientSecrets clientSecrets = GoogleClientSecrets + InputStream in = new FileInputStream(new File("client_secret.json")); + GoogleClientSecrets clientSecrets = GoogleClientSecrets .load(JacksonFactory.getDefaultInstance(), new InputStreamReader(in)); // Build flow and trigger user authorization request. - final DisCalCredential cred = getCredential(credentialId); + DisCalCredential cred = getCredential(credentialId); - final GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow .Builder(cred.getTransport(), cred.getJsonFactory(), clientSecrets, SCOPES) .setDataStoreFactory(cred.getStoreFactory()) .setAccessType("offline") .build(); - final Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()) + Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()) .authorize("user"); //Try to close input stream since I don't think it was ever closed? @@ -111,23 +111,23 @@ public class CalendarAuth { } //TODO: This won't need guild settings eventually once we move the data to calendar data like it should be at - private static Mono authorize(final GuildSettings g) { + private static Mono authorize(GuildSettings g) { return Mono.fromCallable(() -> { if ("N/a".equalsIgnoreCase(g.getEncryptedAccessToken())) return null; - final AESEncryption encryption = new AESEncryption(g); - final String accessToken = Authorization.getAuth().requestNewAccessToken(g, encryption); + AESEncryption encryption = new AESEncryption(g); + String accessToken = Authorization.getAuth().requestNewAccessToken(g, encryption); - final Credential credential = new GoogleCredential(); + Credential credential = new GoogleCredential(); credential.setAccessToken(accessToken); return credential; }).subscribeOn(Schedulers.boundedElastic()); } //TODO: Remove need for guild settings once we move the relevant data to more appropriate classes - public static Mono getCalendarService(@NotNull final GuildSettings g, - @NotNull final CalendarData calData) { + public static Mono getCalendarService(@NotNull GuildSettings g, + @NotNull CalendarData calData) { return Mono.fromCallable(() -> { if (g.useExternalCalendar()) { return authorize(g).map(cred -> @@ -141,8 +141,8 @@ public class CalendarAuth { }).flatMap(Function.identity()); } - public static Mono getCalendarService(final int credentialId) { - final DisCalCredential disCalCredential = getCredential(credentialId); + public static Mono getCalendarService(int credentialId) { + DisCalCredential disCalCredential = getCredential(credentialId); return authorize(credentialId).map(cred -> new Calendar .Builder(disCalCredential.getTransport(), disCalCredential.getJsonFactory(), cred) @@ -150,7 +150,7 @@ public class CalendarAuth { .build()); } - public static Mono getExternalCalendarService(final GuildSettings settings) { + public static Mono getExternalCalendarService(GuildSettings settings) { return authorize(settings).map(cred -> new Calendar. Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), cred) @@ -158,12 +158,12 @@ public class CalendarAuth { .build()); } - private static File getCredentialsFolder(final int credentialId) { + private static File getCredentialsFolder(int credentialId) { return new File(BotSettings.CREDENTIAL_FOLDER.get() + "/" + credentialId); } - private static @Nullable DisCalCredential getCredential(final int id) { - for (final DisCalCredential c : CREDENTIALS) { + private static @Nullable DisCalCredential getCredential(int id) { + for (DisCalCredential c : CREDENTIALS) { if (c.getCredentialId() == id) { return c; } @@ -186,8 +186,7 @@ public class CalendarAuth { private final JsonFactory jsonFactory; - DisCalCredential(final int id, final FileDataStoreFactory store, final HttpTransport transport, - final JsonFactory jsonFactory) { + DisCalCredential(int id, FileDataStoreFactory store, HttpTransport transport, JsonFactory jsonFactory) { this.credentialId = id; this.storeFactory = store; this.transport = transport; diff --git a/core/src/main/java/org/dreamexposure/discal/core/network/google/Authorization.java b/core/src/main/java/org/dreamexposure/discal/core/network/google/Authorization.java index 03f266db..1b01bb1a 100644 --- a/core/src/main/java/org/dreamexposure/discal/core/network/google/Authorization.java +++ b/core/src/main/java/org/dreamexposure/discal/core/network/google/Authorization.java @@ -1,7 +1,5 @@ package org.dreamexposure.discal.core.network.google; -import com.google.api.client.http.HttpStatusCodes; - import org.dreamexposure.discal.core.crypto.AESEncryption; import org.dreamexposure.discal.core.database.DatabaseManager; import org.dreamexposure.discal.core.logger.LogFeed; @@ -9,6 +7,8 @@ import org.dreamexposure.discal.core.logger.object.LogObject; import org.dreamexposure.discal.core.object.BotSettings; import org.dreamexposure.discal.core.object.GuildSettings; import org.dreamexposure.discal.core.object.network.google.ClientData; +import org.dreamexposure.discal.core.utils.CalendarUtils; +import org.dreamexposure.discal.core.utils.GlobalConst; import org.json.JSONObject; import okhttp3.FormBody; @@ -53,25 +53,26 @@ public class Authorization { } - public String requestNewAccessToken(final GuildSettings settings, final AESEncryption encryption) { + //TODO: Rewrite this to be reactive + public String requestNewAccessToken(GuildSettings settings, AESEncryption encryption) { try { - final RequestBody body = new FormBody.Builder() + RequestBody body = new FormBody.Builder() .addEncoded("client_id", this.clientData.getClientId()) .addEncoded("client_secret", this.clientData.getClientSecret()) .addEncoded("refresh_token", encryption.decrypt(settings.getEncryptedRefreshToken())) .addEncoded("grant_type", "refresh_token") .build(); - final Request httpRequest = new okhttp3.Request.Builder() + Request httpRequest = new okhttp3.Request.Builder() .url("https://www.googleapis.com/oauth2/v4/token") .post(body) .header("Content-Type", "application/x-www-form-urlencoded") .build(); - final Response httpResponse = this.client.newCall(httpRequest).execute(); + Response httpResponse = this.client.newCall(httpRequest).execute(); - if (httpResponse.code() == HttpStatusCodes.STATUS_CODE_OK) { - final JSONObject autoRefreshResponse = new JSONObject(httpResponse.body().string()); + if (httpResponse.code() == GlobalConst.STATUS_SUCCESS) { + JSONObject autoRefreshResponse = new JSONObject(httpResponse.body().string()); //Update Db data. settings.setEncryptedAccessToken(encryption.encrypt(autoRefreshResponse.getString("access_token"))); @@ -79,6 +80,22 @@ public class Authorization { //Okay, we can return the access token to be used when this method is called. return autoRefreshResponse.getString("access_token"); + } else if (httpResponse.code() == GlobalConst.STATUS_BAD_REQUEST) { + JSONObject errorBody = new JSONObject(httpResponse.body().string()); + + if ("invalid_grant".equalsIgnoreCase(errorBody.getString("error"))) { + // User revoked access to the calendar, delete our reference to it since they need to re-auth anyway + + DatabaseManager.getCalendar(settings.getGuildID(), 1) + .flatMap(cd -> CalendarUtils.deleteCalendar(cd, settings)) + .subscribe(); + } else { + LogFeed.log(LogObject.forDebug("Error requesting new access token.", + "Status code: " + httpResponse.code() + " | " + httpResponse.message() + + " | " + errorBody)); + } + + return null; } else { //Failed to get OK. Send debug info. LogFeed.log(LogObject.forDebug("Error requesting new access token.", @@ -87,7 +104,7 @@ public class Authorization { return null; } - } catch (final Exception e) { + } catch (Exception e) { //Error occurred, lets just log it and return null. LogFeed.log(LogObject .forException("Failed to request new access token.", e, this.getClass()));