diff options
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.java | 110 |
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 = |