diff --git a/eclipse/ide-target-platform/category.xml b/eclipse/ide-target-platform/category.xml
index c38046f417..fdde41c243 100644
--- a/eclipse/ide-target-platform/category.xml
+++ b/eclipse/ide-target-platform/category.xml
@@ -26,4 +26,5 @@
+
diff --git a/features/com.google.cloud.tools.eclipse.3rdparty.feature/feature.xml b/features/com.google.cloud.tools.eclipse.3rdparty.feature/feature.xml
index d38d5784aa..ced44cc7d9 100644
--- a/features/com.google.cloud.tools.eclipse.3rdparty.feature/feature.xml
+++ b/features/com.google.cloud.tools.eclipse.3rdparty.feature/feature.xml
@@ -60,4 +60,11 @@
version="0.0.0"
unpack="false"/>
+
+
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.deploy/src/com/google/cloud/tools/eclipse/appengine/deploy/standard/StandardDeployCommandHandler.java b/plugins/com.google.cloud.tools.eclipse.appengine.deploy/src/com/google/cloud/tools/eclipse/appengine/deploy/standard/StandardDeployCommandHandler.java
index 4948e852b6..133bfc9860 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.deploy/src/com/google/cloud/tools/eclipse/appengine/deploy/standard/StandardDeployCommandHandler.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.deploy/src/com/google/cloud/tools/eclipse/appengine/deploy/standard/StandardDeployCommandHandler.java
@@ -1,7 +1,30 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
package com.google.cloud.tools.eclipse.appengine.deploy.standard;
-import java.io.IOException;
-import java.nio.file.Files;
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.cloud.tools.eclipse.appengine.deploy.AppEngineProjectDeployer;
+import com.google.cloud.tools.eclipse.appengine.deploy.CleanupOldDeploysJob;
+import com.google.cloud.tools.eclipse.appengine.deploy.Messages;
+import com.google.cloud.tools.eclipse.appengine.login.IGoogleLoginService;
+import com.google.cloud.tools.eclipse.ui.util.ProjectFromSelectionHelper;
+import com.google.cloud.tools.eclipse.util.FacetedProjectHelper;
+import com.google.cloud.tools.eclipse.util.ServiceUtils;
+import com.google.cloud.tools.eclipse.util.status.StatusUtil;
+import com.google.common.annotations.VisibleForTesting;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
@@ -12,19 +35,9 @@
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
-import org.eclipse.jface.window.IShellProvider;
-import org.eclipse.jface.window.SameShellProvider;
-import org.eclipse.ui.handlers.HandlerUtil;
-import com.google.api.client.auth.oauth2.Credential;
-import com.google.cloud.tools.eclipse.appengine.deploy.AppEngineProjectDeployer;
-import com.google.cloud.tools.eclipse.appengine.deploy.CleanupOldDeploysJob;
-import com.google.cloud.tools.eclipse.appengine.deploy.Messages;
-import com.google.cloud.tools.eclipse.appengine.login.GoogleLoginService;
-import com.google.cloud.tools.eclipse.ui.util.ProjectFromSelectionHelper;
-import com.google.cloud.tools.eclipse.util.FacetedProjectHelper;
-import com.google.cloud.tools.eclipse.util.status.StatusUtil;
-import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.nio.file.Files;
/**
* Command handler to deploy an App Engine web application project to App Engine Standard.
@@ -35,22 +48,22 @@
public class StandardDeployCommandHandler extends AbstractHandler {
private ProjectFromSelectionHelper helper;
-
+
public StandardDeployCommandHandler() {
this(new FacetedProjectHelper());
}
-
+
@VisibleForTesting
StandardDeployCommandHandler(FacetedProjectHelper facetedProjectHelper) {
this.helper = new ProjectFromSelectionHelper(facetedProjectHelper);
}
-
+
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
try {
IProject project = helper.getProject(event);
if (project != null) {
- launchDeployJob(project, new SameShellProvider(HandlerUtil.getActiveShell(event)));
+ launchDeployJob(project, ServiceUtils.getService(event, IGoogleLoginService.class));
}
// return value must be null, reserved for future use
return null;
@@ -59,10 +72,11 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
}
}
- private void launchDeployJob(IProject project, IShellProvider shellProvider) throws IOException, CoreException {
+ private void launchDeployJob(IProject project, IGoogleLoginService loginService)
+ throws IOException, CoreException {
IPath workDirectory = createWorkDirectory();
- Credential credential = login(shellProvider);
-
+ Credential credential = login(loginService);
+
StandardDeployJob deploy =
new StandardDeployJob(new ExplodedWarPublisher(),
new StandardProjectStaging(),
@@ -88,8 +102,8 @@ private IPath createWorkDirectory() throws IOException {
return workDirectory;
}
- private Credential login(IShellProvider shellProvider) throws IOException, CoreException {
- Credential credential = new GoogleLoginService().getActiveCredential(shellProvider);
+ private Credential login(IGoogleLoginService loginService) throws CoreException {
+ Credential credential = loginService.getActiveCredential();
if (credential == null) {
throw new CoreException(StatusUtil.error(getClass(), Messages.getString("login.failed")));
}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/META-INF/MANIFEST.MF b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/META-INF/MANIFEST.MF
index 7efe5e321d..b89b8fd32d 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/META-INF/MANIFEST.MF
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/META-INF/MANIFEST.MF
@@ -10,6 +10,7 @@ Fragment-Host: com.google.cloud.tools.eclipse.appengine.login
Require-Bundle: org.hamcrest;bundle-version="1.1.0",
org.junit;bundle-version="4.12.0"
Import-Package: org.mockito;provider=google;version="1.10.19",
+ org.mockito.invocation;provider=google;version="1.10.19",
org.mockito.runners;provider=google;version="1.10.19",
org.mockito.stubbing;provider=google;version="1.10.19",
org.objenesis;provider=google;version="2.2.0"
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelperTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelperTest.java
index c174831908..81c3ca299a 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelperTest.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelperTest.java
@@ -4,23 +4,17 @@
import org.junit.Test;
import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson.JacksonFactory;
import com.google.gson.Gson;
public class CredentialHelperTest {
- @Test
- public void testCreateCredential() {
- Credential credential = new CredentialHelper().createCredential("fake_access_token", "fake_refresh_token");
-
- Assert.assertEquals(credential.getAccessToken(), "fake_access_token");
- Assert.assertEquals(credential.getRefreshToken(), "fake_refresh_token");
- }
-
@Test
public void testGetJsonCredential() {
- CredentialHelper credentialHelper = new CredentialHelper();
- Credential credential = credentialHelper.createCredential("fake_access_token", "fake_refresh_token");
- String jsonCredential = credentialHelper.toJson(credential);
+ Credential credential = createCredential("fake_access_token", "fake_refresh_token");
+ String jsonCredential = new CredentialHelper().toJson(credential);
CredentialType credentialType = new Gson().fromJson(jsonCredential, CredentialType.class);
Assert.assertEquals(credentialType.client_id, Constants.getOAuthClientId());
@@ -35,4 +29,15 @@ private class CredentialType {
private String refresh_token;
private String type;
};
+
+ private Credential createCredential(String accessToken, String refreshToken) {
+ GoogleCredential credential = new GoogleCredential.Builder()
+ .setTransport(new NetHttpTransport())
+ .setJsonFactory(new JacksonFactory())
+ .setClientSecrets(Constants.getOAuthClientId(), Constants.getOAuthClientSecret())
+ .build();
+ credential.setAccessToken(accessToken);
+ credential.setRefreshToken(refreshToken);
+ return credential;
+ }
}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginServiceTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginServiceTest.java
new file mode 100644
index 0000000000..1a6a850095
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginServiceTest.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
+package com.google.cloud.tools.eclipse.appengine.login;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.cloud.tools.eclipse.appengine.login.ui.LoginServiceUi;
+import com.google.cloud.tools.ide.login.LoggerFacade;
+import com.google.cloud.tools.ide.login.OAuthData;
+import com.google.cloud.tools.ide.login.OAuthDataStore;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GoogleLoginServiceTest {
+
+ @Mock private OAuthDataStore dataStore;
+ @Mock private OAuthData savedOAuthData;
+ @Mock private LoginServiceUi uiFacade;
+ @Mock private LoggerFacade loggerFacade;
+
+ private static final SortedSet OAUTH_SCOPES = Collections.unmodifiableSortedSet(
+ new TreeSet<>(Arrays.asList(
+ "email",
+ "https://www.googleapis.com/auth/cloud-platform"
+ )));
+
+ @Before
+ public void setUp() {
+ when(dataStore.loadOAuthData()).thenReturn(savedOAuthData);
+ }
+
+ @Test
+ public void testGoogleLoginService_clearSavedCredentialIfNullRefreshToken() {
+ when(savedOAuthData.getRefreshToken()).thenReturn(null);
+
+ GoogleLoginService loginService = new GoogleLoginService(dataStore, uiFacade, loggerFacade);
+ Assert.assertNull(loginService.getCachedActiveCredential());
+ }
+
+ @Test
+ public void testGoogleLoginService_clearSavedCredentialIfScopesChanged() {
+ // Persisted credential in the data store has an out-dated scopes.
+ SortedSet newScope = new TreeSet(Arrays.asList("new scope"));
+ when(savedOAuthData.getStoredScopes()).thenReturn(newScope);
+ when(savedOAuthData.getRefreshToken()).thenReturn("fake_refresh_token");
+
+ GoogleLoginService loginService = new GoogleLoginService(dataStore, uiFacade, loggerFacade);
+ Assert.assertNull(loginService.getCachedActiveCredential());
+ }
+
+ @Test
+ public void testGoogleLoginService_restoreSavedCredential() {
+ // Persisted credential in the data store is valid.
+ when(savedOAuthData.getStoredScopes()).thenReturn(OAUTH_SCOPES);
+ when(savedOAuthData.getRefreshToken()).thenReturn("fake_refresh_token");
+
+ GoogleLoginService loginService = new GoogleLoginService(dataStore, uiFacade, loggerFacade);
+ verify(dataStore, never()).clearStoredOAuthData();
+ Assert.assertNotNull(loginService.getCachedActiveCredential());
+ }
+}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/TransientOAuthDataStoreTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/TransientOAuthDataStoreTest.java
new file mode 100644
index 0000000000..dd73dabe96
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/TransientOAuthDataStoreTest.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
+package com.google.cloud.tools.eclipse.appengine.login;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.cloud.tools.ide.login.OAuthData;
+
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransientOAuthDataStoreTest {
+
+ @Mock private IEclipseContext eclipseContext;
+
+ @Test
+ public void testLoadOAuthData_emptyStoreReturnsNonNullOAuthData() {
+ when(eclipseContext.get(anyString())).thenReturn(null);
+
+ OAuthData oAuthData = new TransientOAuthDataStore(eclipseContext).loadOAuthData();
+ Assert.assertNotNull(oAuthData);
+ Assert.assertEquals(null, oAuthData.getAccessToken());
+ Assert.assertEquals(null, oAuthData.getRefreshToken());
+ Assert.assertEquals(null, oAuthData.getStoredEmail());
+ Assert.assertEquals(0, oAuthData.getAccessTokenExpiryTime());
+ }
+
+ @Test
+ public void testSaveAndLoadOAuthData() {
+ OAuthData inputData = mock(OAuthData.class);
+ TransientOAuthDataStore dataStore = new TransientOAuthDataStore(eclipseContext);
+ dataStore.saveOAuthData(inputData);
+ dataStore.loadOAuthData();
+
+ ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(OAuthData.class);
+ verify(eclipseContext).set(anyString(), argumentCaptor.capture());
+ verify(eclipseContext).get(anyString());
+ Assert.assertEquals(inputData, argumentCaptor.getValue());
+ }
+}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/ui/GoogleLoginBrowserTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/ui/GoogleLoginBrowserTest.java
deleted file mode 100644
index a3eb4f54ec..0000000000
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login.test/src/com/google/cloud/tools/eclipse/appengine/login/ui/GoogleLoginBrowserTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*******************************************************************************
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * All rights reserved. This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License v1.0 which
- * accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- *******************************************************************************/
-
-package com.google.cloud.tools.eclipse.appengine.login.ui;
-
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import org.eclipse.swt.browser.TitleEvent;
-import org.eclipse.swt.widgets.Widget;
-import org.junit.Test;
-
-public class GoogleLoginBrowserTest {
-
- @Test
- public void testTitleListener_nonCodeTitle() {
- GoogleLoginBrowser loginBrowser = mock(GoogleLoginBrowser.class);
- TitleEvent titleEvent = new TitleEvent(mock(Widget.class));
- titleEvent.title = "Arbitrary HTML Page Title";
-
- new AuthorizationCodeListener(loginBrowser).changed(titleEvent);
-
- verify(loginBrowser, never()).setAuthorizationCode(anyString());
- verify(loginBrowser, never()).logOutAndClose();
- }
-
- @Test
- public void testTitleListener_authorizationCodeTitle() {
- GoogleLoginBrowser loginBrowser = mock(GoogleLoginBrowser.class);
- TitleEvent titleEvent = new TitleEvent(mock(Widget.class));
- titleEvent.title = "Success code=fake_authorization_code";
-
- new AuthorizationCodeListener(loginBrowser).changed(titleEvent);
-
- verify(loginBrowser, times(1)).setAuthorizationCode(eq("fake_authorization_code"));
- verify(loginBrowser, times(1)).logOutAndClose();
- }
-}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/.classpath b/plugins/com.google.cloud.tools.eclipse.appengine.login/.classpath
index 23a5555125..efd44c4958 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/.classpath
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/.classpath
@@ -4,10 +4,11 @@
+
+
-
+
-
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/.project b/plugins/com.google.cloud.tools.eclipse.appengine.login/.project
index 78cb12abfb..b0e3923af3 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/.project
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/.project
@@ -25,6 +25,11 @@
+
+ org.eclipse.pde.ds.core.builder
+
+
+
org.eclipse.m2e.core.maven2Nature
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/META-INF/MANIFEST.MF b/plugins/com.google.cloud.tools.eclipse.appengine.login/META-INF/MANIFEST.MF
index c230b72d97..8e90500fa4 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/META-INF/MANIFEST.MF
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/META-INF/MANIFEST.MF
@@ -7,19 +7,21 @@ Bundle-Vendor: Google, Inc.
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-ActivationPolicy: lazy
-Require-Bundle: com.google.cloud.tools.app.lib;bundle-version="0.1.0",
- com.google.gson,
+Service-Component: OSGI-INF/login.xml
+Require-Bundle: com.google.gson,
com.google.guava;bundle-version="15.0.0",
+ jackson-core-asl,
org.eclipse.core.runtime,
org.eclipse.e4.core.contexts,
org.eclipse.ui
-Bundle-Classpath: lib/google-api-client-1.22.0.jar,
+Bundle-Classpath: lib/com.google.cloud.tools.ide.login-0.0.0-SNAPSHOT.jar,
+ lib/google-api-client-1.22.0.jar,
+ lib/google-api-services-oauth2-v2-rev114-1.22.0.jar,
lib/google-http-client-1.22.0.jar,
- lib/google-http-client-jackson2-1.22.0.jar,
+ lib/google-http-client-jackson-1.22.0.jar,
lib/google-oauth-client-1.22.0.jar,
- lib/jackson-core-2.1.3.jar,
.
Export-Package: com.google.api.client.auth.oauth2;x-friends:="com.google.cloud.tools.eclipse.appengine.deploy",
- com.google.cloud.tools.eclipse.appengine.login,
- com.google.cloud.tools.eclipse.appengine.login.ui;x-friends:="com.google.cloud.tools.eclipse.appengine.deploy"
-Import-Package: com.google.cloud.tools.eclipse.util.status
+ com.google.cloud.tools.eclipse.appengine.login
+Import-Package: com.google.cloud.tools.eclipse.util,
+ org.osgi.service.component
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/OSGI-INF/login.xml b/plugins/com.google.cloud.tools.eclipse.appengine.login/OSGI-INF/login.xml
new file mode 100644
index 0000000000..d1fb019508
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/OSGI-INF/login.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/build.properties b/plugins/com.google.cloud.tools.eclipse.appengine.login/build.properties
index 1d48b28b7b..b42b629f3d 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/build.properties
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/build.properties
@@ -6,6 +6,7 @@ bin.includes = META-INF/,\
plugin.xml,\
plugin.properties,\
icons/,\
- lib/
+ lib/,\
+ OSGI-INF/
javacSource=1.7
javacTarget=1.7
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/com.google.cloud.tools.ide.login-0.0.0-SNAPSHOT.jar b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/com.google.cloud.tools.ide.login-0.0.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..945103b494
Binary files /dev/null and b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/com.google.cloud.tools.ide.login-0.0.0-SNAPSHOT.jar differ
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-api-services-oauth2-v2-rev114-1.22.0.jar b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-api-services-oauth2-v2-rev114-1.22.0.jar
new file mode 100644
index 0000000000..1dc24f17dd
Binary files /dev/null and b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-api-services-oauth2-v2-rev114-1.22.0.jar differ
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-http-client-jackson-1.22.0.jar b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-http-client-jackson-1.22.0.jar
new file mode 100644
index 0000000000..d599c56402
Binary files /dev/null and b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-http-client-jackson-1.22.0.jar differ
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-http-client-jackson2-1.22.0.jar b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-http-client-jackson2-1.22.0.jar
deleted file mode 100644
index 07fae6461f..0000000000
Binary files a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/google-http-client-jackson2-1.22.0.jar and /dev/null differ
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/jackson-core-2.1.3.jar b/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/jackson-core-2.1.3.jar
deleted file mode 100644
index f47619f41f..0000000000
Binary files a/plugins/com.google.cloud.tools.eclipse.appengine.login/lib/jackson-core-2.1.3.jar and /dev/null differ
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelper.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelper.java
index 62be25be0d..3fed62ff13 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelper.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/CredentialHelper.java
@@ -4,8 +4,6 @@
import java.util.Map;
import com.google.api.client.auth.oauth2.Credential;
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
-import com.google.api.client.googleapis.util.Utils;
import com.google.gson.Gson;
/**
@@ -19,20 +17,6 @@ public class CredentialHelper {
private static final String GCLOUD_USER_TYPE_LABEL = "type";
private static final String GCLOUD_USER_TYPE = "authorized_user";
- /**
- * Creates a {@link Credential} that has its access and refresh token set
- */
- public Credential createCredential(String accessToken, String refreshToken) {
- GoogleCredential credential = new GoogleCredential.Builder()
- .setTransport(Utils.getDefaultTransport())
- .setJsonFactory(Utils.getDefaultJsonFactory())
- .setClientSecrets(Constants.getOAuthClientId(), Constants.getOAuthClientSecret())
- .build();
- credential.setAccessToken(accessToken);
- credential.setRefreshToken(refreshToken);
- return credential;
- }
-
/**
* Writes a {@link Credential} object in the JSON format expected by gcloud's credential-file-override
* feature
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginCommandHandler.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginCommandHandler.java
index 77db75894a..1874c9ee0e 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginCommandHandler.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginCommandHandler.java
@@ -1,58 +1,47 @@
package com.google.cloud.tools.eclipse.appengine.login;
-import java.io.IOException;
-import java.util.Map;
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.cloud.tools.eclipse.util.ServiceUtils;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.window.SameShellProvider;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.commands.IElementUpdater;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.menus.UIElement;
-import com.google.api.client.auth.oauth2.Credential;
+import java.util.Map;
public class GoogleLoginCommandHandler extends AbstractHandler implements IElementUpdater {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
- Shell shell = HandlerUtil.getActiveShell(event);
- GoogleLoginService loginService = new GoogleLoginService();
+ IGoogleLoginService loginService = ServiceUtils.getService(event, IGoogleLoginService.class);
Credential credential = loginService.getCachedActiveCredential();
if (credential == null) {
- try {
- credential = loginService.getActiveCredential(new SameShellProvider(shell));
-
- boolean success = new GoogleLoginTemporaryTester().testLogin(credential);
- MessageDialog.openInformation(shell,
- "TESTING AUTH", success ? "SUCCESS" : "FAILURE (to be implemented)");
- } catch (IOException ioe) {
- throw new ExecutionException(ioe.getMessage());
- }
+ credential = loginService.getActiveCredential();
} else {
- if (MessageDialog.openConfirm(shell,
+ if (MessageDialog.openConfirm(HandlerUtil.getActiveShell(event),
Messages.LOGOUT_CONFIRM_DIALOG_TITILE, Messages.LOGOUT_CONFIRM_DIALOG_MESSAGE)) {
loginService.clearCredential();
}
}
- ICommandService commandService =
- (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
- commandService.refreshElements(
- "com.google.cloud.tools.eclipse.appengine.login.commands.loginCommand", null); //$NON-NLS-1$
-
+ if (credential != null) {
+ boolean success = new GoogleLoginTemporaryTester().testLogin(credential);
+ MessageDialog.openInformation(HandlerUtil.getActiveShell(event),
+ "TESTING AUTH", success ? "WORKING CREDENTIAL" : "FAILURE (See console)");
+ }
return null;
}
@Override
public void updateElement(UIElement element, @SuppressWarnings("rawtypes") Map parameters) {
- boolean loggedIn = new GoogleLoginService().getCachedActiveCredential() != null;
+ IGoogleLoginService loginService =
+ element.getServiceLocator().getService(IGoogleLoginService.class);
+ boolean loggedIn = loginService.getCachedActiveCredential() != null;
element.setText(
loggedIn ? Messages.LOGIN_MENU_LOGGED_IN : Messages.LOGIN_MENU_LOGGED_OUT);
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginService.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginService.java
index bf2c483a5b..3afa112f9d 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginService.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginService.java
@@ -16,96 +16,136 @@
package com.google.cloud.tools.eclipse.appengine.login;
import com.google.api.client.auth.oauth2.Credential;
-import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
-import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
-import com.google.api.client.googleapis.util.Utils;
-import com.google.cloud.tools.eclipse.appengine.login.ui.GoogleLoginBrowser;
+import com.google.cloud.tools.eclipse.appengine.login.ui.LoginServiceUi;
+import com.google.cloud.tools.ide.login.GoogleLoginState;
+import com.google.cloud.tools.ide.login.LoggerFacade;
+import com.google.cloud.tools.ide.login.OAuthDataStore;
+import com.google.common.annotations.VisibleForTesting;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.jface.window.IShellProvider;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
-import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
-import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Provides service related to login, e.g., account management, getting a credential of a
* currently active user, etc.
*/
-public class GoogleLoginService {
-
- private static final String STASH_OAUTH_CRED_KEY = "OAUTH_CRED";
+public class GoogleLoginService implements IGoogleLoginService {
// For the detailed info about each scope, see
// https://github.com/GoogleCloudPlatform/gcloud-eclipse-tools/wiki/Cloud-Tools-for-Eclipse-Technical-Design#oauth-20-scopes-requested
- private static final List OAUTH_SCOPES = Collections.unmodifiableList(Arrays.asList(
- "https://www.googleapis.com/auth/cloud-platform" //$NON-NLS-1$
- ));
+ private static final SortedSet OAUTH_SCOPES = Collections.unmodifiableSortedSet(
+ new TreeSet<>(Arrays.asList(
+ "email", //$NON-NLS-1$
+ "https://www.googleapis.com/auth/cloud-platform" //$NON-NLS-1$
+ )));
- private CredentialHelper credentialHelper = new CredentialHelper();
-
- /**
- * Returns the credential of an active user (among multiple logged-in users). A login screen
- * may be presented, e.g., if no user is logged in or login is required due to an expired
- * credential. This method returns {@code null} if a user cancels the login process.
- * For this reason, if {@code null} is returned, the caller should cancel the current
- * operation and display a general message that login is required but was cancelled or failed.
- *
- * Must be called from a UI context.
- *
- * @param shellProvider provides a shell for the login screen if login is necessary
- * @throws IOException can be thrown by the underlying Login API request (e.g., network
- * error from the transport layer while sending/receiving a HTTP request/response.)
- */
- public Credential getActiveCredential(IShellProvider shellProvider) throws IOException {
- Credential credential = getCachedActiveCredential();
+ private GoogleLoginState loginState;
+ private AtomicBoolean loginInProgress;
- if (credential == null) {
- credential = logIn(shellProvider);
+ private LoginServiceUi loginServiceUi;
- IEclipseContext eclipseContext = PlatformUI.getWorkbench().getService(IEclipseContext.class);
- eclipseContext.set(STASH_OAUTH_CRED_KEY, credential);
- }
- return credential;
+ /**
+ * Called by OSGi Declarative Services Runtime when the {@link GoogleLoginService} is activated
+ * as an OSGi service.
+ */
+ protected void activate() {
+ final IWorkbench workbench = PlatformUI.getWorkbench();
+ IEclipseContext eclipseContext = workbench.getService(IEclipseContext.class);
+ IShellProvider shellProvider = new IShellProvider() {
+ @Override
+ public Shell getShell() {
+ return workbench.getDisplay().getActiveShell();
+ }
+ };
+
+ loginServiceUi = new LoginServiceUi(workbench, shellProvider);
+ loginState = new GoogleLoginState(
+ Constants.getOAuthClientId(), Constants.getOAuthClientSecret(), OAUTH_SCOPES,
+ new TransientOAuthDataStore(eclipseContext), loginServiceUi, new LoginServiceLogger());
+ loginInProgress = new AtomicBoolean(false);
}
/**
- * Returns the credential of an active user (among multiple logged-in users). Unlike {@link
- * #getActiveCredential}, this version does not involve login process or make API calls.
- * Returns {@code null} if no credential has been cached.
- *
- * Safe to call from non-UI contexts.
+ * 0-arg constructor is necessary for OSGi Declarative Services. Initialization will be done
+ * by {@link activate()}.
*/
- public Credential getCachedActiveCredential() {
- IEclipseContext eclipseContext = PlatformUI.getWorkbench().getService(IEclipseContext.class);
- return (Credential) eclipseContext.get(STASH_OAUTH_CRED_KEY);
+ public GoogleLoginService() {}
+
+ @VisibleForTesting
+ GoogleLoginService(
+ OAuthDataStore dataStore, LoginServiceUi uiFacade, LoggerFacade loggerFacade) {
+ loginServiceUi = uiFacade;
+ loginState = new GoogleLoginState(
+ Constants.getOAuthClientId(), Constants.getOAuthClientSecret(), OAUTH_SCOPES,
+ dataStore, uiFacade, loggerFacade);
+ loginInProgress = new AtomicBoolean(false);
}
- private Credential logIn(IShellProvider shellProvider) throws IOException {
- GoogleLoginBrowser loginBrowser = new GoogleLoginBrowser(
- shellProvider.getShell(), Constants.getOAuthClientId(), OAUTH_SCOPES);
- if (loginBrowser.open() != GoogleLoginBrowser.OK) {
+ @Override
+ public Credential getActiveCredential() {
+ if (!loginInProgress.compareAndSet(false, true)) {
+ loginServiceUi.showErrorDialogHelper(
+ Messages.LOGIN_ERROR_DIALOG_TITLE, Messages.LOGIN_ERROR_IN_PROGRESS);
return null;
}
- GoogleAuthorizationCodeTokenRequest authRequest = new GoogleAuthorizationCodeTokenRequest(
- Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(),
- Constants.getOAuthClientId(), Constants.getOAuthClientSecret(),
- loginBrowser.getAuthorizationCode(),
- GoogleLoginBrowser.REDIRECT_URI);
-
- return createCredential(authRequest.execute());
+ // TODO: holding a lock for a long period of time (especially when waiting for UI events)
+ // should be avoided. Make the login library thread-safe, and don't lock during UI events.
+ // As a workaround and temporary relief, we use the loginInProgress flag above to fail
+ // conservatively if login seems to be in progress.
+ try {
+ synchronized (loginState) {
+ if (loginState.logIn(null /* parameter ignored */)) {
+ return loginState.getCredential();
+ }
+ return null;
+ }
+ }
+ finally {
+ loginInProgress.set(false);
+ }
}
- private Credential createCredential(GoogleTokenResponse tokenResponse) {
- return credentialHelper.createCredential(tokenResponse.getAccessToken(),
- tokenResponse.getRefreshToken());
+ @Override
+ public Credential getCachedActiveCredential() {
+ synchronized (loginState) {
+ if (loginState.isLoggedIn()) {
+ return loginState.getCredential();
+ }
+ return null;
+ }
}
+ @Override
public void clearCredential() {
- IEclipseContext eclipseContext = PlatformUI.getWorkbench().getService(IEclipseContext.class);
- eclipseContext.remove(STASH_OAUTH_CRED_KEY);
+ synchronized (loginState) {
+ loginState.logOut(false /* Don't prompt for logout. */);
+ }
}
+
+ private static final Logger logger = Logger.getLogger(GoogleLoginService.class.getName());
+
+ private static class LoginServiceLogger implements LoggerFacade {
+
+ @Override
+ public void logError(String message, Throwable thrown) {
+ logger.log(Level.SEVERE, message, thrown);
+ }
+
+ @Override
+ public void logWarning(String message) {
+ logger.log(Level.WARNING, message);
+ }
+ };
}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginTemporaryTester.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginTemporaryTester.java
index 79ca746020..a55a55e2b5 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginTemporaryTester.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/GoogleLoginTemporaryTester.java
@@ -27,9 +27,14 @@
// FIXME This class is for manual integration login test. Remove it in the final product.
public class GoogleLoginTemporaryTester {
- public boolean testLogin(Credential credential) throws IOException {
- File credentialFile = getCredentialFile(credential);
- return credentialFile != null && testCredentialWithGcloud(credentialFile);
+ public boolean testLogin(Credential credential) {
+ try {
+ File credentialFile = getCredentialFile(credential);
+ return credentialFile != null && testCredentialWithGcloud(credentialFile);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
}
private File getCredentialFile(Credential credential) throws IOException {
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/IGoogleLoginService.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/IGoogleLoginService.java
new file mode 100644
index 0000000000..5cac62dc1c
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/IGoogleLoginService.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
+package com.google.cloud.tools.eclipse.appengine.login;
+
+import com.google.api.client.auth.oauth2.Credential;
+
+public interface IGoogleLoginService {
+
+ /**
+ * Returns the credential of an active user (among multiple logged-in users). A login screen
+ * may be presented, e.g., if no user is logged in or login is required due to an expired
+ * credential. This method returns {@code null} if a user cancels the login process.
+ * For this reason, if {@code null} is returned, the caller should cancel the current
+ * operation and display a general message that login is required but was cancelled or failed.
+ *
+ * Must be called from a UI context.
+ */
+ public Credential getActiveCredential();
+
+ /**
+ * Returns the credential of an active user (among multiple logged-in users). Unlike {@link
+ * #getActiveCredential}, this version does not involve login process or make API calls.
+ * Returns {@code null} if no credential has been cached.
+ *
+ * Safe to call from non-UI contexts.
+ */
+ public Credential getCachedActiveCredential();
+
+ /**
+ * Clears all credentials. ("logging out" from user perspective.)
+ *
+ * Safe to call from non-UI contexts.
+ */
+ public void clearCredential();
+}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/Messages.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/Messages.java
index 62ae35bf4f..f8230b62f8 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/Messages.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/Messages.java
@@ -4,7 +4,10 @@
public class Messages extends NLS {
private static final String BUNDLE_NAME = "com.google.cloud.tools.eclipse.appengine.login.messages"; //$NON-NLS-1$
- public static String LOGIN_BROWSER_TITLE;
+ public static String LOGIN_ERROR_CANNOT_OPEN_BROWSER;
+ public static String LOGIN_ERROR_DIALOG_MESSAGE;
+ public static String LOGIN_ERROR_DIALOG_TITLE;
+ public static String LOGIN_ERROR_IN_PROGRESS;
public static String LOGIN_MENU_LOGGED_IN;
public static String LOGIN_MENU_LOGGED_OUT;
public static String LOGIN_TOOLTIP_LOGGED_IN;
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/TransientOAuthDataStore.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/TransientOAuthDataStore.java
new file mode 100644
index 0000000000..d7a4117f23
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/TransientOAuthDataStore.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
+package com.google.cloud.tools.eclipse.appengine.login;
+
+import com.google.cloud.tools.ide.login.OAuthData;
+import com.google.cloud.tools.ide.login.OAuthDataStore;
+
+import org.eclipse.e4.core.contexts.IEclipseContext;
+
+/**
+ * Provides a transient store for saving and loading {@link OAuthData} (a user credential).
+ */
+public class TransientOAuthDataStore implements OAuthDataStore {
+
+ private static final String STASH_OAUTH_CRED_KEY = "OAUTH_CRED";
+
+ private IEclipseContext eclipseContext;
+
+ public TransientOAuthDataStore(IEclipseContext eclipseContext) {
+ this.eclipseContext = eclipseContext;
+ }
+
+ @Override
+ public void clearStoredOAuthData() {
+ eclipseContext.remove(STASH_OAUTH_CRED_KEY);
+ }
+
+ @Override
+ public OAuthData loadOAuthData() {
+ OAuthData credential = (OAuthData) eclipseContext.get(STASH_OAUTH_CRED_KEY);
+ if (credential == null) {
+ return new OAuthData(null, null, null, null, 0); // null credential
+ }
+ return credential;
+ }
+
+ @Override
+ public void saveOAuthData(OAuthData credential) {
+ eclipseContext.set(STASH_OAUTH_CRED_KEY, credential);
+ }
+}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/messages.properties b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/messages.properties
index e40c8a4c65..c2375b8f17 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/messages.properties
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/messages.properties
@@ -1,7 +1,10 @@
-LOGIN_BROWSER_TITLE=Sign in to Google
+LOGIN_ERROR_CANNOT_OPEN_BROWSER=Error opening a system browser. Signing in requires an open browser.
+LOGIN_ERROR_DIALOG_MESSAGE=An error occurred while trying to sign in.
+LOGIN_ERROR_DIALOG_TITLE=Sign in Failed
+LOGIN_ERROR_IN_PROGRESS=Login is already in progress. Please try again later.
LOGIN_MENU_LOGGED_IN=Sign out from Google
LOGIN_MENU_LOGGED_OUT=Sign in to Google...
LOGIN_TOOLTIP_LOGGED_IN=Sign out from Google
LOGIN_TOOLTIP_LOGGED_OUT=Authorize Eclipse to operate on Google Cloud Platform resources
LOGOUT_CONFIRM_DIALOG_MESSAGE=Do you want to sign out from Google?
-LOGOUT_CONFIRM_DIALOG_TITILE=Signed out from Google
+LOGOUT_CONFIRM_DIALOG_TITILE=Sign out from Google
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/AuthorizationCodeListener.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/AuthorizationCodeListener.java
deleted file mode 100644
index 346d096468..0000000000
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/AuthorizationCodeListener.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*******************************************************************************
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * All rights reserved. This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License v1.0 which
- * accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- *******************************************************************************/
-
-package com.google.cloud.tools.eclipse.appengine.login.ui;
-
-import org.eclipse.swt.browser.TitleEvent;
-import org.eclipse.swt.browser.TitleListener;
-
-/**
- * Listens for title changes of HTML pages for {@link GoogleLoginBrowser} to capture an authorization
- * code. (The authorization code after successful Google login will be given in the HTML title.)
- */
-class AuthorizationCodeListener implements TitleListener {
-
- private static final String SUCCESS_CODE_PREFIX = "Success code="; //$NON-NLS-1$
-
- private GoogleLoginBrowser loginBrowser;
-
- AuthorizationCodeListener(GoogleLoginBrowser loginBrowser) {
- this.loginBrowser = loginBrowser;
- }
-
- @Override
- public void changed(TitleEvent event) {
- if (event.title != null && event.title.startsWith(SUCCESS_CODE_PREFIX)) {
- loginBrowser.setAuthorizationCode(event.title.substring(SUCCESS_CODE_PREFIX.length()));
- // We don't close the browser now; rather we make the browser log out the user first.
- loginBrowser.logOutAndClose();
- }
- }
-}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/GoogleLoginBrowser.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/GoogleLoginBrowser.java
deleted file mode 100644
index e7daa876ad..0000000000
--- a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/GoogleLoginBrowser.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*******************************************************************************
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * All rights reserved. This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License v1.0 which
- * accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- *******************************************************************************/
-
-package com.google.cloud.tools.eclipse.appengine.login.ui;
-
-import org.eclipse.jface.dialogs.Dialog;
-import org.eclipse.jface.dialogs.IDialogConstants;
-import org.eclipse.jface.layout.GridDataFactory;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.browser.ProgressAdapter;
-import org.eclipse.swt.browser.ProgressEvent;
-import org.eclipse.swt.browser.TitleEvent;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Shell;
-
-import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
-import com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants;
-import com.google.cloud.tools.eclipse.appengine.login.Messages;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.net.URL;
-import java.util.Collection;
-
-/**
- * A login dialog that is specific for Google login. The dialog embeds a full-featured browser,
- * which is used to connect to a Google login URL. Successful browser login will auto-close
- * the dialog (after explicitly logging out the user from the browser), and the authorization
- * code can be retrieved by {@link #getAuthorizationCode()} thereafter.
- *
- * Implementation detail worth noting: successful browser login will return an authorization code
- * as the title of an HTML page (i.e., the redirect URL after login is basically set to
- * {@link GoogleOAuthConstants.OOB_REDIRECT_URI}, which is {@code "urn:ietf:wg:oauth:2.0:oob"}).
- */
-public class GoogleLoginBrowser extends Dialog {
-
- public static final String REDIRECT_URI = GoogleOAuthConstants.OOB_REDIRECT_URI + ":auto";
-
- private static final String LOGOUT_URL = "https://www.google.com/accounts/Logout"; //$NON-NLS-1$
-
- private Browser browser;
- private URL loginUrl;
-
- private String authorizationCode;
-
- public GoogleLoginBrowser(Shell parentShell,
- String OAuthClientId, Collection OAuthScopes) {
- super(parentShell);
- loginUrl = new GoogleAuthorizationCodeRequestUrl(
- OAuthClientId, REDIRECT_URI, OAuthScopes).toURL();
- }
-
- /**
- * @return authorization code or {@code null} if login is not completed for whatever reason.
- */
- public String getAuthorizationCode() {
- return authorizationCode;
- }
-
- @Override
- protected void configureShell(Shell newShell) {
- super.configureShell(newShell);
- newShell.setText(Messages.LOGIN_BROWSER_TITLE);
- }
-
- @Override
- protected Control createDialogArea(Composite parent) {
- Composite composite = (Composite) super.createDialogArea(parent);
-
- browser = new Browser(composite, SWT.BORDER);
- browser.setUrl(loginUrl.toString());
- browser.addProgressListener(new PageLoadingListener());
- browser.addTitleListener(new AuthorizationCodeListener(this));
- GridDataFactory.fillDefaults().grab(true, true).hint(1060, 660).applyTo(browser);
-
- return composite;
- }
-
- @Override
- protected void createButtonsForButtonBar(Composite parent) {
- createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
- }
-
- @Override
- protected boolean isResizable() {
- return true;
- }
-
- /**
- * To close the login browser after verifying that the browser is in the logged-out state.
- * Logging out (inside the browser) is triggered via {@link #LOGOUT_URL} right after the
- * retrieval of an authorization code from successful browser login. (See {@link
- * AuthorizationCodeListener#changed(TitleEvent)}.) (If we don't make the browser log out
- * the user, the next time the login browser is launched, the browser may already be in
- * the logged-in state.)
- */
- private class PageLoadingListener extends ProgressAdapter {
- @Override
- public void completed(ProgressEvent event) {
- if (authorizationCode != null) {
- close();
- }
- }
- }
-
- @VisibleForTesting
- protected void setAuthorizationCode(String authorizationCode) {
- this.authorizationCode = authorizationCode;
- }
-
- @VisibleForTesting
- protected void logOutAndClose() {
- browser.setVisible(false);
- browser.setUrl(LOGOUT_URL);
- }
-}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/LoginServiceUi.java b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/LoginServiceUi.java
new file mode 100644
index 0000000000..40f94e5d3f
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.login/src/com/google/cloud/tools/eclipse/appengine/login/ui/LoginServiceUi.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
+package com.google.cloud.tools.eclipse.appengine.login.ui;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
+import com.google.cloud.tools.eclipse.appengine.login.Messages;
+import com.google.cloud.tools.ide.login.UiFacade;
+import com.google.cloud.tools.ide.login.VerificationCodeHolder;
+
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.window.IShellProvider;
+import org.eclipse.swt.program.Program;
+import org.eclipse.ui.commands.ICommandService;
+import org.eclipse.ui.services.IServiceLocator;
+
+public class LoginServiceUi implements UiFacade {
+
+ private IServiceLocator serviceLocator;
+ private IShellProvider shellProvider;
+
+ public LoginServiceUi(IServiceLocator serviceLocator, IShellProvider shellProvider) {
+ this.serviceLocator = serviceLocator;
+ this.shellProvider = shellProvider;
+ }
+
+ public void showErrorDialogHelper(String title, String message) {
+ MessageDialog.openError(shellProvider.getShell(), title, message);
+ }
+
+ @Override
+ public boolean askYesOrNo(String title, String message) {
+ throw new RuntimeException("Not allowed to ensure non-UI threads don't prompt."); //$NON-NLS-1$
+ }
+
+ @Override
+ public void showErrorDialog(String title, String message) {
+ // Ignore "title" and "message", as they are non-localized hard-coded strings in the library.
+ showErrorDialogHelper(Messages.LOGIN_ERROR_DIALOG_TITLE, Messages.LOGIN_ERROR_DIALOG_MESSAGE);
+ }
+
+ @Override
+ public void notifyStatusIndicator() {
+ // Update and refresh the menu, toolbar button, and tooltip.
+ shellProvider.getShell().getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ serviceLocator.getService(ICommandService.class).refreshElements(
+ "com.google.cloud.tools.eclipse.appengine.login.commands.loginCommand", //$NON-NLS-1$
+ null);
+ }
+ });
+ }
+
+ @Override
+ public VerificationCodeHolder obtainVerificationCodeFromExternalUserInteraction(String title) {
+ throw new RuntimeException("Not to be called."); //$NON-NLS-1$
+ }
+
+ @Override
+ public String obtainVerificationCodeFromUserInteraction(
+ String title, GoogleAuthorizationCodeRequestUrl authCodeRequestUrl) {
+ if (!Program.launch(authCodeRequestUrl.toString())) {
+ MessageDialog.openError(shellProvider.getShell(),
+ Messages.LOGIN_ERROR_DIALOG_TITLE, Messages.LOGIN_ERROR_CANNOT_OPEN_BROWSER);
+ return null;
+ }
+
+ // TODO(chanseok): remove InputDialog and set up a web server to receive authorization code.
+ InputDialog dialog = new InputDialog(null,
+ "Enter Authorization Code", "Enter authorization code from the browser.", null, null);
+ dialog.open();
+ return dialog.getValue();
+ }
+}
diff --git a/plugins/com.google.cloud.tools.eclipse.util/META-INF/MANIFEST.MF b/plugins/com.google.cloud.tools.eclipse.util/META-INF/MANIFEST.MF
index 5c53a4594c..a9aaedd157 100644
--- a/plugins/com.google.cloud.tools.eclipse.util/META-INF/MANIFEST.MF
+++ b/plugins/com.google.cloud.tools.eclipse.util/META-INF/MANIFEST.MF
@@ -10,8 +10,13 @@ Bundle-ActivationPolicy: lazy
Export-Package: com.google.cloud.tools.eclipse.util,
com.google.cloud.tools.eclipse.util.io,
com.google.cloud.tools.eclipse.util.status
-Require-Bundle: org.eclipse.core.runtime
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui
Import-Package: com.google.common.base;version="15.0.0",
+ org.eclipse.core.commands,
org.eclipse.core.resources,
+ org.eclipse.jface.window,
org.eclipse.ui.console,
+ org.eclipse.ui.handlers,
+ org.eclipse.ui.services,
org.eclipse.wst.common.project.facet.core
diff --git a/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/ServiceUtils.java b/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/ServiceUtils.java
new file mode 100644
index 0000000000..324033dd98
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.util/src/com/google/cloud/tools/eclipse/util/ServiceUtils.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *******************************************************************************/
+
+package com.google.cloud.tools.eclipse.util;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IWorkbenchPartSite;
+import org.eclipse.ui.IWorkbenchSite;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Utility class for using OSGi services, such as locating OSGi services from a desired context.
+ */
+public class ServiceUtils {
+
+ /**
+ * Returns an OSGi service from {@link ExecutionEvent}. It looks up a service in the following
+ * locations (if exist) in the given order:
+ *
+ * {@code HandlerUtil.getActiveSite(event)}
+ * {@code HandlerUtil.getActiveEditor(event).getEditorSite()}
+ * {@code HandlerUtil.getActiveEditor(event).getSite()}
+ * {@code HandlerUtil.getActiveWorkbenchWindow(event)}
+ * {@code PlatformUI.getWorkbench()}
+ */
+ public static T getService(ExecutionEvent event, Class api) {
+ IWorkbenchSite activeSite = HandlerUtil.getActiveSite(event);
+ if (activeSite != null) {
+ return activeSite.getService(api);
+ }
+
+ IEditorPart activeEditor = HandlerUtil.getActiveEditor(event);
+ if (activeEditor != null) {
+ IEditorSite editorSite = activeEditor.getEditorSite();
+ if (editorSite != null) {
+ return editorSite.getService(api);
+ }
+ IWorkbenchPartSite site = activeEditor.getSite();
+ if (site != null) {
+ return site.getService(api);
+ }
+ }
+
+ IWorkbenchWindow workbenchWindow = HandlerUtil.getActiveWorkbenchWindow(event);
+ if (workbenchWindow != null) {
+ return workbenchWindow.getService(api);
+ }
+
+ return PlatformUI.getWorkbench().getService(api);
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 4abc76c4fc..a2451bc197 100644
--- a/pom.xml
+++ b/pom.xml
@@ -95,6 +95,12 @@
guava
17.0
+
+
+ org.codehaus.jackson
+ jackson-core-asl
+ 1.9.13
+