blob: 9fb377e1dafeb5f933d9da52e1059b0425bc4c48 [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.auth;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.util.PopupUtil;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.WaitForProgressToShow;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.proxy.CommonProxy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnConfiguration;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.dialogs.SimpleCredentialsDialog;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.auth.SVNAuthentication;
import java.io.File;
import java.io.IOException;
import java.net.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 2/26/13
* Time: 1:27 PM
*/
public class AuthenticationService {
@NotNull private final SvnVcs myVcs;
private final boolean myIsActive;
private static final Logger LOG = Logger.getInstance(AuthenticationService.class);
private File myTempDirectory;
private boolean myProxyCredentialsWereReturned;
private SvnConfiguration myConfiguration;
private final Set<String> myRequestedCredentials;
public AuthenticationService(@NotNull SvnVcs vcs, boolean isActive) {
myVcs = vcs;
myIsActive = isActive;
myConfiguration = SvnConfiguration.getInstance(myVcs.getProject());
myRequestedCredentials = ContainerUtil.newHashSet();
}
@NotNull
public SvnVcs getVcs() {
return myVcs;
}
@Nullable
public File getTempDirectory() {
return myTempDirectory;
}
public boolean isActive() {
return myIsActive;
}
public boolean authenticateFor(@Nullable String realm, SVNURL repositoryUrl, boolean passwordRequest) {
if (repositoryUrl == null) {
return false;
}
return new CredentialsAuthenticator(this, repositoryUrl, realm).tryAuthenticate(passwordRequest);
}
@Nullable
public SVNAuthentication requestCredentials(final SVNURL repositoryUrl, final String type) {
SVNAuthentication authentication = null;
if (repositoryUrl != null) {
final String realm = repositoryUrl.toDecodedString();
authentication = requestCredentials(realm, type, new Getter<SVNAuthentication>() {
@Override
public SVNAuthentication get() {
return myVcs.getSvnConfiguration().getInteractiveManager(myVcs).getInnerProvider()
.requestClientAuthentication(type, repositoryUrl, realm, null, null, true);
}
});
}
if (authentication == null) {
LOG.warn("Could not get authentication. Type - " + type + ", Url - " + repositoryUrl);
}
return authentication;
}
@Nullable
private <T> T requestCredentials(@NotNull String realm, @NotNull String type, @NotNull Getter<T> fromUserProvider) {
T result = null;
// Search for stored credentials not only by key but also by "parent" keys. This is useful when we work just with URLs
// (not working copy) and can't detect repository url beforehand because authentication is required. If found credentials of "parent"
// are not correct then current key will already be stored in myRequestedCredentials - thus user will be asked for credentials and
// provided result will be stored in cache (with necessary key).
Object data = SvnConfiguration.RUNTIME_AUTH_CACHE.getDataWithLowerCheck(type, realm);
String key = SvnConfiguration.AuthStorage.getKey(type, realm);
// we return credentials from cache if they are asked for the first time during command execution, otherwise - user is asked
if (data != null && !myRequestedCredentials.contains(key)) {
// we already have credentials in memory cache
result = (T)data;
myRequestedCredentials.add(key);
}
else if (myIsActive) {
// ask user for credentials
result = fromUserProvider.get();
if (result != null) {
// save user credentials to memory cache
myVcs.getSvnConfiguration().acknowledge(type, realm, result);
myRequestedCredentials.add(key);
}
}
return result;
}
@Nullable
public String requestSshCredentials(@NotNull final String realm,
@NotNull final SimpleCredentialsDialog.Mode mode,
@NotNull final String key) {
return requestCredentials(realm, ISVNAuthenticationManager.SSH, new Getter<String>() {
@Override
public String get() {
final Ref<String> answer = new Ref<String>();
Runnable command = new Runnable() {
public void run() {
SimpleCredentialsDialog dialog = new SimpleCredentialsDialog(myVcs.getProject());
dialog.setup(mode, realm, key, true);
dialog.setTitle(SvnBundle.message("dialog.title.authentication.required"));
dialog.setSaveEnabled(false);
dialog.show();
if (dialog.isOK()) {
answer.set(dialog.getPassword());
}
}
};
// 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());
return answer.get();
}
});
}
public boolean acceptSSLServerCertificate(final SVNURL repositoryUrl, final String realm) {
if (repositoryUrl == null) {
return false;
}
return new SSLServerCertificateAuthenticator(this, repositoryUrl, realm).tryAuthenticate();
}
public void clearPassiveCredentials(String realm, SVNURL repositoryUrl, boolean password) {
if (repositoryUrl == null) {
return;
}
final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject());
final List<String> kinds = getKinds(repositoryUrl, password);
for (String kind : kinds) {
configuration.clearCredentials(kind, realm);
}
}
public boolean haveDataForTmpConfig() {
final HttpConfigurable instance = HttpConfigurable.getInstance();
return SvnConfiguration.getInstance(myVcs.getProject()).isIsUseDefaultProxy() &&
(instance.USE_HTTP_PROXY || instance.USE_PROXY_PAC);
}
@Nullable
public static Proxy getIdeaDefinedProxy(@NotNull final SVNURL url) {
// SVNKit authentication implementation sets repositories as noProxy() to provide custom proxy authentication logic - see for instance,
// SvnAuthenticationManager.getProxyManager(). But noProxy() setting is not cleared correctly in all cases - so if svn command
// (for command line) is executed on thread where repository url was added as noProxy() => proxies are not retrieved for such commands
// and execution logic is incorrect.
// To prevent such behavior repositoryUrl is manually removed from noProxy() list (for current thread).
// NOTE, that current method is only called from code flows for executing commands through command line client and should not be called
// from SVNKit code flows.
CommonProxy.getInstance().removeNoProxy(url.getProtocol(), url.getHost(), url.getPort());
final List<Proxy> proxies = CommonProxy.getInstance().select(URI.create(url.toString()));
if (proxies != null && !proxies.isEmpty()) {
for (Proxy proxy : proxies) {
if (HttpConfigurable.isRealProxy(proxy) && Proxy.Type.HTTP.equals(proxy.type())) {
return proxy;
}
}
}
return null;
}
@Nullable
public PasswordAuthentication getProxyAuthentication(@NotNull SVNURL repositoryUrl) {
Proxy proxy = getIdeaDefinedProxy(repositoryUrl);
PasswordAuthentication result = null;
if (proxy != null) {
if (myProxyCredentialsWereReturned) {
showFailedAuthenticateProxy();
}
else {
result = getProxyAuthentication(proxy, repositoryUrl);
myProxyCredentialsWereReturned = result != null;
}
}
return result;
}
private static void showFailedAuthenticateProxy() {
HttpConfigurable instance = HttpConfigurable.getInstance();
String message = instance.USE_HTTP_PROXY || instance.USE_PROXY_PAC
? "Failed to authenticate to proxy. You can change proxy credentials in HTTP proxy settings."
: "Failed to authenticate to proxy.";
PopupUtil.showBalloonForActiveComponent(message, MessageType.ERROR);
}
@Nullable
private static PasswordAuthentication getProxyAuthentication(@NotNull Proxy proxy, @NotNull SVNURL repositoryUrl) {
PasswordAuthentication result = null;
try {
result = Authenticator.requestPasswordAuthentication(repositoryUrl.getHost(), ((InetSocketAddress)proxy.address()).getAddress(),
repositoryUrl.getPort(), repositoryUrl.getProtocol(), repositoryUrl.getHost(),
repositoryUrl.getProtocol(), new URL(repositoryUrl.toString()),
Authenticator.RequestorType.PROXY);
}
catch (MalformedURLException e) {
LOG.info(e);
}
return result;
}
public void reset() {
if (myTempDirectory != null) {
FileUtil.delete(myTempDirectory);
}
}
@NotNull
public static List<String> getKinds(final SVNURL url, boolean passwordRequest) {
if (passwordRequest || "http".equals(url.getProtocol())) {
return Collections.singletonList(ISVNAuthenticationManager.PASSWORD);
}
else if ("https".equals(url.getProtocol())) {
return Collections.singletonList(ISVNAuthenticationManager.SSL);
}
else if ("svn".equals(url.getProtocol())) {
return Collections.singletonList(ISVNAuthenticationManager.PASSWORD);
}
else if (url.getProtocol().contains("svn+")) { // todo +-
return Arrays.asList(ISVNAuthenticationManager.SSH, ISVNAuthenticationManager.USERNAME);
}
return Collections.singletonList(ISVNAuthenticationManager.USERNAME);
}
@Nullable
public File getSpecialConfigDir() {
return myTempDirectory != null ? myTempDirectory : new File(myConfiguration.getConfigurationDirectory());
}
public void initTmpDir(SvnConfiguration configuration) throws IOException {
if (myTempDirectory == null) {
myTempDirectory = FileUtil.createTempDirectory("tmp", "Subversion");
FileUtil.copyDir(new File(configuration.getConfigurationDirectory()), myTempDirectory);
}
}
}