/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2012 Alex Buloichik 2014 Alex Buloichik, Aaron Madlon-Kay 2015 Hiroshi Miura, Aaron Madlon-Kay Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.core.team2.impl; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JOptionPane; import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.transport.CredentialItem; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.JschConfigSessionFactory; import org.eclipse.jgit.transport.OpenSshConfig; import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.FS; import org.omegat.core.Core; import org.omegat.core.KnownException; import org.omegat.core.team2.ProjectTeamSettings; import org.omegat.core.team2.TeamSettings; import org.omegat.util.Log; import org.omegat.util.OStrings; import com.jcraft.jsch.IdentityRepository; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.agentproxy.AgentProxyException; import com.jcraft.jsch.agentproxy.Connector; import com.jcraft.jsch.agentproxy.ConnectorFactory; import com.jcraft.jsch.agentproxy.RemoteIdentityRepository; import com.jcraft.jsch.agentproxy.USocketFactory; import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector; import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory; /** * Git repository credentials provider. One credentials provider created for all git instances. * <p> * Git supports these protocols: * <ul> * <li>file:// * <li>ssh:// * <li>git:// * <li>http:// * </ul> * * @author Alex Buloichik (alex73mail@gmail.com) * @author Aaron Madlon-Kay * @see <a href="http://www.codeaffine.com/2014/12/09/jgit-authentication/">JGit Authentication Explained</a> */ public class GITCredentialsProvider extends CredentialsProvider { static { // Set up ssh-agent support JschConfigSessionFactory sessionFactory = new JschConfigSessionFactory() { @Override protected void configure(OpenSshConfig.Host host, Session session) { session.setConfig("StrictHostKeyChecking", "true"); } @Override protected JSch createDefaultJSch(FS fs) throws JSchException { Connector con = null; try { if (SSHAgentConnector.isConnectorAvailable()) { USocketFactory usf = new JNAUSocketFactory(); con = new SSHAgentConnector(usf); } else { ConnectorFactory cf = ConnectorFactory.getDefault(); con = cf.createConnector(); } } catch (AgentProxyException e) { Log.log(e); } JSch jsch = super.createDefaultJSch(fs); if (con != null) { JSch.setConfig("PreferredAuthentications", "publickey"); IdentityRepository irepo = new RemoteIdentityRepository(con); jsch.setIdentityRepository(irepo); } return jsch; } }; SshSessionFactory.setInstance(sessionFactory); } static final String KEY_USERNAME_SUFFIX = "username"; static final String KEY_PASSWORD_SUFFIX = "password"; static final String KEY_FINGERPRINT_SUFFIX = "fingerprint"; //private ProjectTeamSettings teamSettings; /** Predefined in the omegat.project file. */ private final Map<String, String> predefined = Collections.synchronizedMap(new HashMap<String, String>()); public void setTeamSettings(ProjectTeamSettings teamSettings) { //this.teamSettings = teamSettings; } public void setPredefinedCredentials(String url, String predefinedUser, String predefinedPass, String predefinedFingerprint) { predefined.put("user." + url, predefinedUser); predefined.put("pass." + url, predefinedPass); predefined.put("fingerprint." + url, predefinedFingerprint); } private Credentials loadCredentials(URIish uri) { String url = uri.toString(); Credentials credentials = new Credentials(); credentials.username = TeamSettings.get(url + "!" + KEY_USERNAME_SUFFIX); credentials.password = TeamUtils.decodePassword(TeamSettings.get(url + "!" + KEY_PASSWORD_SUFFIX)); return credentials; } private void saveCredentials(URIish uri, Credentials credentials) { String url = uri.toString(); try { TeamSettings.set(url + "!" + KEY_USERNAME_SUFFIX, credentials.username); TeamSettings.set(url + "!" + KEY_PASSWORD_SUFFIX, TeamUtils.encodePassword(credentials.password)); } catch (Exception e) { Core.getMainWindow().displayErrorRB(e, "TEAM_ERROR_SAVE_CREDENTIALS", null, "TF_ERROR"); } } private String loadFingerprint(URIish uri) { String url = uri.toString(); return TeamSettings.get(url + "!" + KEY_FINGERPRINT_SUFFIX); } private void saveFingerprint(URIish uri, String fingerprint) { String url = uri.toString(); try { TeamSettings.set(url + "!" + KEY_FINGERPRINT_SUFFIX, fingerprint); } catch (Exception e) { Core.getMainWindow().displayErrorRB(e, "TEAM_ERROR_SAVE_CREDENTIALS", null, "TF_ERROR"); } } @Override public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { // get predefined if exist String url = uri.toString(); String predefinedUser = predefined.get("user." + url); String predefinedPass = predefined.get("pass." + url); String predefinedFingerprint = predefined.get("fingerprint." + url); // get saved Credentials credentials = loadCredentials(uri); boolean ok = false; // theoretically, username can be unknown, but in practice it is always set, so not requested. for (CredentialItem i : items) { if (i instanceof CredentialItem.Username) { if (predefinedUser != null && predefinedPass != null) { ((CredentialItem.Username) i).setValue(predefinedUser); continue; } if (credentials.username == null) { credentials = askCredentials(uri, credentials); if (credentials == null) { throw new UnsupportedCredentialItem(uri, OStrings.getString("TEAM_CREDENTIALS_DENIED")); } saveCredentials(uri, credentials); ok = true; } ((CredentialItem.Username) i).setValue(credentials.username); continue; } else if (i instanceof CredentialItem.Password) { if (predefinedUser != null && predefinedPass != null) { ((CredentialItem.Password) i).setValue(predefinedPass.toCharArray()); continue; } if (credentials.password == null) { credentials = askCredentials(uri, credentials); if (credentials == null) { throw new UnsupportedCredentialItem(uri, OStrings.getString("TEAM_CREDENTIALS_DENIED")); } saveCredentials(uri, credentials); ok = true; } ((CredentialItem.Password) i).setValue(credentials.password.toCharArray()); continue; } else if (i instanceof CredentialItem.StringType) { if (i.getPromptText().equals("Password: ")) { if (predefinedUser != null && predefinedPass != null) { ((CredentialItem.StringType) i).setValue(predefinedPass); continue; } if (credentials.password == null) { if (!ok) { credentials = askCredentials(uri, credentials); if (credentials == null) { throw new UnsupportedCredentialItem(uri, OStrings.getString("TEAM_CREDENTIALS_DENIED")); } saveCredentials(uri, credentials); } } ((CredentialItem.StringType) i).setValue(credentials.password); continue; } else if (i.getPromptText().startsWith("Passphrase for ")) { // Private key passphrase if (!ok) { String passphrase = askPassphrase(i.getPromptText()); if (passphrase == null) { throw new UnsupportedCredentialItem(uri, OStrings.getString("TEAM_CREDENTIALS_DENIED")); } ((CredentialItem.StringType) i).setValue(passphrase); continue; } } } else if (i instanceof CredentialItem.YesNoType) { // e.g.: The authenticity of host 'mygitserver' can't be established. // RSA key fingerprint is e2:d3:84:d5:86:e7:68:69:a0:aa:a6:ad:a3:a0:ab:a2. // Are you sure you want to continue connecting? String storedFingerprint = loadFingerprint(uri); String promptText = i.getPromptText(); String promptedFingerprint = extractFingerprint(promptText); if (promptedFingerprint == null) { throw new UnsupportedCredentialItem(uri, "Wrong fingerprint pattern"); } if (predefinedFingerprint != null) { if (promptedFingerprint.equals(predefinedFingerprint)) { ((CredentialItem.YesNoType) i).setValue(true); } else { ((CredentialItem.YesNoType) i).setValue(false); } continue; } if (promptedFingerprint.equals(storedFingerprint)) { ((CredentialItem.YesNoType) i).setValue(true); continue; } int choice = Core.getMainWindow().showConfirmDialog(promptText, null, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (choice == JOptionPane.YES_OPTION) { ((CredentialItem.YesNoType) i).setValue(true); saveFingerprint(uri, promptedFingerprint); } else { ((CredentialItem.YesNoType) i).setValue(false); } continue; } else if (i instanceof CredentialItem.InformationalMessage) { Core.getMainWindow().showMessageDialog(i.getPromptText()); continue; } throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText()); } return true; } @Override public boolean isInteractive() { return true; } @Override public boolean supports(CredentialItem... items) { for (CredentialItem i : items) { if (i instanceof CredentialItem.Username) { continue; } else if (i instanceof CredentialItem.Password) { continue; } else { return false; } } return true; } /** * shows dialog to ask for credentials, and stores credentials. * * @return true when entered, false on cancel. */ private Credentials askCredentials(URIish uri, Credentials credentials) { GITUserPassDialog userPassDialog = new GITUserPassDialog(Core.getMainWindow().getApplicationFrame()); userPassDialog.setLocationRelativeTo(Core.getMainWindow().getApplicationFrame()); userPassDialog.descriptionTextArea.setText(OStrings .getString(credentials.username == null ? "TEAM_USERPASS_FIRST" : "TEAM_USERPASS_WRONG")); // if username is already available in uri, then we will not be asked for an username, so we cannot // change it. if (uri.getUser() != null && !"".equals(uri.getUser())) { userPassDialog.userText.setText(uri.getUser()); userPassDialog.userText.setEditable(false); userPassDialog.userText.setEnabled(false); } if (credentials.username != null) { userPassDialog.userText.setText(credentials.username); } userPassDialog.setVisible(true); if (userPassDialog.getReturnStatus() == GITUserPassDialog.RET_OK) { credentials.username = userPassDialog.userText.getText(); credentials.password = new String(userPassDialog.passwordField.getPassword()); return credentials; } else { return null; } } private String askPassphrase(String prompt) { GITUserPassDialog userPassDialog = new GITUserPassDialog(Core.getMainWindow().getApplicationFrame()); userPassDialog.setLocationRelativeTo(Core.getMainWindow().getApplicationFrame()); userPassDialog.descriptionTextArea.setText(prompt); userPassDialog.userText.setVisible(false); userPassDialog.userLabel.setVisible(false); userPassDialog.passwordField.requestFocusInWindow(); userPassDialog.setVisible(true); if (userPassDialog.getReturnStatus() == GITUserPassDialog.RET_OK) { return new String(userPassDialog.passwordField.getPassword()); } else { return null; } } @Override public void reset(URIish uri) { // reset is called after 5 authorization failures. After 3 resets, the transport gives up. String url = uri.toString(); String predefinedUser = predefined.get("user." + url); String predefinedPass = predefined.get("pass." + url); if (predefinedUser != null && predefinedPass != null) { throw new KnownException("TEAM_PREDEFINED_CREDENTIALS_ERROR"); } Credentials credentials = loadCredentials(uri); credentials.username = null; credentials.password = null; saveCredentials(uri, credentials); } private static String extractFingerprint(String text) { Pattern p = Pattern .compile("The authenticity of host '.*' can't be established\\.\\nRSA key fingerprint is (([0-9a-f]{2}:){15}[0-9a-f]{2})\\.\\nAre you sure you want to continue connecting\\?"); Matcher fingerprintMatcher = p.matcher(text); if (fingerprintMatcher.find()) { int start = fingerprintMatcher.start(1); int end = fingerprintMatcher.end(1); return text.substring(start, end); } return null; } public static class Credentials { public String username = null; public String password = null; } }