Remove check for user/credentials when trying to SSH to Gerrit
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / op / ConfigureGerritAfterCloneTask.java
blob1035aee2e0df0c7f76a193fb0f741341ff4ac797
1 /*******************************************************************************
2 * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
3 * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
5 * All rights reserved. This program and the accompanying materials
6 * are made available under the terms of the Eclipse Public License v1.0
7 * which accompanies this distribution, and is available at
8 * http://www.eclipse.org/legal/epl-v10.html
9 *******************************************************************************/
10 package org.eclipse.egit.core.op;
12 import java.io.BufferedReader;
13 import java.io.ByteArrayOutputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.InputStreamReader;
17 import java.net.HttpURLConnection;
18 import java.net.MalformedURLException;
19 import java.net.URISyntaxException;
20 import java.net.URL;
21 import java.util.regex.Pattern;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IProgressMonitor;
25 import org.eclipse.egit.core.Activator;
26 import org.eclipse.egit.core.NetUtil;
27 import org.eclipse.egit.core.internal.gerrit.GerritUtil;
28 import org.eclipse.egit.core.op.CloneOperation.PostCloneTask;
29 import org.eclipse.jgit.lib.Constants;
30 import org.eclipse.jgit.lib.Repository;
31 import org.eclipse.jgit.lib.StoredConfig;
32 import org.eclipse.jgit.transport.CredentialsProvider;
33 import org.eclipse.jgit.transport.RemoteConfig;
34 import org.eclipse.jgit.transport.RemoteSession;
35 import org.eclipse.jgit.transport.SshSessionFactory;
36 import org.eclipse.jgit.transport.URIish;
37 import org.eclipse.jgit.util.FS;
38 import org.eclipse.jgit.util.io.MessageWriter;
39 import org.eclipse.jgit.util.io.StreamCopyThread;
41 /**
42 * Configure Gerrit if repository was cloned from a Gerrit server
44 public class ConfigureGerritAfterCloneTask implements PostCloneTask {
46 private static final String GIT_ECLIPSE_ORG = "git.eclipse.org"; //$NON-NLS-1$
48 private static final String GERRIT_CONTEXT_ROOT = "/r/"; //$NON-NLS-1$
50 private static final String HTTP = "http"; //$NON-NLS-1$
52 private static final String HTTPS = "https"; //$NON-NLS-1$
54 private static final String SSH = "ssh"; //$NON-NLS-1$
56 private static final String GERRIT_CONFIG_SERVER_VERSION_API = "/config/server/version"; //$NON-NLS-1$
58 private static final int GERRIT_SSHD_DEFAULT_PORT = 29418;
60 private static final String GERRIT_SSHD_VERSION_API = "gerrit version"; //$NON-NLS-1$
62 /**
63 * Pattern to match the sshd reply[1] against to determine whether it's a
64 * Gerrit.
65 * <p>
66 * [1]<a href=
67 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-version.html">
68 * Gerrit 2.11 gerrit version ssh command</a>
69 * </p>
70 * <p>
71 * We match the whole reply from Gerrit's sshd (as opposed to a prefix match
72 * for "gerrit version") just in case a non-Gerrit has the great idea to
73 * return an error message like "gerrit version: unknown command" or some
74 * such on its stdout...
75 * </p>
77 private static final Pattern GERRIT_SSHD_REPLY = Pattern
78 .compile(GERRIT_SSHD_VERSION_API
79 + "\\s+(?:\\d+(?:\\.\\d+)+|.+-\\d+-g[0-9a-fA-F]{7,})"); //$NON-NLS-1$
81 /**
82 * To prevent against Cross Site Script Inclusion (XSSI) attacks, the Gerrit
83 * JSON response body starts with a magic prefix line we can use as a second
84 * marker beyond the get version endpoint [1] to detect a Gerrit server
85 * <p/>
86 * [1] <a href=
87 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
88 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
89 * endpoint</a>
91 private static final String GERRIT_XSSI_MAGIC_STRING = ")]}\'\n"; //$NON-NLS-1$
93 private final String uri;
95 private final String remoteName;
97 private final CredentialsProvider credentialsProvider;
99 private int timeout;
102 * @param uri
103 * not null
104 * @param remoteName
105 * not null
106 * @param credentialsProvider
107 * {@link CredentialsProvider} to use for remote communication;
108 * if {@code null} auto-configuration for repositories cloned
109 * over ssh will work only for git.eclipse.org
110 * @param timeout
111 * timeout for remote communication in seconds
113 public ConfigureGerritAfterCloneTask(String uri, String remoteName,
114 CredentialsProvider credentialsProvider, int timeout) {
115 this.uri = uri;
116 this.remoteName = remoteName;
117 this.timeout = timeout;
118 this.credentialsProvider = credentialsProvider;
121 @Override
122 public void execute(Repository repository, IProgressMonitor monitor)
123 throws CoreException {
124 try {
125 if (isGerrit(repository)) {
126 Activator.logInfo(uri
127 + " was detected to be hosted by a Gerrit server"); //$NON-NLS-1$
128 configureGerrit(repository);
130 } catch (Exception e) {
131 throw new CoreException(Activator.error(e.getMessage(), e));
136 * Try to use Gerrit's "Get Version" REST API endpoint [1] to detect if the
137 * repository is hosted on a Gerrit server.
138 * <p/>
139 * [1] <a href=
140 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
141 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
142 * endpoint</a>
144 * @param repo
145 * the repository to be configured
147 * @return {@code true} if the repository is hosted on a Gerrit server
148 * @throws IOException
149 * @throws MalformedURLException
150 * @throws URISyntaxException
152 private boolean isGerrit(Repository repo) throws MalformedURLException,
153 IOException,
154 URISyntaxException {
155 URIish u = new URIish(uri);
156 final String s = u.getScheme();
157 final String host = u.getHost();
158 final String path = u.getPath();
160 // shortcut for Eclipse Gerrit server
161 if (host != null && host.equals(GIT_ECLIPSE_ORG)) {
162 if (HTTPS.equals(s) && (u.getPort() == 443 || u.getPort() == -1)
163 && path != null && path.startsWith(GERRIT_CONTEXT_ROOT)) {
164 return true;
165 } else if (SSH.equals(s)
166 && u.getPort() == GERRIT_SSHD_DEFAULT_PORT) {
167 return true;
171 if (path != null && (HTTP.equals(s) || HTTPS.equals(s))) {
172 String baseURL = u.setPath("/").toString(); //$NON-NLS-1$
173 baseURL = baseURL.substring(0, baseURL.length() - 1);
174 String tmpPath = ""; //$NON-NLS-1$
175 int slash = 1;
176 while (true) {
177 HttpURLConnection httpConnection = null;
178 try {
179 httpConnection = (HttpURLConnection) new URL(baseURL
180 + tmpPath + GERRIT_CONFIG_SERVER_VERSION_API)
181 .openConnection();
182 NetUtil.setSslVerification(repo, httpConnection);
183 httpConnection.setRequestMethod("GET"); //$NON-NLS-1$
184 httpConnection.setReadTimeout(1000 * timeout);
185 int responseCode = httpConnection.getResponseCode();
186 switch (responseCode) {
187 case HttpURLConnection.HTTP_OK:
188 try (InputStream in = httpConnection.getInputStream()) {
189 String response = readFully(in, "UTF-8"); //$NON-NLS-1$
190 return response
191 .startsWith(GERRIT_XSSI_MAGIC_STRING);
193 case HttpURLConnection.HTTP_NOT_FOUND:
194 if (slash > path.length()) {
195 return false;
197 slash = path.indexOf('/', slash);
198 if (slash == -1) {
199 // try the entire path
200 slash = path.length();
202 tmpPath = path.substring(0, slash);
203 slash++;
204 break;
205 default:
206 return false;
208 } catch (IOException e) {
209 return false;
210 } finally {
211 if (httpConnection != null) {
212 httpConnection.disconnect();
216 } else if (SSH.equals(s)) {
217 if (u.getPort() < 0) {
218 return false;
220 URIish sshUri = u.setPath(""); //$NON-NLS-1$
221 try {
222 String result = runSshCommand(sshUri, credentialsProvider,
223 repo.getFS(), GERRIT_SSHD_VERSION_API);
224 return result != null
225 && GERRIT_SSHD_REPLY.matcher(result).matches();
226 } catch (IOException e) {
227 // Something went wrong with the connection or with the command
228 // execution. Maybe the server didn't recognize the command. Do
229 // the safe thing and claim it wasn't a Gerrit. In the worst
230 // case, the user may have to do the Gerrit config setup via
231 // the ConfigureGerritWizard.
232 return false;
235 return false;
238 private String readFully(InputStream inputStream, String encoding)
239 throws IOException {
240 return new String(readFully(inputStream), encoding);
243 private byte[] readFully(InputStream inputStream) throws IOException {
244 ByteArrayOutputStream os = new ByteArrayOutputStream();
245 byte[] buffer = new byte[1024];
246 int length = 0;
247 while ((length = inputStream.read(buffer)) != -1) {
248 os.write(buffer, 0, length);
250 return os.toByteArray();
253 private void configureGerrit(Repository repository)
254 throws URISyntaxException, IOException {
255 StoredConfig config = repository.getConfig();
256 RemoteConfig remoteConfig;
257 remoteConfig = GerritUtil.findRemoteConfig(config, remoteName);
258 if (remoteConfig == null) {
259 return;
261 GerritUtil.configurePushURI(remoteConfig, new URIish(uri));
262 GerritUtil.configurePushRefSpec(remoteConfig, Constants.MASTER);
263 GerritUtil.configureFetchNotes(remoteConfig);
264 GerritUtil.setCreateChangeId(config);
265 remoteConfig.update(config);
266 config.save();
269 private String runSshCommand(URIish sshUri, CredentialsProvider provider,
270 FS fs, String command) throws IOException {
271 RemoteSession session = null;
272 Process process = null;
273 StreamCopyThread errorThread = null;
274 try (MessageWriter stderr = new MessageWriter()) {
275 session = SshSessionFactory.getInstance().getSession(sshUri,
276 provider, fs, 1000 * timeout);
277 process = session.exec(command, 0);
278 errorThread = new StreamCopyThread(process.getErrorStream(),
279 stderr.getRawStream());
280 errorThread.start();
281 try (BufferedReader reader = new BufferedReader(
282 new InputStreamReader(process.getInputStream(),
283 Constants.CHARSET))) {
284 return reader.readLine();
286 } finally {
287 if (errorThread != null) {
288 try {
289 errorThread.halt();
290 } catch (InterruptedException e) {
291 // Stop waiting and return anyway.
292 } finally {
293 errorThread = null;
296 if (process != null) {
297 process.destroy();
299 if (session != null) {
300 SshSessionFactory.getInstance().releaseSession(session);