diff options
Diffstat (limited to 'plugins/svn4idea/src/org/jetbrains/idea/svn/auth/AuthenticationService.java')
-rw-r--r-- | plugins/svn4idea/src/org/jetbrains/idea/svn/auth/AuthenticationService.java | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/AuthenticationService.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/AuthenticationService.java new file mode 100644 index 000000000000..9fb377e1dafe --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/AuthenticationService.java @@ -0,0 +1,303 @@ +/* + * 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); + } + } +} |