| /* |
| * Copyright 2000-2012 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 org.jetbrains.idea.svn.dialogs; |
| |
| import com.intellij.ide.util.PropertiesComponent; |
| import com.intellij.openapi.fileChooser.FileChooser; |
| import com.intellij.openapi.fileChooser.FileChooserDescriptor; |
| import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; |
| import com.intellij.openapi.help.HelpManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.TextFieldWithBrowseButton; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFileManager; |
| import com.intellij.ui.components.JBRadioButton; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.SystemProperties; |
| import com.intellij.util.ui.UIUtil; |
| import com.trilead.ssh2.crypto.PEMDecoder; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.idea.svn.SvnBundle; |
| import org.tmatesoft.svn.core.internal.io.svn.SVNSSHPrivateKeyUtil; |
| |
| import javax.swing.*; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.FocusAdapter; |
| import java.awt.event.FocusEvent; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.List; |
| |
| /** |
| * @author alex |
| */ |
| public class SSHCredentialsDialog extends DialogWrapper implements ActionListener, DocumentListener { |
| private boolean myAllowSave; |
| private boolean myIsAgentAllowed; |
| private String myUserName; |
| |
| private String myRealm; |
| private JTextField myUserNameText; |
| private JCheckBox myAllowSaveCheckBox; |
| private JPasswordField myPasswordText; |
| private JPasswordField myPassphraseText; |
| private TextFieldWithBrowseButton myKeyFileText; |
| private JBRadioButton mySshAgentButton; |
| private JRadioButton myPasswordButton; |
| private JRadioButton myKeyButton; |
| private JLabel myPasswordLabel; |
| private JLabel myKeyFileLabel; |
| private JLabel myPortLabel; |
| private JTextField myPortField; |
| private JLabel myPassphraseLabel; |
| private final Project myProject; |
| |
| @NonNls private static final String HELP_ID = "vcs.subversion.authentication"; |
| private boolean myKeyFileEmptyOrCorrect; |
| |
| protected SSHCredentialsDialog(Project project, |
| String realm, |
| String userName, |
| boolean allowSave, |
| final int port, |
| boolean isAgentAllowed) { |
| super(project, true); |
| myProject = project; |
| myRealm = realm; |
| myUserName = userName; |
| myAllowSave = allowSave; |
| myIsAgentAllowed = isAgentAllowed; |
| setResizable(true); |
| getHelpAction().setEnabled(true); |
| init(); |
| myPortField.setText("" + port); |
| myKeyFileEmptyOrCorrect = true; |
| } |
| |
| protected void doHelpAction() { |
| HelpManager.getInstance().invokeHelp(HELP_ID); |
| } |
| |
| @NotNull |
| protected Action[] createActions() { |
| return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; |
| } |
| |
| protected JComponent createCenterPanel() { |
| JPanel panel = new JPanel(); |
| panel.setLayout(new GridBagLayout()); |
| |
| GridBagConstraints gb = new GridBagConstraints(); |
| |
| // top label. |
| gb.insets = new Insets(2, 2, 2, 2); |
| gb.weightx = 1; |
| gb.weighty = 0; |
| gb.gridwidth = 3; |
| gb.gridheight = 1; |
| gb.gridx = 0; |
| gb.gridy = 0; |
| gb.anchor = GridBagConstraints.WEST; |
| gb.fill = GridBagConstraints.HORIZONTAL; |
| |
| JLabel label = new JLabel(SvnBundle.message("label.ssh.authentication.realm", myRealm)); |
| panel.add(label, gb); |
| |
| // user name |
| gb.gridy += 1; |
| gb.gridwidth = 1; |
| gb.weightx = 0; |
| gb.fill = GridBagConstraints.NONE; |
| |
| label = new JLabel(SvnBundle.message("label.ssh.user.name")); |
| panel.add(label, gb); |
| |
| // user name field |
| gb.gridx = 1; |
| gb.gridwidth = 2; |
| gb.weightx = 1; |
| gb.fill = GridBagConstraints.HORIZONTAL; |
| |
| myUserNameText = new JTextField(); |
| panel.add(myUserNameText, gb); |
| label.setLabelFor(myUserNameText); |
| |
| if (myUserName != null) { |
| myUserNameText.setText(myUserName); |
| } |
| myUserNameText.selectAll(); |
| myUserNameText.getDocument().addDocumentListener(this); |
| |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 3; |
| |
| // ssh agent type |
| mySshAgentButton = new JBRadioButton(SvnBundle.message("radio.ssh.authentication.with.agent")); |
| panel.add(mySshAgentButton, gb); |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 3; |
| |
| // password type |
| myPasswordButton = new JRadioButton(SvnBundle.message("radio.ssh.authentication.with.password")); |
| panel.add(myPasswordButton, gb); |
| |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.gridwidth = 1; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 1; |
| |
| myPasswordLabel = new JLabel(SvnBundle.message("label.ssh.password")); |
| panel.add(myPasswordLabel, gb); |
| |
| // passworde field |
| gb.gridx = 1; |
| gb.weightx = 1; |
| gb.gridwidth = 2; |
| gb.fill = GridBagConstraints.HORIZONTAL; |
| |
| myPasswordText = new JPasswordField(); |
| panel.add(myPasswordText, gb); |
| myPasswordLabel.setLabelFor(myPasswordText); |
| |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 3; |
| myKeyButton = new JRadioButton(SvnBundle.message("radio.ssh.authentication.private.key")); |
| panel.add(myKeyButton, gb); |
| |
| // key file. |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.gridwidth = 1; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 1; |
| |
| myKeyFileLabel = new JLabel(SvnBundle.message("label.ssh.key.file")); |
| panel.add(myKeyFileLabel, gb); |
| |
| // key field |
| gb.gridx = 1; |
| gb.weightx = 1; |
| gb.gridwidth = 2; |
| gb.fill = GridBagConstraints.HORIZONTAL; |
| |
| myKeyFileText = new TextFieldWithBrowseButton(this); |
| myKeyFileText.setEditable(false); |
| panel.add(myKeyFileText, gb); |
| myKeyFileLabel.setLabelFor(myKeyFileText); |
| |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.gridwidth = 1; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 1; |
| |
| myPassphraseLabel = new JLabel(SvnBundle.message("label.ssh.passphrase")); |
| panel.add(myPassphraseLabel, gb); |
| |
| // key field |
| gb.gridx = 1; |
| gb.weightx = 1; |
| gb.gridwidth = 2; |
| gb.fill = GridBagConstraints.HORIZONTAL; |
| |
| myPassphraseText = new JPasswordField(30); |
| panel.add(myPassphraseText, gb); |
| myPassphraseText.getDocument().addDocumentListener(this); |
| myPassphraseText.addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusLost(FocusEvent e) { |
| checkKeyFile(); |
| updateOKButton(); |
| } |
| }); |
| |
| myPassphraseLabel.setLabelFor(myPassphraseText); |
| |
| // key file. |
| gb.gridy += 1; |
| gb.weightx = 0; |
| gb.gridx = 0; |
| gb.gridwidth = 1; |
| gb.fill = GridBagConstraints.NONE; |
| gb.gridwidth = 1; |
| |
| myPortLabel = new JLabel(SvnBundle.message("label.ssh.port")); |
| panel.add(myPortLabel, gb); |
| |
| // key field |
| gb.gridx = 1; |
| gb.weightx = 0; |
| gb.gridwidth = 2; |
| gb.fill = GridBagConstraints.NONE; |
| |
| myPortField = new JTextField(); |
| myPortField.setColumns(6); |
| panel.add(myPortField, gb); |
| myPortLabel.setLabelFor(myPortField); |
| myPortField.setText("22"); |
| myPortField.setMinimumSize(myPortField.getPreferredSize()); |
| myPortField.getDocument().addDocumentListener(this); |
| |
| ButtonGroup group = new ButtonGroup(); |
| group.add(mySshAgentButton); |
| group.add(myPasswordButton); |
| group.add(myKeyButton); |
| |
| mySshAgentButton.setEnabled(myIsAgentAllowed); |
| group.setSelected((myIsAgentAllowed ? mySshAgentButton : myPasswordButton).getModel(), true); |
| |
| gb.gridy += 1; |
| gb.gridx = 0; |
| gb.gridwidth = 3; |
| gb.weightx = 1; |
| gb.anchor = GridBagConstraints.WEST; |
| gb.fill = GridBagConstraints.HORIZONTAL; |
| myAllowSaveCheckBox = new JCheckBox(SvnBundle.message("checkbox.ssh.keep.for.current.session")); |
| panel.add(myAllowSaveCheckBox, gb); |
| if (! myAllowSave) { |
| gb.gridy += 1; |
| final JLabel cannotSaveLabel = new JLabel(SvnBundle.message("svn.cannot.save.credentials.store-auth-creds")); |
| cannotSaveLabel.setForeground(UIUtil.getInactiveTextColor()); |
| panel.add(cannotSaveLabel, gb); |
| } |
| gb.gridy += 1; |
| panel.add(new JSeparator(), gb); |
| |
| gb.gridy += 1; |
| gb.weighty = 1; |
| panel.add(new JLabel(), gb); |
| |
| myAllowSaveCheckBox.setSelected(false); |
| myAllowSaveCheckBox.setEnabled(myAllowSave); |
| |
| mySshAgentButton.addActionListener(this); |
| myKeyButton.addActionListener(this); |
| myPasswordButton.addActionListener(this); |
| |
| updateFields(); |
| updateOKButton(); |
| |
| return panel; |
| } |
| |
| public JComponent getPreferredFocusedComponent() { |
| return myUserNameText; |
| } |
| |
| public boolean shouldCloseOnCross() { |
| return true; |
| } |
| |
| protected String getDimensionServiceKey() { |
| return "svn.sshPasswordDialog"; |
| } |
| |
| public boolean isOKActionEnabled() { |
| boolean ok = myUserNameText != null && myUserNameText.getText().trim().length() > 0; |
| if (ok) { |
| if (myPasswordButton.isSelected()) { |
| ok = myPasswordText != null && myPasswordText.getPassword() != null; |
| } |
| else if (myKeyButton.isSelected()) { |
| if (! myKeyFileEmptyOrCorrect) return false; |
| ok = myKeyFileText != null && myKeyFileText.getText().trim().length() > 0; |
| } |
| else { |
| ok = mySshAgentButton.isSelected(); |
| } |
| if (ok) { |
| String portNumber = myPortField.getText(); |
| try { |
| int port = Integer.parseInt(portNumber); |
| ok = port > 0; |
| } catch (NumberFormatException nfe) { |
| ok = false; |
| } |
| } |
| } |
| return ok; |
| } |
| |
| public boolean isSshAgentSelected() { |
| return mySshAgentButton.isSelected(); |
| } |
| |
| public String getUserName() { |
| return isOK() && myUserNameText != null ? myUserNameText.getText() : null; |
| } |
| |
| public String getKeyFile() { |
| if (myKeyFileText.isEnabled()) { |
| return myKeyFileText.getText(); |
| } |
| return null; |
| } |
| |
| public int getPortNumber() { |
| String portNumber = myPortField.getText(); |
| int port = 22; |
| try { |
| port = Integer.parseInt(portNumber); |
| } catch (NumberFormatException nfe) { |
| port = 22; |
| } |
| if (port <= 0) { |
| port = 22; |
| } |
| return port; |
| } |
| |
| public String getPassphrase() { |
| if (myPassphraseText == null || !myPassphraseText.isEnabled()) { |
| return null; |
| } |
| if (isOK()) { |
| char[] pwd = myPassphraseText.getPassword(); |
| if (pwd != null) { |
| return new String(pwd); |
| } |
| } |
| return null; |
| } |
| |
| public String getPassword() { |
| if (myPasswordText != null && !myPasswordText.isEnabled()) { |
| return null; |
| } |
| if (isOK() && myPasswordText != null) { |
| char[] pwd = myPasswordText.getPassword(); |
| if (pwd != null) { |
| return new String(pwd); |
| } |
| } |
| return null; |
| } |
| |
| public boolean isSaveAllowed() { |
| return isOK() && myAllowSave && myAllowSaveCheckBox != null && myAllowSaveCheckBox.isSelected(); |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if (e.getSource() == myPasswordButton || e.getSource() == myKeyButton || e.getSource() == mySshAgentButton) { |
| updateFields(); |
| checkKeyFile(); |
| updateOKButton(); |
| } |
| else { |
| final String[] path = {myKeyFileText.getText()}; |
| VirtualFile file; |
| if (path[0] != null && path[0].trim().length() > 0) { |
| path[0] = "file://" + path[0].replace(File.separatorChar, '/'); |
| file = VirtualFileManager.getInstance().findFileByUrl(path[0]); |
| } |
| else { |
| path[0] = "file://" + SystemProperties.getUserHome() + "/.ssh/identity"; |
| path[0] = path[0].replace(File.separatorChar, '/'); |
| file = VirtualFileManager.getInstance().findFileByUrl(path[0]); |
| if (file == null || !file.exists()) { |
| path[0] = "file://" + SystemProperties.getUserHome() + "/.ssh"; |
| file = VirtualFileManager.getInstance().findFileByUrl(path[0]); |
| } |
| } |
| FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(); |
| |
| descriptor.setShowFileSystemRoots(true); |
| descriptor.setTitle(SvnBundle.message("dialog.title.openssh.v2.private.key")); |
| descriptor.setDescription(SvnBundle.message("dialog.description.openssh.v2.private.key")); |
| descriptor.setHideIgnored(false); |
| |
| final String oldValue = PropertiesComponent.getInstance().getValue("FileChooser.showHiddens"); |
| PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", Boolean.TRUE.toString()); |
| |
| FileChooser.chooseFiles(descriptor, myProject, file, new Consumer<List<VirtualFile>>() { |
| @Override |
| public void consume(List<VirtualFile> files) { |
| PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", oldValue); |
| if (files.size() == 1) { |
| path[0] = FileUtil.toSystemDependentName(files.get(0).getPath()); |
| myKeyFileText.setText(path[0]); |
| } |
| checkKeyFile(); |
| updateOKButton(); |
| } |
| }); |
| } |
| } |
| |
| private void checkKeyFile() { |
| myKeyFileEmptyOrCorrect = true; |
| setErrorText(null); |
| |
| if (! myKeyButton.isSelected()) return; |
| final String text = myKeyFileText.getText(); |
| if (StringUtil.isEmptyOrSpaces(text)) return; |
| final File file = new File(text); |
| if (! file.exists()) { |
| setErrorText("Private key file does not exist"); |
| myKeyFileEmptyOrCorrect = false; |
| return; |
| } |
| final char[] password = myPassphraseText.getPassword(); |
| try { |
| PEMDecoder.decode(SVNSSHPrivateKeyUtil.readPrivateKey(file), String.valueOf(password)); |
| } |
| catch (IOException e) { |
| setErrorText("Private key file is not valid. " + e.getMessage()); |
| myKeyFileEmptyOrCorrect = false; |
| } |
| } |
| |
| private void updateOKButton() { |
| getOKAction().setEnabled(isOKActionEnabled()); |
| } |
| |
| private void updateFields() { |
| myPasswordText.setEnabled(myPasswordButton.isSelected()); |
| myPasswordLabel.setEnabled(myPasswordButton.isSelected()); |
| |
| myKeyFileText.setEnabled(myKeyButton.isSelected()); |
| myKeyFileLabel.setEnabled(myKeyButton.isSelected()); |
| myPassphraseLabel.setEnabled(myKeyButton.isSelected()); |
| myPassphraseText.setEnabled(myKeyButton.isSelected()); |
| } |
| |
| public void insertUpdate(DocumentEvent e) { |
| updateOKButton(); |
| } |
| |
| public void removeUpdate(DocumentEvent e) { |
| updateOKButton(); |
| } |
| |
| public void changedUpdate(DocumentEvent e) { |
| updateOKButton(); |
| } |
| |
| @Override |
| protected void doOKAction() { |
| checkKeyFile(); |
| updateOKButton(); |
| if (! isOKActionEnabled()) return; |
| super.doOKAction(); |
| } |
| } |