summaryrefslogtreecommitdiff
path: root/google-login-plugin/src/com/google/gct/login/GoogleLoginState.java
diff options
context:
space:
mode:
Diffstat (limited to 'google-login-plugin/src/com/google/gct/login/GoogleLoginState.java')
-rw-r--r--google-login-plugin/src/com/google/gct/login/GoogleLoginState.java110
1 files changed, 87 insertions, 23 deletions
diff --git a/google-login-plugin/src/com/google/gct/login/GoogleLoginState.java b/google-login-plugin/src/com/google/gct/login/GoogleLoginState.java
index 7fcc689..df9879a 100644
--- a/google-login-plugin/src/com/google/gct/login/GoogleLoginState.java
+++ b/google-login-plugin/src/com/google/gct/login/GoogleLoginState.java
@@ -15,7 +15,9 @@
*/
package com.google.gct.login;
+import com.android.tools.analytics.UsageTracker;
import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleRefreshTokenRequest;
@@ -36,6 +38,11 @@ import com.google.gct.login.common.OAuthDataStore;
import com.google.gct.login.common.UiFacade;
import com.google.gct.login.common.VerificationCodeHolder;
import com.google.gson.Gson;
+import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
+import com.google.wireless.android.sdk.stats.GoogleLoginPluginEvent;
+import com.intellij.notification.NotificationAction;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -44,8 +51,10 @@ import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.SortedSet;
+import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
/**
* Provides methods for logging into and out of Google services via OAuth 2.0, and for fetching
@@ -59,6 +68,9 @@ import org.jetbrains.annotations.Nullable;
public class GoogleLoginState {
private static final String GET_EMAIL_URL = "https://openidconnect.googleapis.com/v1/userinfo";
+ private static final String EXPIRED_OR_REVOKED_MESSAGE = "Token has been expired or revoked.";
+ private static final NotificationGroup notificationGroup = NotificationGroup.findRegisteredGroup("Google Login");
+ private static boolean isNotificationShowing = false;
private static final JsonFactory jsonFactory = new JacksonFactory();
private static final HttpTransport transport = new NetHttpTransport();
@@ -76,10 +88,26 @@ public class GoogleLoginState {
private boolean isLoggedIn;
private String email;
private boolean connected; // whether we connected to the internet
+ private final Function<VerificationCodeHolder, GoogleAuthorizationCodeTokenRequest> authCodeTokenRequestFactory;
private final Collection<LoginListener> listeners;
/**
+ * Logs the login state change event
+ * @param eventKind - GoogleLoginPluginEvent.EventKind that is to be logged
+ */
+ static void trackEvent(GoogleLoginPluginEvent.EventKind eventKind) {
+ UsageTracker.log(
+ AndroidStudioEvent.newBuilder()
+ .setKind(AndroidStudioEvent.EventKind.GOOGLE_LOGIN_EVENT)
+ .setGoogleLoginEvent(
+ GoogleLoginPluginEvent.newBuilder().setEvent(eventKind).build()
+ )
+ );
+ }
+
+
+ /**
* Construct a new platform-specific {@code GoogleLoginState} for a specified client application
* and specified authorization scopes.
*
@@ -92,7 +120,26 @@ public class GoogleLoginState {
*/
public GoogleLoginState(
String clientId, String clientSecret, SortedSet<String> oAuthScopes,
- OAuthDataStore authDataStore, UiFacade uiFacade) {
+ OAuthDataStore authDataStore, UiFacade uiFacade, boolean trackLogin) {
+ this(clientId, clientSecret, oAuthScopes,
+ authDataStore, uiFacade, trackLogin,
+ (verificationCodeHolder) -> new GoogleAuthorizationCodeTokenRequest(
+ transport,
+ jsonFactory,
+ clientId,
+ clientSecret,
+ verificationCodeHolder.getVerificationCode(),
+ verificationCodeHolder.getRedirectUrl()
+ )
+ );
+ }
+
+ @VisibleForTesting
+ public GoogleLoginState(
+ String clientId, String clientSecret, SortedSet<String> oAuthScopes,
+ OAuthDataStore authDataStore, UiFacade uiFacade, boolean trackLogin,
+ Function<VerificationCodeHolder, GoogleAuthorizationCodeTokenRequest> authCodeTokenRequestFactory
+ ) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.oAuthScopes = oAuthScopes;
@@ -101,10 +148,11 @@ public class GoogleLoginState {
this.isLoggedIn = false;
this.email = "";
+ this.authCodeTokenRequestFactory = authCodeTokenRequestFactory;
connected = true; // assume we're connected until checkCredentials is called
listeners = Lists.newLinkedList();
- retrieveSavedCredentials();
+ retrieveSavedCredentials(trackLogin);
}
/**
@@ -149,8 +197,8 @@ public class GoogleLoginState {
* @throws IOException if something goes wrong while fetching the token.
*
*/
- public String fetchAccessToken() throws IOException {
- if (!checkLoggedIn(null)) {
+ public synchronized String fetchAccessToken() throws IOException {
+ if (!isLoggedIn) {
return null;
}
if (accessTokenExpiryTime != 0) {
@@ -189,14 +237,11 @@ public class GoogleLoginState {
* Makes a request to get an OAuth2 access token from the OAuth2 refresh
* token. This token is short lived.
*
- * @return an OAuth2 token, or null if there was an error or if the user
- * wasn't signed in and canceled signing in.
* @throws IOException if something goes wrong while fetching the token.
- *
*/
- public String fetchOAuth2Token() throws IOException {
+ private void fetchOAuth2Token() throws IOException {
if (!checkLoggedIn(null)) {
- return null;
+ return;
}
try {
@@ -207,12 +252,33 @@ public class GoogleLoginState {
oAuth2Credential.setAccessToken(accessToken);
accessTokenExpiryTime = new GregorianCalendar().getTimeInMillis() / 1000
+ authResponse.getExpiresInSeconds();
+ } catch (TokenResponseException e) {
+ if (e.getDetails().getErrorDescription().equals(EXPIRED_OR_REVOKED_MESSAGE)) {
+ getLogger().warn(EXPIRED_OR_REVOKED_MESSAGE, e);
+ if (!isNotificationShowing) {
+ isNotificationShowing = true;
+ // TODO(b/275738836): Use FORCE_LOGOUT to represent this logout action.
+ GoogleLogin.getInstance().logOut(false);
+ notificationGroup.createNotification(
+ "Authentication error",
+ "Your session has expired. Please login again",
+ NotificationType.WARNING
+ ).addAction(
+ NotificationAction.createExpiring(
+ "Login...",
+ (action, notification) -> GoogleLogin.getInstance().logIn()
+ )
+ ).whenExpired(
+ () -> isNotificationShowing = false
+ ).notify(null);
+ }
+ throw e;
+ }
} catch (IOException e) {
getLogger().warn("Could not obtain an OAuth2 access token.", e);
throw e;
}
saveCredentials();
- return accessToken;
}
public Credential getCredential() {
@@ -276,14 +342,7 @@ public class GoogleLoginState {
return false;
}
- GoogleAuthorizationCodeTokenRequest authRequest =
- new GoogleAuthorizationCodeTokenRequest(
- transport,
- jsonFactory,
- clientId,
- clientSecret,
- verificationCodeHolder.getVerificationCode(),
- verificationCodeHolder.getRedirectUrl());
+ GoogleAuthorizationCodeTokenRequest authRequest = authCodeTokenRequestFactory.apply(verificationCodeHolder);
GoogleTokenResponse authResponse;
try {
authResponse = authRequest.execute();
@@ -295,6 +354,7 @@ public class GoogleLoginState {
return false;
}
isLoggedIn = true;
+ trackEvent(GoogleLoginPluginEvent.EventKind.LOGIN_WITH_SUCCESS);
updateUserCredentials(authResponse);
return true;
}
@@ -320,7 +380,7 @@ public class GoogleLoginState {
if (logOut) {
email = "";
isLoggedIn = false;
-
+ trackEvent(GoogleLoginPluginEvent.EventKind.LOGOUT_WITH_SUCCESS);
authDataStore.clearStoredOAuthData();
notifyLoginStatusChange(false);
@@ -348,13 +408,13 @@ public class GoogleLoginState {
oAuth2Credential = makeCredential();
accessTokenExpiryTime = System.currentTimeMillis() / 1000
+ tokenResponse.getExpiresInSeconds();
- email = queryEmail();
+ email = queryEmail(createRequestFactory(null));
saveCredentials();
uiFacade.notifyStatusIndicator();
notifyLoginStatusChange(true);
}
- private void retrieveSavedCredentials() {
+ private void retrieveSavedCredentials(boolean trackLogin) {
OAuthData savedAuthState = authDataStore.loadOAuthData();
@@ -372,6 +432,9 @@ public class GoogleLoginState {
this.email = savedAuthState.getStoredEmail();
isLoggedIn = true;
+ if (trackLogin) {
+ trackEvent(GoogleLoginPluginEvent.EventKind.LOGIN_WITH_SUCCESS);
+ }
if (!oAuthScopes.equals(savedAuthState.getStoredScopes())) {
getLogger().warn(
@@ -406,12 +469,13 @@ public class GoogleLoginState {
}
}
- private String queryEmail() {
+ @VisibleForTesting
+ protected static String queryEmail(HttpRequestFactory requestFactory) {
String url = GET_EMAIL_URL;
HttpResponse resp;
try {
- HttpRequest get = createRequestFactory(null).buildGetRequest(new GenericUrl(url));
+ HttpRequest get = requestFactory.buildGetRequest(new GenericUrl(url));
resp = get.execute();
Type responseData = new TypeToken<LoginResponseHolder>() {}.getType();
LoginResponseHolder loginResponseHolder =