From c994c9de727e516eabbea013aa7ff9196e1f66ee Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 22 Sep 2014 20:24:02 +0400 Subject: [PATCH] IDEA-119102: Support oauth2 for uploading App Engine app --- .../appengine/actions/AppEngineUploader.java | 49 +++---- .../appengine/cloud/AppEngineAuthData.java | 55 ++++++++ .../cloud/AppEngineCloudConfigurable.form | 88 ++++++++++++ .../cloud/AppEngineCloudConfigurable.java | 133 ++++++++++++++++++ .../appengine/cloud/AppEngineCloudType.java | 44 +----- .../cloud/AppEngineServerConfiguration.java | 10 ++ .../facet/AppEngineAccountDialog.form | 63 --------- .../facet/AppEngineAccountDialog.java | 99 ++++++------- 8 files changed, 350 insertions(+), 191 deletions(-) create mode 100644 plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineAuthData.java create mode 100644 plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.form create mode 100644 plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.java delete mode 100644 plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.form diff --git a/plugins/google-app-engine/source/com/intellij/appengine/actions/AppEngineUploader.java b/plugins/google-app-engine/source/com/intellij/appengine/actions/AppEngineUploader.java index ea24e05f88d0..5c2a9893120b 100644 --- a/plugins/google-app-engine/source/com/intellij/appengine/actions/AppEngineUploader.java +++ b/plugins/google-app-engine/source/com/intellij/appengine/actions/AppEngineUploader.java @@ -16,6 +16,7 @@ package com.intellij.appengine.actions; import com.intellij.CommonBundle; +import com.intellij.appengine.cloud.AppEngineAuthData; import com.intellij.appengine.cloud.AppEngineServerConfiguration; import com.intellij.appengine.descriptor.dom.AppEngineWebApp; import com.intellij.appengine.facet.AppEngineAccountDialog; @@ -37,7 +38,6 @@ import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.execution.ui.RunnerLayoutUi; import com.intellij.execution.ui.actions.CloseAction; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.DefaultActionGroup; @@ -91,19 +91,17 @@ public class AppEngineUploader { private final Artifact myArtifact; private final AppEngineFacet myAppEngineFacet; private final AppEngineSdk mySdk; - private final String myEmail; - private final String myPassword; + private final AppEngineAuthData myAuthData; private final ServerRuntimeInstance.DeploymentOperationCallback myCallback; private final LoggingHandler myLoggingHandler; - private AppEngineUploader(Project project, Artifact artifact, AppEngineFacet appEngineFacet, AppEngineSdk sdk, String email, - String password, ServerRuntimeInstance.DeploymentOperationCallback callback, @Nullable LoggingHandler loggingHandler) { + private AppEngineUploader(Project project, Artifact artifact, AppEngineFacet appEngineFacet, AppEngineSdk sdk, AppEngineAuthData authData, + ServerRuntimeInstance.DeploymentOperationCallback callback, @Nullable LoggingHandler loggingHandler) { myProject = project; myArtifact = artifact; myAppEngineFacet = appEngineFacet; mySdk = sdk; - myEmail = email; - myPassword = password; + myAuthData = authData; myCallback = callback; myLoggingHandler = loggingHandler; } @@ -154,26 +152,10 @@ public class AppEngineUploader { } } - String password = null; - String email = null; - try { - email = AppEngineAccountDialog.getStoredEmail(configuration, project); - password = AppEngineAccountDialog.getStoredPassword(project, email); - } - catch (PasswordSafeException e) { - LOG.info("Cannot load stored password: " + e.getMessage()); - LOG.info(e); - } - if (StringUtil.isEmpty(email) || StringUtil.isEmpty(password)) { - final AppEngineAccountDialog dialog = new AppEngineAccountDialog(project, configuration); - dialog.show(); - if (!dialog.isOK()) return null; + AppEngineAuthData authData = AppEngineAccountDialog.createAuthData(project, configuration); + if (authData == null) return null; - email = dialog.getEmail(); - password = dialog.getPassword(); - } - - return new AppEngineUploader(project, artifact, appEngineFacet, sdk, email, password, callback, loggingHandler); + return new AppEngineUploader(project, artifact, appEngineFacet, sdk, authData, callback, loggingHandler); } public void startUploading() { @@ -231,7 +213,14 @@ public class AppEngineUploader { } final ParametersList programParameters = parameters.getProgramParametersList(); - programParameters.add("--email=" + myEmail); + if (myAuthData.isOAuth2()) { + programParameters.add("--oauth2"); + } + else { + programParameters.add("--email=" + myAuthData.getEmail()); + programParameters.add("--passin"); + } + programParameters.add("--no_cookies"); programParameters.add("update"); programParameters.add(FileUtil.toSystemDependentName(myArtifact.getOutputPath())); @@ -281,15 +270,15 @@ public class AppEngineUploader { @Override public void onTextAvailable(ProcessEvent event, Key outputType) { - if (!myPasswordEntered && !outputType.equals(ProcessOutputTypes.SYSTEM) && event.getText().contains(myEmail)) { + if (!myAuthData.isOAuth2() && !myPasswordEntered && !outputType.equals(ProcessOutputTypes.SYSTEM) && event.getText().contains(myAuthData.getEmail())) { myPasswordEntered = true; final OutputStream processInput = myProcessHandler.getProcessInput(); if (processInput != null) { //noinspection IOResourceOpenedButNotSafelyClosed final PrintWriter input = new PrintWriter(processInput); - input.println(myPassword); + input.println(myAuthData.getPassword()); input.flush(); - String message = StringUtil.repeatSymbol('*', myPassword.length()) + "\n"; + String message = StringUtil.repeatSymbol('*', myAuthData.getPassword().length()) + "\n"; if (myConsole != null) { myConsole.print(message, ConsoleViewContentType.USER_INPUT); } diff --git a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineAuthData.java b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineAuthData.java new file mode 100644 index 000000000000..7210049b42fa --- /dev/null +++ b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineAuthData.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.intellij.appengine.cloud; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +public class AppEngineAuthData { + private final boolean myOAuth2; + private final String myEmail; + private final String myPassword; + + @NotNull + public static AppEngineAuthData oauth2() { + return new AppEngineAuthData(true, null, null); + } + + @NotNull + public static AppEngineAuthData login(@NotNull String email, @NotNull String password) { + return new AppEngineAuthData(false, email, password); + } + + private AppEngineAuthData(boolean OAuth2, String email, String password) { + myOAuth2 = OAuth2; + myEmail = email; + myPassword = password; + } + + public boolean isOAuth2() { + return myOAuth2; + } + + public String getEmail() { + return myEmail; + } + + public String getPassword() { + return myPassword; + } +} diff --git a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.form b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.form new file mode 100644 index 000000000000..e26b9e6f10f0 --- /dev/null +++ b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.form @@ -0,0 +1,88 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.java b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.java new file mode 100644 index 000000000000..6d497e0c001b --- /dev/null +++ b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudConfigurable.java @@ -0,0 +1,133 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.intellij.appengine.cloud; + +import com.intellij.appengine.facet.AppEngineAccountDialog; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBRadioButton; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** +* @author nik +*/ +public class AppEngineCloudConfigurable implements Configurable { + private final AppEngineServerConfiguration myConfiguration; + @Nullable private final Project myProject; + private JTextField myEmailField; + private JPasswordField myPasswordField; + private JBRadioButton myPasswordLoginButton; + private JBRadioButton myOAuthLoginButton; + private JPanel myMainPanel; + private JCheckBox myRememberPasswordCheckBox; + + public AppEngineCloudConfigurable(@NotNull AppEngineServerConfiguration configuration, @Nullable Project project) { + myConfiguration = configuration; + myProject = project; + ActionListener actionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + updateControls(); + } + }; + myPasswordLoginButton.addActionListener(actionListener); + myOAuthLoginButton.addActionListener(actionListener); + myEmailField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + updateControls(); + } + }); + updateControls(); + } + + private void updateControls() { + boolean passwordLogin = myPasswordLoginButton.isSelected(); + myEmailField.setEnabled(passwordLogin); + myPasswordField.setEnabled(passwordLogin); + myRememberPasswordCheckBox.setEnabled(passwordLogin); + } + + public String getEmail() { + return StringUtil.nullize(myEmailField.getText(), true); + } + + @Nls + @Override + public String getDisplayName() { + return "Google App Engine Account"; + } + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + + @Nullable + @Override + public JComponent createComponent() { + return myMainPanel; + } + + public void reset() { + String email = myConfiguration.getEmail(); + myEmailField.setText(StringUtil.notNullize(email)); + if (myConfiguration.isOAuth2()) { + myOAuthLoginButton.setSelected(true); + } + else { + myPasswordLoginButton.setSelected(true); + } + updateControls(); + } + + public void apply() { + String email = getEmail(); + myConfiguration.setEmail(email); + myConfiguration.setOAuth2(isOAuth2()); + String password = getPassword(); + if (myRememberPasswordCheckBox.isSelected() && !StringUtil.isEmpty(email) && !password.isEmpty()) { + AppEngineAccountDialog.storePassword(email, password, myProject); + } + } + + public boolean isOAuth2() { + return myOAuthLoginButton.isSelected(); + } + + public String getPassword() { + return new String(myPasswordField.getPassword()); + } + + public boolean isModified() { + return !Comparing.strEqual(getEmail(), myConfiguration.getEmail()) || myConfiguration.isOAuth2() != isOAuth2(); + } + + @Override + public void disposeUIResources() { + } +} diff --git a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudType.java b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudType.java index 1c466d88284a..58c1b1559bc6 100644 --- a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudType.java +++ b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineCloudType.java @@ -17,7 +17,6 @@ package com.intellij.appengine.cloud; import com.intellij.appengine.actions.AppEngineUploader; import com.intellij.appengine.util.AppEngineUtil; -import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.options.UnnamedConfigurable; import com.intellij.openapi.project.Project; @@ -30,13 +29,10 @@ import com.intellij.remoteServer.runtime.ServerTaskExecutor; import com.intellij.remoteServer.runtime.deployment.DeploymentLogManager; import com.intellij.remoteServer.runtime.deployment.DeploymentTask; import com.intellij.remoteServer.runtime.deployment.ServerRuntimeInstance; -import com.intellij.util.ui.FormBuilder; import icons.GoogleAppEngineIcons; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; import java.util.List; /** @@ -69,7 +65,7 @@ public class AppEngineCloudType extends ServerType @NotNull @Override public UnnamedConfigurable createConfigurable(@NotNull AppEngineServerConfiguration configuration) { - return new AppEngineCloudConfigurable(configuration); + return new AppEngineCloudConfigurable(configuration, null); } @NotNull @@ -81,46 +77,10 @@ public class AppEngineCloudType extends ServerType @NotNull @Override - public DeploymentConfigurator createDeploymentConfigurator(Project project) { + public AppEngineDeploymentConfigurator createDeploymentConfigurator(Project project) { return new AppEngineDeploymentConfigurator(project); } - private static class AppEngineCloudConfigurable implements UnnamedConfigurable { - private final JTextField myEmailField; - private final AppEngineServerConfiguration myConfiguration; - - public AppEngineCloudConfigurable(AppEngineServerConfiguration configuration) { - myConfiguration = configuration; - myEmailField = new JTextField(); - myEmailField.setPreferredSize(new Dimension(250, myEmailField.getPreferredSize().height)); - } - - @Nullable - @Override - public JComponent createComponent() { - return FormBuilder.createFormBuilder().addLabeledComponent("E-mail:", myEmailField).getPanel(); - } - - @Override - public boolean isModified() { - return !myEmailField.getText().equals(myConfiguration.getEmail()); - } - - @Override - public void apply() throws ConfigurationException { - myConfiguration.setEmail(myEmailField.getText()); - } - - @Override - public void reset() { - myEmailField.setText(myConfiguration.getEmail()); - } - - @Override - public void disposeUIResources() { - } - } - private static class AppEngineDeploymentConfigurator extends DeploymentConfigurator { private final Project myProject; diff --git a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineServerConfiguration.java b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineServerConfiguration.java index 4576fb41bdde..0acea6888c3d 100644 --- a/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineServerConfiguration.java +++ b/plugins/google-app-engine/source/com/intellij/appengine/cloud/AppEngineServerConfiguration.java @@ -23,13 +23,23 @@ import com.intellij.util.xmlb.annotations.Attribute; */ public class AppEngineServerConfiguration extends ServerConfigurationBase { private String myEmail; + private boolean myOAuth2; @Attribute("email") public String getEmail() { return myEmail; } + @Attribute("oauth2") + public boolean isOAuth2() { + return myOAuth2; + } + public void setEmail(String email) { myEmail = email; } + + public void setOAuth2(boolean OAuth2) { + myOAuth2 = OAuth2; + } } diff --git a/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.form b/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.form deleted file mode 100644 index f6f2f66f607f..000000000000 --- a/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.form +++ /dev/null @@ -1,63 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.java b/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.java index 3e4cda649855..7b344b09b170 100644 --- a/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.java +++ b/plugins/google-app-engine/source/com/intellij/appengine/facet/AppEngineAccountDialog.java @@ -15,87 +15,68 @@ */ package com.intellij.appengine.facet; -import com.intellij.CommonBundle; +import com.intellij.appengine.cloud.AppEngineAuthData; +import com.intellij.appengine.cloud.AppEngineCloudConfigurable; import com.intellij.appengine.cloud.AppEngineServerConfiguration; import com.intellij.ide.passwordSafe.PasswordSafe; import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; - /** * @author nik */ -public class AppEngineAccountDialog extends DialogWrapper { - public static final String PASSWORD_KEY = "GOOGLE_APP_ENGINE_PASSWORD"; +public class AppEngineAccountDialog { + private static final Logger LOG = Logger.getInstance(AppEngineAccountDialog.class); + private static final String PASSWORD_KEY = "GOOGLE_APP_ENGINE_PASSWORD"; private static final String EMAIL_KEY = "GOOGLE_APP_ENGINE_ACCOUNT_EMAIL"; - private JPanel myMainPanel; - private JCheckBox myRememberPasswordCheckBox; - private JPasswordField myPasswordField; - private JTextField myUserEmailField; - private final Project myProject; - @Nullable private final AppEngineServerConfiguration myConfiguration; - public AppEngineAccountDialog(@NotNull Project project, @Nullable AppEngineServerConfiguration configuration) { - super(project); - myProject = project; - myConfiguration = configuration; - setTitle("AppEngine Account"); - myUserEmailField.setText(StringUtil.notNullize(getStoredEmail(myConfiguration, project))); - init(); - } + @Nullable + public static AppEngineAuthData createAuthData(@NotNull Project project, @Nullable AppEngineServerConfiguration stored) { + AppEngineServerConfiguration configuration = stored != null ? stored : new AppEngineServerConfiguration(); + if (configuration.isOAuth2()) { + return AppEngineAuthData.oauth2(); + } - public String getEmail() { - return myUserEmailField.getText(); - } + String email = getStoredEmail(stored, project); + if (!StringUtil.isEmpty(email)) { + String password = getStoredPassword(project, email); + if (!StringUtil.isEmpty(password)) { + return AppEngineAuthData.login(email, password); + } + } - public String getPassword() { - return new String(myPasswordField.getPassword()); - } - - @Override - protected JComponent createCenterPanel() { - return myMainPanel; - } - - @Override - public JComponent getPreferredFocusedComponent() { - return myUserEmailField; + AppEngineCloudConfigurable configurable = new AppEngineCloudConfigurable(configuration, project); + boolean ok = ShowSettingsUtil.getInstance().editConfigurable(project, configurable); + if (!ok) { + return null; + } + if (configurable.isOAuth2()) { + return AppEngineAuthData.oauth2(); + } + return AppEngineAuthData.login(configurable.getEmail(), configurable.getPassword()); } @Nullable - public static String getStoredEmail(@Nullable AppEngineServerConfiguration configuration, @NotNull Project project) { + private static String getStoredEmail(@Nullable AppEngineServerConfiguration configuration, @NotNull Project project) { if (configuration != null) { return configuration.getEmail(); } return PropertiesComponent.getInstance(project).getValue(EMAIL_KEY);//todo[nik] remove this } - @Override - protected void doOKAction() { - final String email = getEmail(); - if (myConfiguration != null) { - myConfiguration.setEmail(email); + public static void storePassword(@NotNull String email, @NotNull String password, @Nullable Project project) { + try { + PasswordSafe.getInstance().storePassword(project, AppEngineAccountDialog.class, getPasswordKey(email), password); } - else { - PropertiesComponent.getInstance(myProject).setValue(EMAIL_KEY, email); + catch (PasswordSafeException e) { + LOG.error(e); } - if (myRememberPasswordCheckBox.isSelected()) { - try { - PasswordSafe.getInstance().storePassword(myProject, AppEngineAccountDialog.class, getPasswordKey(email), getPassword()); - } - catch (PasswordSafeException e) { - Messages.showErrorDialog(myProject, "Cannot store password: " + e.getMessage(), CommonBundle.getErrorTitle()); - return; - } - } - super.doOKAction(); } private static String getPasswordKey(String email) { @@ -103,11 +84,17 @@ public class AppEngineAccountDialog extends DialogWrapper { } @Nullable - public static String getStoredPassword(Project project, String email) throws PasswordSafeException { + private static String getStoredPassword(Project project, String email) { if (StringUtil.isEmpty(email)) { return null; } - return PasswordSafe.getInstance().getPassword(project, AppEngineAccountDialog.class, getPasswordKey(email)); + try { + return PasswordSafe.getInstance().getPassword(project, AppEngineAccountDialog.class, getPasswordKey(email)); + } + catch (PasswordSafeException e) { + LOG.error(e); + return null; + } } }