View Javadoc
1   /*
2    * Copyright (C) 2017 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.revwalk.RevBlob;
45  import org.eclipse.jgit.revwalk.RevCommit;
46  import org.eclipse.jgit.transport.CredentialItem;
47  import org.eclipse.jgit.transport.CredentialsProvider;
48  import org.eclipse.jgit.transport.Transport;
49  import org.eclipse.jgit.transport.URIish;
50  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
51  import org.eclipse.jgit.transport.http.HttpConnectionFactory;
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 AllFactoriesHttpTestCase {
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(HttpConnectionFactory cf) {
116 		super(cf);
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 		src.getRepository()
132 				.getConfig()
133 				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
134 						ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
135 
136 		GitServlet gs = new GitServlet();
137 
138 		ServletContextHandler app = addNormalContext(gs, src, srcName);
139 
140 		server.setUp();
141 
142 		remoteURI = toURIish(app, srcName);
143 		secureURI = new URIish(rewriteUrl(remoteURI.toString(), "https",
144 				server.getSecurePort()));
145 
146 		A_txt = src.blob("A");
147 		A = src.commit().add("A_txt", A_txt).create();
148 		B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
149 		src.update(master, B);
150 
151 		src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
152 	}
153 
154 	private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
155 		ServletContextHandler app = server.addContext("/git");
156 		app.addFilter(new FilterHolder(new Filter() {
157 
158 			@Override
159 			public void init(FilterConfig filterConfig)
160 					throws ServletException {
161 				// empty
162 			}
163 
164 			// Redirects http to https for requests containing "/https/".
165 			@Override
166 			public void doFilter(ServletRequest request,
167 					ServletResponse response, FilterChain chain)
168 					throws IOException, ServletException {
169 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
170 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
171 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
172 				if (httpServletRequest.getQueryString() != null) {
173 					fullUrl.append("?")
174 							.append(httpServletRequest.getQueryString());
175 				}
176 				String urlString = rewriteUrl(fullUrl.toString(), "https",
177 						server.getSecurePort());
178 				httpServletResponse
179 						.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
180 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
181 						urlString.replace("/https/", "/"));
182 			}
183 
184 			@Override
185 			public void destroy() {
186 				// empty
187 			}
188 		}), "/https/*", EnumSet.of(DispatcherType.REQUEST));
189 		app.addFilter(new FilterHolder(new Filter() {
190 
191 			@Override
192 			public void init(FilterConfig filterConfig)
193 					throws ServletException {
194 				// empty
195 			}
196 
197 			// Redirects https back to http for requests containing "/back/".
198 			@Override
199 			public void doFilter(ServletRequest request,
200 					ServletResponse response, FilterChain chain)
201 					throws IOException, ServletException {
202 				final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
203 				final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
204 				final StringBuffer fullUrl = httpServletRequest.getRequestURL();
205 				if (httpServletRequest.getQueryString() != null) {
206 					fullUrl.append("?")
207 							.append(httpServletRequest.getQueryString());
208 				}
209 				String urlString = rewriteUrl(fullUrl.toString(), "http",
210 						server.getPort());
211 				httpServletResponse
212 						.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
213 				httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
214 						urlString.replace("/back/", "/"));
215 			}
216 
217 			@Override
218 			public void destroy() {
219 				// empty
220 			}
221 		}), "/back/*", EnumSet.of(DispatcherType.REQUEST));
222 		gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
223 		app.addServlet(new ServletHolder(gs), "/*");
224 		return app;
225 	}
226 
227 	@Test
228 	public void testInitialClone_ViaHttps() throws Exception {
229 		Repository dst = createBareRepository();
230 		assertFalse(dst.getObjectDatabase().has(A_txt));
231 
232 		try (Transport t = Transport.open(dst, secureURI)) {
233 			t.setCredentialsProvider(testCredentials);
234 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
235 		}
236 		assertTrue(dst.getObjectDatabase().has(A_txt));
237 		assertEquals(B, dst.exactRef(master).getObjectId());
238 		fsck(dst, B);
239 
240 		List<AccessEvent> requests = getRequests();
241 		assertEquals(2, requests.size());
242 	}
243 
244 	@Test
245 	public void testInitialClone_RedirectToHttps() throws Exception {
246 		Repository dst = createBareRepository();
247 		assertFalse(dst.getObjectDatabase().has(A_txt));
248 
249 		URIish cloneFrom = extendPath(remoteURI, "/https");
250 		try (Transport t = Transport.open(dst, cloneFrom)) {
251 			t.setCredentialsProvider(testCredentials);
252 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
253 		}
254 		assertTrue(dst.getObjectDatabase().has(A_txt));
255 		assertEquals(B, dst.exactRef(master).getObjectId());
256 		fsck(dst, B);
257 
258 		List<AccessEvent> requests = getRequests();
259 		assertEquals(3, requests.size());
260 	}
261 
262 	@Test
263 	public void testInitialClone_RedirectBackToHttp() throws Exception {
264 		Repository dst = createBareRepository();
265 		assertFalse(dst.getObjectDatabase().has(A_txt));
266 
267 		URIish cloneFrom = extendPath(secureURI, "/back");
268 		try (Transport t = Transport.open(dst, cloneFrom)) {
269 			t.setCredentialsProvider(testCredentials);
270 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
271 			fail("Should have failed (redirect from https to http)");
272 		} catch (TransportException e) {
273 			assertTrue(e.getMessage().contains("not allowed"));
274 		}
275 	}
276 
277 	@Test
278 	public void testInitialClone_SslFailure() throws Exception {
279 		Repository dst = createBareRepository();
280 		assertFalse(dst.getObjectDatabase().has(A_txt));
281 
282 		try (Transport t = Transport.open(dst, secureURI)) {
283 			// Set a credentials provider that doesn't handle questions
284 			t.setCredentialsProvider(
285 					new UsernamePasswordCredentialsProvider("any", "anypwd"));
286 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
287 			fail("Should have failed (SSL certificate not trusted)");
288 		} catch (TransportException e) {
289 			assertTrue(e.getMessage().contains("Secure connection"));
290 		}
291 	}
292 
293 }