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