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 +