View Javadoc
1   /*
2    * Copyright (C) 2017, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.http.test;
12  
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertFalse;
15  import static org.junit.Assert.assertTrue;
16  import static org.junit.Assert.fail;
17  
18  import java.io.IOException;
19  import java.util.EnumSet;
20  import java.util.List;
21  
22  import javax.servlet.DispatcherType;
23  import javax.servlet.Filter;
24  import javax.servlet.FilterChain;
25  import javax.servlet.FilterConfig;
26  import javax.servlet.ServletException;
27  import javax.servlet.ServletRequest;
28  import javax.servlet.ServletResponse;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.eclipse.jetty.servlet.FilterHolder;
33  import org.eclipse.jetty.servlet.ServletContextHandler;
34  import org.eclipse.jetty.servlet.ServletHolder;
35  import org.eclipse.jgit.errors.TransportException;
36  import org.eclipse.jgit.errors.UnsupportedCredentialItem;
37  import org.eclipse.jgit.http.server.GitServlet;
38  import org.eclipse.jgit.junit.TestRepository;
39  import org.eclipse.jgit.junit.http.AccessEvent;
40  import org.eclipse.jgit.junit.http.AppServer;
41  import org.eclipse.jgit.lib.ConfigConstants;
42  import org.eclipse.jgit.lib.NullProgressMonitor;
43  import org.eclipse.jgit.lib.Repository;
44  import org.eclipse.jgit.lib.StoredConfig;
45  import org.eclipse.jgit.revwalk.RevBlob;
46  import org.eclipse.jgit.revwalk.RevCommit;
47  import org.eclipse.jgit.transport.CredentialItem;
48  import org.eclipse.jgit.transport.CredentialsProvider;
49  import org.eclipse.jgit.transport.Transport;
50  import org.eclipse.jgit.transport.URIish;
51  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
52  import org.eclipse.jgit.util.HttpSupport;
53  import org.junit.Before;
54  import org.junit.Test;
55  import org.junit.runner.RunWith;
56  import org.junit.runners.Parameterized;
57  
58  @RunWith(Parameterized.class)
59  public class SmartClientSmartServerSslTest extends AllProtocolsHttpTestCase {
60  
61  	// We run these tests with a server on localhost with a self-signed
62  	// certificate. We don't do authentication tests here, so there's no need
63  	// for username and password.
64  	//
65  	// But the server certificate will not validate. We know that Transport will
66  	// ask whether we trust the server all the same. This credentials provider
67  	// blindly trusts the self-signed certificate by answering "Yes" to all
68  	// questions.
69  	private CredentialsProvider testCredentials = new CredentialsProvider() {
70  
71  		@Override
72  		public boolean isInteractive() {
73  			return false;
74  		}
75  
76  		@Override
77  		public boolean supports(CredentialItem... items) {
78  			for (CredentialItem item : items) {
79  				if (item instanceof CredentialItem.InformationalMessage) {
80  					continue;
81  				}
82  				if (item instanceof CredentialItem.YesNoType) {
83  					continue;
84  				}
85  				return false;
86  			}
87  			return true;
88  		}
89  
90  		@Override
91  		public boolean get(URIish uri, CredentialItem... items)
92  				throws UnsupportedCredentialItem {
93  			for (CredentialItem item : items) {
94  				if (item instanceof CredentialItem.InformationalMessage) {
95  					continue;
96  				}
97  				if (item instanceof CredentialItem.YesNoType) {
98  					((CredentialItem.YesNoType) item).setValue(true);
99  					continue;
100 				}
101 				return false;
102 			}
103 			return true;
104 		}
105 	};
106 
107 	private URIish remoteURI;
108 
109 	private URIish secureURI;
110 
111 	private RevBlob A_txt;
112 
113 	private RevCommit A, B;
114 
115 	public SmartClientSmartServerSslTest(TestParameters params) {
116 		super(params);
117 	}
118 
119 	@Override
120 	protected AppServer createServer() {
121 		return new AppServer(0, 0);
122 	}
123 
124 	@Override
125 	@Before
126 	public void setUp() throws Exception {
127 		super.setUp();
128 
129 		final TestRepository<Repository> src = createTestRepository();
130 		final String srcName = src.getRepository().getDirectory().getName();
131 		StoredConfig cfg = src.getRepository().getConfig();
132 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
133 				ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
134 		cfg.setInt("protocol", null, "version", enableProtocolV2 ? 2 : 0);
135 		cfg.save();
136 
137 		GitServlet gs = new GitServlet();
138 
139 		ServletContextHandler app = addNormalContext(gs, src, srcName);
140 
141 		server.setUp();
142 
143 		remoteURI = toURIish(app, srcName);
144 		secureURI = new URIish(rewriteUrl(remoteURI.toString(), "https",
145 				server.getSecurePort()));
146 
147 		A_txt = src.blob("A");
148 		A = src.commit().add("A_txt", A_txt).create();
149 		B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
150 		src.update(master, B);
151 
152 		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
153 	}
154 
155 	private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
156 		ServletContextHandler app = server.addContext("/git");
157 		app.addFilter(new FilterHolder(new Filter() {
158 
159 			@Override
160 			public void init(FilterConfig filterConfig)
161 					throws ServletException {
162 				// empty
163 			}
164 
165 			// Redirects http to https for requests containing "/https/".
166 			@Override
167 			public void doFilter(ServletRequest request,
168 					ServletResponse response, FilterChain chain)
169 					throws IOException, ServletException {
170 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
171 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
172 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
173 				if (httpServletRequest.getQueryString() != null) {
174 					fullUrl.append("?")
175 							.append(httpServletRequest.getQueryString());
176 				}
177 				String urlString = rewriteUrl(fullUrl.toString(), "https",
178 						server.getSecurePort());
179 				httpServletResponse
180 						.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
181 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
182 						urlString.replace("/https/", "/"));
183 			}
184 
185 			@Override
186 			public void destroy() {
187 				// empty
188 			}
189 		}), "/https/*", EnumSet.of(DispatcherType.REQUEST));
190 		app.addFilter(new FilterHolder(new Filter() {
191 
192 			@Override
193 			public void init(FilterConfig filterConfig)
194 					throws ServletException {
195 				// empty
196 			}
197 
198 			// Redirects https back to http for requests containing "/back/".
199 			@Override
200 			public void doFilter(ServletRequest request,
201 					ServletResponse response, FilterChain chain)
202 					throws IOException, ServletException {
203 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
204 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
205 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
206 				if (httpServletRequest.getQueryString() != null) {
207 					fullUrl.append("?")
208 							.append(httpServletRequest.getQueryString());
209 				}
210 				String urlString = rewriteUrl(fullUrl.toString(), "http",
211 						server.getPort());
212 				httpServletResponse
213 						.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
214 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
215 						urlString.replace("/back/", "/"));
216 			}
217 
218 			@Override
219 			public void destroy() {
220 				// empty
221 			}
222 		}), "/back/*", EnumSet.of(DispatcherType.REQUEST));
223 		gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
224 		app.addServlet(new ServletHolder(gs), "/*");
225 		return app;
226 	}
227 
228 	@Test
229 	public void testInitialClone_ViaHttps() throws Exception {
230 		Repository dst = createBareRepository();
231 		assertFalse(dst.getObjectDatabase().has(A_txt));
232 
233 		try (Transport t = Transport.open(dst, secureURI)) {
234 			t.setCredentialsProvider(testCredentials);
235 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
236 		}
237 		assertTrue(dst.getObjectDatabase().has(A_txt));
238 		assertEquals(B, dst.exactRef(master).getObjectId());
239 		fsck(dst, B);
240 
241 		List<AccessEvent> requests = getRequests();
242 		assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
243 	}
244 
245 	@Test
246 	public void testInitialClone_RedirectToHttps() throws Exception {
247 		Repository dst = createBareRepository();
248 		assertFalse(dst.getObjectDatabase().has(A_txt));
249 
250 		URIish cloneFrom = extendPath(remoteURI, "/https");
251 		try (Transport t = Transport.open(dst, cloneFrom)) {
252 			t.setCredentialsProvider(testCredentials);
253 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
254 		}
255 		assertTrue(dst.getObjectDatabase().has(A_txt));
256 		assertEquals(B, dst.exactRef(master).getObjectId());
257 		fsck(dst, B);
258 
259 		List<AccessEvent> requests = getRequests();
260 		assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
261 	}
262 
263 	@Test
264 	public void testInitialClone_RedirectBackToHttp() throws Exception {
265 		Repository dst = createBareRepository();
266 		assertFalse(dst.getObjectDatabase().has(A_txt));
267 
268 		URIish cloneFrom = extendPath(secureURI, "/back");
269 		try (Transport t = Transport.open(dst, cloneFrom)) {
270 			t.setCredentialsProvider(testCredentials);
271 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
272 			fail("Should have failed (redirect from https to http)");
273 		} catch (TransportException e) {
274 			assertTrue(e.getMessage().contains("not allowed"));
275 		}
276 	}
277 
278 	@Test
279 	public void testInitialClone_SslFailure() throws Exception {
280 		Repository dst = createBareRepository();
281 		assertFalse(dst.getObjectDatabase().has(A_txt));
282 
283 		try (Transport t = Transport.open(dst, secureURI)) {
284 			// Set a credentials provider that doesn't handle questions
285 			t.setCredentialsProvider(
286 					new UsernamePasswordCredentialsProvider("any", "anypwd"));
287 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
288 			fail("Should have failed (SSL certificate not trusted)");
289 		} catch (TransportException e) {
290 			assertTrue(e.getMessage().contains("Secure connection"));
291 		}
292 	}
293 
294 }