blob: ca4fce9d910b9dd510780da9b2e32a7cca78816c [file] [log] [blame]
/*
* Copyright 2000-2013 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.commandLine;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.WaitForProgressToShow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.svn.dialogs.ServerSSHDialog;
import org.jetbrains.idea.svn.dialogs.SimpleCredentialsDialog;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Konstantin Kolosovsky.
*/
public class TerminalSshModule extends LineCommandAdapter implements CommandRuntimeModule, InteractiveCommandListener {
private static final Logger LOG = Logger.getInstance(TerminalSshModule.class);
private static final Pattern PASSPHRASE_PROMPT = Pattern.compile("Enter passphrase for key \\'(.*)\\':\\s?");
private static final Pattern PASSWORD_PROMPT = Pattern.compile("(.*)\\'s password:\\s?");
private static final Pattern UNKNOWN_HOST_MESSAGE =
Pattern.compile("The authenticity of host \\'((.*) \\((.*)\\))\\' can\\'t be established\\.\\s?");
private static final Pattern HOST_FINGERPRINT_MESSAGE = Pattern.compile("(\\w+) key fingerprint is (.*)\\.\\s?");
private static final Pattern ACCEPT_HOST_PROMPT = Pattern.compile("Are you sure you want to continue connecting \\(yes/no\\)\\?\\s?");
@NotNull private final CommandRuntime myRuntime;
@NotNull private final CommandExecutor myExecutor;
private String unknownHost;
private String fingerprintAlgorithm;
private String hostFingerprint;
// TODO: Do not accept executor here and make it as command runtime module
public TerminalSshModule(@NotNull CommandRuntime runtime, @NotNull CommandExecutor executor) {
myExecutor = executor;
myRuntime = runtime;
}
@Override
public void onStart(@NotNull Command command) throws SvnBindException {
}
@Override
public boolean handlePrompt(String line, Key outputType) {
return checkPassphrase(line) || checkPassword(line) || checkUnknownHost(line);
}
private boolean checkPassphrase(@NotNull String line) {
Matcher matcher = PASSPHRASE_PROMPT.matcher(line);
return matcher.matches() && handleAuthPrompt(SimpleCredentialsDialog.Mode.SSH_PASSPHRASE, matcher.group(1));
}
private boolean checkPassword(@NotNull String line) {
Matcher matcher = PASSWORD_PROMPT.matcher(line);
return matcher.matches() && handleAuthPrompt(SimpleCredentialsDialog.Mode.SSH_PASSWORD, matcher.group(1));
}
private boolean checkUnknownHost(@NotNull String line) {
Matcher unknownHostMatcher = UNKNOWN_HOST_MESSAGE.matcher(line);
Matcher hostFingerPrintMatcher = HOST_FINGERPRINT_MESSAGE.matcher(line);
Matcher acceptHostMatcher = ACCEPT_HOST_PROMPT.matcher(line);
if (unknownHostMatcher.matches()) {
unknownHost = unknownHostMatcher.group(1);
}
else if (hostFingerPrintMatcher.matches()) {
fingerprintAlgorithm = hostFingerPrintMatcher.group(1);
hostFingerprint = hostFingerPrintMatcher.group(2);
}
else if (acceptHostMatcher.matches()) {
handleUnknownHost();
}
return unknownHostMatcher.matches() || hostFingerPrintMatcher.matches() || acceptHostMatcher.matches();
}
private void handleUnknownHost() {
final Project project = myRuntime.getVcs().getProject();
final Ref<Integer> answer = new Ref<Integer>();
Runnable command = new Runnable() {
@Override
public void run() {
final ServerSSHDialog dialog = new ServerSSHDialog(project, true, unknownHost, fingerprintAlgorithm, hostFingerprint);
dialog.show();
answer.set(dialog.getResult());
}
};
// Use ModalityState.any() as currently ssh credentials in terminal mode are requested in the thread that reads output and not in
// the thread that started progress
WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command, ModalityState.any());
unknownHost = null;
fingerprintAlgorithm = null;
hostFingerprint = null;
sendAnswer(answer.get() == ISVNAuthenticationProvider.REJECTED ? "no" : "yes");
}
private boolean handleAuthPrompt(@NotNull final SimpleCredentialsDialog.Mode mode, @NotNull final String key) {
final SVNURL repositoryUrl = myExecutor.getCommand().getRepositoryUrl();
// TODO: repositoryUrl could be null for some cases, for instance for info command for file is invoked that requires
// TODO: authentication (like "svn info <file> -r HEAD"), if it is invoked before all working copy roots are resolved.
// TODO: resolving repositoryUrl logic should be updated so that repositoryUrl is not null here.
String auth =
myRuntime.getAuthenticationService().requestSshCredentials(repositoryUrl != null ? repositoryUrl.toDecodedString() : "", mode, key);
if (!StringUtil.isEmpty(auth)) {
sendAnswer(auth);
} else {
myExecutor.destroyProcess("Authentication canceled for repository: " + repositoryUrl);
}
return !StringUtil.isEmpty(auth);
}
private boolean sendAnswer(@NotNull String answer) {
try {
myExecutor.write(answer + "\n");
return true;
}
catch (SvnBindException e) {
// TODO: handle this more carefully
LOG.info(e);
}
return false;
}
}