View Javadoc
1   /*
2    * Copyright (C) 2010, 2012 Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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.IOException;
50  import java.net.InetAddress;
51  import java.net.URI;
52  import java.net.URISyntaxException;
53  import java.net.UnknownHostException;
54  import java.util.ArrayList;
55  import java.util.List;
56  
57  import org.eclipse.jetty.security.Authenticator;
58  import org.eclipse.jetty.security.ConstraintMapping;
59  import org.eclipse.jetty.security.ConstraintSecurityHandler;
60  import org.eclipse.jetty.security.MappedLoginService;
61  import org.eclipse.jetty.security.authentication.BasicAuthenticator;
62  import org.eclipse.jetty.server.Connector;
63  import org.eclipse.jetty.server.HttpConfiguration;
64  import org.eclipse.jetty.server.HttpConnectionFactory;
65  import org.eclipse.jetty.server.Server;
66  import org.eclipse.jetty.server.ServerConnector;
67  import org.eclipse.jetty.server.UserIdentity;
68  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
69  import org.eclipse.jetty.servlet.ServletContextHandler;
70  import org.eclipse.jetty.util.security.Constraint;
71  import org.eclipse.jetty.util.security.Password;
72  import org.eclipse.jgit.transport.URIish;
73  
74  /**
75   * Tiny web application server for unit testing.
76   * <p>
77   * Tests should start the server in their {@code setUp()} method and stop the
78   * server in their {@code tearDown()} method. Only while started the server's
79   * URL and/or port number can be obtained.
80   */
81  public class AppServer {
82  	/** Realm name for the secure access areas. */
83  	public static final String realm = "Secure Area";
84  
85  	/** Username for secured access areas. */
86  	public static final String username = "agitter";
87  
88  	/** Password for {@link #username} in secured access areas. */
89  	public static final String password = "letmein";
90  
91  	static {
92  		// Install a logger that throws warning messages.
93  		//
94  		final String prop = "org.eclipse.jetty.util.log.class";
95  		System.setProperty(prop, RecordingLogger.class.getName());
96  	}
97  
98  	private final Server server;
99  
100 	private final ServerConnector connector;
101 
102 	private final ContextHandlerCollection contexts;
103 
104 	private final TestRequestLog log;
105 
106 	public AppServer() {
107 		this(0);
108 	}
109 
110 	/**
111 	 * @param port
112 	 *            the http port number
113 	 * @since 4.2
114 	 */
115 	public AppServer(int port) {
116 		server = new Server();
117 
118 		HttpConfiguration http_config = new HttpConfiguration();
119 		http_config.setSecureScheme("https");
120 		http_config.setSecurePort(8443);
121 		http_config.setOutputBufferSize(32768);
122 
123 		connector = new ServerConnector(server,
124 				new HttpConnectionFactory(http_config));
125 		connector.setPort(port);
126 		try {
127 			final InetAddress me = InetAddress.getByName("localhost");
128 			connector.setHost(me.getHostAddress());
129 		} catch (UnknownHostException e) {
130 			throw new RuntimeException("Cannot find localhost", e);
131 		}
132 
133 		contexts = new ContextHandlerCollection();
134 
135 		log = new TestRequestLog();
136 		log.setHandler(contexts);
137 
138 		server.setConnectors(new Connector[] { connector });
139 		server.setHandler(log);
140 	}
141 
142 	/**
143 	 * Create a new servlet context within the server.
144 	 * <p>
145 	 * This method should be invoked before the server is started, once for each
146 	 * context the caller wants to register.
147 	 *
148 	 * @param path
149 	 *            path of the context; use "/" for the root context if binding
150 	 *            to the root is desired.
151 	 * @return the context to add servlets into.
152 	 */
153 	public ServletContextHandler addContext(String path) {
154 		assertNotYetSetUp();
155 		if ("".equals(path))
156 			path = "/";
157 
158 		ServletContextHandler ctx = new ServletContextHandler();
159 		ctx.setContextPath(path);
160 		contexts.addHandler(ctx);
161 
162 		return ctx;
163 	}
164 
165 	public ServletContextHandler authBasic(ServletContextHandler ctx) {
166 		assertNotYetSetUp();
167 		auth(ctx, new BasicAuthenticator());
168 		return ctx;
169 	}
170 
171 	private void auth(ServletContextHandler ctx, Authenticator authType) {
172 		final String role = "can-access";
173 
174 		MappedLoginService users = new MappedLoginService() {
175 			@Override
176 			protected UserIdentity loadUser(String who) {
177 				return null;
178 			}
179 
180 			@Override
181 			protected void loadUsers() throws IOException {
182 				putUser(username, new Password(password), new String[] { role });
183 			}
184 		};
185 
186 		ConstraintMapping cm = new ConstraintMapping();
187 		cm.setConstraint(new Constraint());
188 		cm.getConstraint().setAuthenticate(true);
189 		cm.getConstraint().setDataConstraint(Constraint.DC_NONE);
190 		cm.getConstraint().setRoles(new String[] { role });
191 		cm.setPathSpec("/*");
192 
193 		ConstraintSecurityHandler sec = new ConstraintSecurityHandler();
194 		sec.setRealmName(realm);
195 		sec.setAuthenticator(authType);
196 		sec.setLoginService(users);
197 		sec.setConstraintMappings(new ConstraintMapping[] { cm });
198 		sec.setHandler(ctx);
199 
200 		contexts.removeHandler(ctx);
201 		contexts.addHandler(sec);
202 	}
203 
204 	/**
205 	 * Start the server on a random local port.
206 	 *
207 	 * @throws Exception
208 	 *             the server cannot be started, testing is not possible.
209 	 */
210 	public void setUp() throws Exception {
211 		RecordingLogger.clear();
212 		log.clear();
213 		server.start();
214 	}
215 
216 	/**
217 	 * Shutdown the server.
218 	 *
219 	 * @throws Exception
220 	 *             the server refuses to halt, or wasn't running.
221 	 */
222 	public void tearDown() throws Exception {
223 		RecordingLogger.clear();
224 		log.clear();
225 		server.stop();
226 	}
227 
228 	/**
229 	 * Get the URI to reference this server.
230 	 * <p>
231 	 * The returned URI includes the proper host name and port number, but does
232 	 * not contain a path.
233 	 *
234 	 * @return URI to reference this server's root context.
235 	 */
236 	public URI getURI() {
237 		assertAlreadySetUp();
238 		String host = connector.getHost();
239 		if (host.contains(":") && !host.startsWith("["))
240 			host = "[" + host + "]";
241 		final String uri = "http://" + host + ":" + getPort();
242 		try {
243 			return new URI(uri);
244 		} catch (URISyntaxException e) {
245 			throw new RuntimeException("Unexpected URI error on " + uri, e);
246 		}
247 	}
248 
249 	/** @return the local port number the server is listening on. */
250 	public int getPort() {
251 		assertAlreadySetUp();
252 		return connector.getLocalPort();
253 	}
254 
255 	/** @return all requests since the server was started. */
256 	public List<AccessEvent> getRequests() {
257 		return new ArrayList<AccessEvent>(log.getEvents());
258 	}
259 
260 	/**
261 	 * @param base
262 	 *            base URI used to access the server.
263 	 * @param path
264 	 *            the path to locate requests for, relative to {@code base}.
265 	 * @return all requests which match the given path.
266 	 */
267 	public List<AccessEvent> getRequests(URIish base, String path) {
268 		return getRequests(HttpTestCase.join(base, path));
269 	}
270 
271 	/**
272 	 * @param path
273 	 *            the path to locate requests for.
274 	 * @return all requests which match the given path.
275 	 */
276 	public List<AccessEvent> getRequests(String path) {
277 		ArrayList<AccessEvent> r = new ArrayList<AccessEvent>();
278 		for (AccessEvent event : log.getEvents()) {
279 			if (event.getPath().equals(path)) {
280 				r.add(event);
281 			}
282 		}
283 		return r;
284 	}
285 
286 	private void assertNotYetSetUp() {
287 		assertFalse("server is not running", server.isRunning());
288 	}
289 
290 	private void assertAlreadySetUp() {
291 		assertTrue("server is running", server.isRunning());
292 	}
293 }