| /* |
| * 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); |
| } |
| } |
| } |