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 	static class TestMappedLoginService extends MappedLoginService {
172 		private String role;
173 
174 		TestMappedLoginService(String role) {
175 			this.role = role;
176 		}
177 
178 		@Override
179 		protected UserIdentity loadUser(String who) {
180 			return null;
181 		}
182 
183 		@Override
184 		protected void loadUsers() throws IOException {
185 			putUser(username, new Password(password), new String[] { role });
186 		}
187 
188 		protected String[] loadRoleInfo(KnownUser user) {
189 			return null;
190 		}
191 
192 		protected KnownUser loadUserInfo(String usrname) {
193 			return null;
194 		}
195 	}
196 
197 	private void auth(ServletContextHandler ctx, Authenticator authType) {
198 		final String role = "can-access";
199 
200 		MappedLoginService users = new TestMappedLoginService(role);
201 		ConstraintMapping cm = new ConstraintMapping();
202 		cm.setConstraint(new Constraint());
203 		cm.getConstraint().setAuthenticate(true);
204 		cm.getConstraint().setDataConstraint(Constraint.DC_NONE);
205 		cm.getConstraint().setRoles(new String[] { role });
206 		cm.setPathSpec("/*");
207 
208 		ConstraintSecurityHandler sec = new ConstraintSecurityHandler();
209 		sec.setRealmName(realm);
210 		sec.setAuthenticator(authType);
211 		sec.setLoginService(users);
212 		sec.setConstraintMappings(new ConstraintMapping[] { cm });
213 		sec.setHandler(ctx);
214 
215 		contexts.removeHandler(ctx);
216 		contexts.addHandler(sec);
217 	}
218 
219 	/**
220 	 * Start the server on a random local port.
221 	 *
222 	 * @throws Exception
223 	 *             the server cannot be started, testing is not possible.
224 	 */
225 	public void setUp() throws Exception {
226 		RecordingLogger.clear();
227 		log.clear();
228 		server.start();
229 	}
230 
231 	/**
232 	 * Shutdown the server.
233 	 *
234 	 * @throws Exception
235 	 *             the server refuses to halt, or wasn't running.
236 	 */
237 	public void tearDown() throws Exception {
238 		RecordingLogger.clear();
239 		log.clear();
240 		server.stop();
241 	}
242 
243 	/**
244 	 * Get the URI to reference this server.
245 	 * <p>
246 	 * The returned URI includes the proper host name and port number, but does
247 	 * not contain a path.
248 	 *
249 	 * @return URI to reference this server's root context.
250 	 */
251 	public URI getURI() {
252 		assertAlreadySetUp();
253 		String host = connector.getHost();
254 		if (host.contains(":") && !host.startsWith("["))
255 			host = "[" + host + "]";
256 		final String uri = "http://" + host + ":" + getPort();
257 		try {
258 			return new URI(uri);
259 		} catch (URISyntaxException e) {
260 			throw new RuntimeException("Unexpected URI error on " + uri, e);
261 		}
262 	}
263 
264 	/** @return the local port number the server is listening on. */
265 	public int getPort() {
266 		assertAlreadySetUp();
267 		return connector.getLocalPort();
268 	}
269 
270 	/** @return all requests since the server was started. */
271 	public List<AccessEvent> getRequests() {
272 		return new ArrayList<>(log.getEvents());
273 	}
274 
275 	/**
276 	 * @param base
277 	 *            base URI used to access the server.
278 	 * @param path
279 	 *            the path to locate requests for, relative to {@code base}.
280 	 * @return all requests which match the given path.
281 	 */
282 	public List<AccessEvent> getRequests(URIish base, String path) {
283 		return getRequests(HttpTestCase.join(base, path));
284 	}
285 
286 	/**
287 	 * @param path
288 	 *            the path to locate requests for.
289 	 * @return all requests which match the given path.
290 	 */
291 	public List<AccessEvent> getRequests(String path) {
292 		ArrayList<AccessEvent> r = new ArrayList<>();
293 		for (AccessEvent event : log.getEvents()) {
294 			if (event.getPath().equals(path)) {
295 				r.add(event);
296 			}
297 		}
298 		return r;
299 	}
300 
301 	private void assertNotYetSetUp() {
302 		assertFalse("server is not running", server.isRunning());
303 	}
304 
305 	private void assertAlreadySetUp() {
306 		assertTrue("server is running", server.isRunning());
307 	}
308 }