1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.junit.http;
12
13 import static org.junit.Assert.assertFalse;
14 import static org.junit.Assert.assertTrue;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.net.InetAddress;
19 import java.net.URI;
20 import java.net.URISyntaxException;
21 import java.net.UnknownHostException;
22 import java.nio.file.Files;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.concurrent.ConcurrentHashMap;
29
30 import org.eclipse.jetty.security.AbstractLoginService;
31 import org.eclipse.jetty.security.Authenticator;
32 import org.eclipse.jetty.security.ConstraintMapping;
33 import org.eclipse.jetty.security.ConstraintSecurityHandler;
34 import org.eclipse.jetty.security.RolePrincipal;
35 import org.eclipse.jetty.security.UserPrincipal;
36 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
37 import org.eclipse.jetty.server.Connector;
38 import org.eclipse.jetty.server.HttpConfiguration;
39 import org.eclipse.jetty.server.HttpConnectionFactory;
40 import org.eclipse.jetty.server.SecureRequestCustomizer;
41 import org.eclipse.jetty.server.Server;
42 import org.eclipse.jetty.server.ServerConnector;
43 import org.eclipse.jetty.server.SslConnectionFactory;
44 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
45 import org.eclipse.jetty.servlet.ServletContextHandler;
46 import org.eclipse.jetty.util.security.Constraint;
47 import org.eclipse.jetty.util.security.Password;
48 import org.eclipse.jetty.util.ssl.SslContextFactory;
49 import org.eclipse.jgit.transport.URIish;
50
51
52
53
54
55
56
57
58 public class AppServer {
59
60 public static final String realm = "Secure Area";
61
62
63 public static final String username = "agitter";
64
65
66 public static final String password = "letmein";
67
68
69 private static final String keyPassword = "mykeys";
70
71
72 private static final String authRole = "can-access";
73
74 static {
75
76
77 final String prop = "org.eclipse.jetty.util.log.class";
78 System.setProperty(prop, RecordingLogger.class.getName());
79 }
80
81 private final Server server;
82
83 private final HttpConfiguration config;
84
85 private final ServerConnector connector;
86
87 private final HttpConfiguration secureConfig;
88
89 private final ServerConnector secureConnector;
90
91 private final ContextHandlerCollection contexts;
92
93 private final TestRequestLog log;
94
95 private List<File> filesToDelete = new ArrayList<>();
96
97
98
99
100 public AppServer() {
101 this(0, -1);
102 }
103
104
105
106
107
108
109
110
111
112 public AppServer(int port) {
113 this(port, -1);
114 }
115
116
117
118
119
120
121
122
123
124
125
126 public AppServer(int port, int sslPort) {
127 server = new Server();
128
129 config = new HttpConfiguration();
130 config.setSecureScheme("https");
131 config.setSecurePort(0);
132 config.setOutputBufferSize(32768);
133
134 connector = new ServerConnector(server,
135 new HttpConnectionFactory(config));
136 connector.setPort(port);
137 String ip;
138 String hostName;
139 try {
140 final InetAddress me = InetAddress.getByName("localhost");
141 ip = me.getHostAddress();
142 connector.setHost(ip);
143 hostName = InetAddress.getLocalHost().getCanonicalHostName();
144 } catch (UnknownHostException e) {
145 throw new RuntimeException("Cannot find localhost", e);
146 }
147
148 if (sslPort >= 0) {
149 SslContextFactory.Server sslContextFactory = createTestSslContextFactory(
150 hostName, ip);
151 secureConfig = new HttpConfiguration(config);
152 secureConfig.addCustomizer(new SecureRequestCustomizer());
153 HttpConnectionFactory http11 = new HttpConnectionFactory(
154 secureConfig);
155 SslConnectionFactory tls = new SslConnectionFactory(
156 sslContextFactory, http11.getProtocol());
157 secureConnector = new ServerConnector(server, tls, http11);
158 secureConnector.setPort(sslPort);
159 secureConnector.setHost(ip);
160 } else {
161 secureConfig = null;
162 secureConnector = null;
163 }
164
165 contexts = new ContextHandlerCollection();
166
167 log = new TestRequestLog();
168 log.setHandler(contexts);
169
170 if (secureConnector == null) {
171 server.setConnectors(new Connector[] { connector });
172 } else {
173 server.setConnectors(
174 new Connector[] { connector, secureConnector });
175 }
176 server.setHandler(log);
177 }
178
179 private SslContextFactory.Server createTestSslContextFactory(
180 String hostName, String ip) {
181 SslContextFactory.Server factory = new SslContextFactory.Server();
182
183 String dName = "CN=localhost,OU=JGit,O=Eclipse,ST=Ontario,L=Toronto,C=CA";
184
185 try {
186 File tmpDir = Files.createTempDirectory("jks").toFile();
187 tmpDir.deleteOnExit();
188 makePrivate(tmpDir);
189 File keyStore = new File(tmpDir, "keystore.jks");
190 File keytool = new File(
191 new File(new File(System.getProperty("java.home")), "bin"),
192 "keytool");
193 Runtime.getRuntime().exec(
194 new String[] {
195 keytool.getAbsolutePath(),
196 "-keystore", keyStore.getAbsolutePath(),
197 "-storepass", keyPassword,
198 "-alias", hostName,
199 "-ext", "bc=ca:true",
200 "-ext",
201 String.format(
202 "san=ip:%s,ip:127.0.0.1,ip:[::1],DNS:%s",
203 ip, hostName),
204 "-genkeypair",
205 "-keyalg", "RSA",
206 "-keypass", keyPassword,
207 "-dname", dName,
208 "-validity", "2"
209 }).waitFor();
210 keyStore.deleteOnExit();
211 makePrivate(keyStore);
212 filesToDelete.add(keyStore);
213 filesToDelete.add(tmpDir);
214 factory.setKeyStorePath(keyStore.getAbsolutePath());
215 factory.setKeyStorePassword(keyPassword);
216 factory.setKeyManagerPassword(keyPassword);
217 factory.setTrustStorePath(keyStore.getAbsolutePath());
218 factory.setTrustStorePassword(keyPassword);
219 } catch (InterruptedException | IOException e) {
220 throw new RuntimeException("Cannot create ssl key/certificate", e);
221 }
222 return factory;
223 }
224
225 private void makePrivate(File file) {
226 file.setReadable(false);
227 file.setWritable(false);
228 file.setExecutable(false);
229 file.setReadable(true, true);
230 file.setWritable(true, true);
231 if (file.isDirectory()) {
232 file.setExecutable(true, true);
233 }
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247 public ServletContextHandler addContext(String path) {
248 assertNotYetSetUp();
249 if ("".equals(path))
250 path = "/";
251
252 ServletContextHandler ctx = new ServletContextHandler();
253 ctx.setContextPath(path);
254 contexts.addHandler(ctx);
255
256 return ctx;
257 }
258
259
260
261
262
263
264
265
266 public ServletContextHandler authBasic(ServletContextHandler ctx,
267 String... methods) {
268 assertNotYetSetUp();
269 auth(ctx, new BasicAuthenticator(), methods);
270 return ctx;
271 }
272
273 static class TestMappedLoginService extends AbstractLoginService {
274 private RolePrincipal role;
275
276 protected final Map<String, UserPrincipal> users = new ConcurrentHashMap<>();
277
278 TestMappedLoginService(String role) {
279 this.role = new RolePrincipal(role);
280 }
281
282 @Override
283 protected void doStart() throws Exception {
284 UserPrincipal p = new UserPrincipal(username,
285 new Password(password));
286 users.put(username, p);
287 super.doStart();
288 }
289
290 @Override
291 protected UserPrincipal loadUserInfo(String user) {
292 return users.get(user);
293 }
294
295 @Override
296 protected List<RolePrincipal> loadRoleInfo(UserPrincipal user) {
297 if (users.get(user.getName()) == null) {
298 return null;
299 }
300 return Collections.singletonList(role);
301 }
302 }
303
304 private ConstraintMapping createConstraintMapping() {
305 ConstraintMapping cm = new ConstraintMapping();
306 cm.setConstraint(new Constraint());
307 cm.getConstraint().setAuthenticate(true);
308 cm.getConstraint().setDataConstraint(Constraint.DC_NONE);
309 cm.getConstraint().setRoles(new String[] { authRole });
310 cm.setPathSpec("/*");
311 return cm;
312 }
313
314 private void auth(ServletContextHandler ctx, Authenticator authType,
315 String... methods) {
316 AbstractLoginService users = new TestMappedLoginService(authRole);
317 List<ConstraintMapping> mappings = new ArrayList<>();
318 if (methods == null || methods.length == 0) {
319 mappings.add(createConstraintMapping());
320 } else {
321 for (String method : methods) {
322 ConstraintMapping cm = createConstraintMapping();
323 cm.setMethod(method.toUpperCase(Locale.ROOT));
324 mappings.add(cm);
325 }
326 }
327
328 ConstraintSecurityHandler sec = new ConstraintSecurityHandler();
329 sec.setRealmName(realm);
330 sec.setAuthenticator(authType);
331 sec.setLoginService(users);
332 sec.setConstraintMappings(
333 mappings.toArray(new ConstraintMapping[0]));
334 sec.setHandler(ctx);
335
336 contexts.removeHandler(ctx);
337 contexts.addHandler(sec);
338 }
339
340
341
342
343
344
345
346 public void setUp() throws Exception {
347 RecordingLogger.clear();
348 log.clear();
349 server.start();
350 config.setSecurePort(getSecurePort());
351 if (secureConfig != null) {
352 secureConfig.setSecurePort(getSecurePort());
353 }
354 }
355
356
357
358
359
360
361
362 public void tearDown() throws Exception {
363 RecordingLogger.clear();
364 log.clear();
365 server.stop();
366 for (File f : filesToDelete) {
367 f.delete();
368 }
369 filesToDelete.clear();
370 }
371
372
373
374
375
376
377
378
379
380 public URI getURI() {
381 assertAlreadySetUp();
382 String host = connector.getHost();
383 if (host.contains(":") && !host.startsWith("["))
384 host = "[" + host + "]";
385 final String uri = "http://" + host + ":" + getPort();
386 try {
387 return new URI(uri);
388 } catch (URISyntaxException e) {
389 throw new RuntimeException("Unexpected URI error on " + uri, e);
390 }
391 }
392
393
394
395
396
397
398 public int getPort() {
399 assertAlreadySetUp();
400 return connector.getLocalPort();
401 }
402
403
404
405
406
407
408 public int getSecurePort() {
409 assertAlreadySetUp();
410 return secureConnector != null ? secureConnector.getLocalPort() : -1;
411 }
412
413
414
415
416
417
418 public List<AccessEvent> getRequests() {
419 return new ArrayList<>(log.getEvents());
420 }
421
422
423
424
425
426
427
428
429
430
431 public List<AccessEvent> getRequests(URIish base, String path) {
432 return getRequests(HttpTestCase.join(base, path));
433 }
434
435
436
437
438
439
440
441
442 public List<AccessEvent> getRequests(String path) {
443 ArrayList<AccessEvent> r = new ArrayList<>();
444 for (AccessEvent event : log.getEvents()) {
445 if (event.getPath().equals(path)) {
446 r.add(event);
447 }
448 }
449 return r;
450 }
451
452 private void assertNotYetSetUp() {
453 assertFalse("server is not running", server.isRunning());
454 }
455
456 private void assertAlreadySetUp() {
457 assertTrue("server is running", server.isRunning());
458 }
459 }