001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.http; 019 020import java.io.IOException; 021import java.io.PrintWriter; 022import java.net.HttpURLConnection; 023import java.net.URI; 024import java.net.URL; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.SortedSet; 032import java.util.TreeSet; 033import java.util.concurrent.CountDownLatch; 034import java.util.concurrent.Executor; 035import java.util.concurrent.Executors; 036import javax.servlet.Filter; 037import javax.servlet.FilterChain; 038import javax.servlet.FilterConfig; 039import javax.servlet.ServletContext; 040import javax.servlet.ServletException; 041import javax.servlet.ServletRequest; 042import javax.servlet.ServletResponse; 043import javax.servlet.http.HttpServlet; 044import javax.servlet.http.HttpServletRequest; 045import javax.servlet.http.HttpServletRequestWrapper; 046import javax.servlet.http.HttpServletResponse; 047import org.apache.hadoop.conf.Configuration; 048import org.apache.hadoop.fs.CommonConfigurationKeys; 049import org.apache.hadoop.hbase.HBaseClassTestRule; 050import org.apache.hadoop.hbase.http.HttpServer.QuotingInputFilter.RequestQuoter; 051import org.apache.hadoop.hbase.http.resource.JerseyResource; 052import org.apache.hadoop.hbase.testclassification.MiscTests; 053import org.apache.hadoop.hbase.testclassification.SmallTests; 054import org.apache.hadoop.net.NetUtils; 055import org.apache.hadoop.security.Groups; 056import org.apache.hadoop.security.ShellBasedUnixGroupsMapping; 057import org.apache.hadoop.security.UserGroupInformation; 058import org.apache.hadoop.security.authorize.AccessControlList; 059import org.eclipse.jetty.server.ServerConnector; 060import org.eclipse.jetty.util.ajax.JSON; 061import org.junit.AfterClass; 062import org.junit.Assert; 063import org.junit.BeforeClass; 064import org.junit.ClassRule; 065import org.junit.Ignore; 066import org.junit.Test; 067import org.junit.experimental.categories.Category; 068import org.mockito.Mockito; 069import org.slf4j.Logger; 070import org.slf4j.LoggerFactory; 071 072@Category({MiscTests.class, SmallTests.class}) 073public class TestHttpServer extends HttpServerFunctionalTest { 074 @ClassRule 075 public static final HBaseClassTestRule CLASS_RULE = 076 HBaseClassTestRule.forClass(TestHttpServer.class); 077 078 private static final Logger LOG = LoggerFactory.getLogger(TestHttpServer.class); 079 private static HttpServer server; 080 private static URL baseUrl; 081 // jetty 9.4.x needs this many threads to start, even in the small. 082 static final int MAX_THREADS = 16; 083 084 @SuppressWarnings("serial") 085 public static class EchoMapServlet extends HttpServlet { 086 @Override 087 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 088 PrintWriter out = response.getWriter(); 089 Map<String, String[]> params = request.getParameterMap(); 090 SortedSet<String> keys = new TreeSet<>(params.keySet()); 091 for(String key: keys) { 092 out.print(key); 093 out.print(':'); 094 String[] values = params.get(key); 095 if (values.length > 0) { 096 out.print(values[0]); 097 for(int i=1; i < values.length; ++i) { 098 out.print(','); 099 out.print(values[i]); 100 } 101 } 102 out.print('\n'); 103 } 104 out.close(); 105 } 106 } 107 108 @SuppressWarnings("serial") 109 public static class EchoServlet extends HttpServlet { 110 @Override 111 public void doGet(HttpServletRequest request, HttpServletResponse response) 112 throws IOException { 113 PrintWriter out = response.getWriter(); 114 SortedSet<String> sortedKeys = new TreeSet<>(); 115 Enumeration<String> keys = request.getParameterNames(); 116 while(keys.hasMoreElements()) { 117 sortedKeys.add(keys.nextElement()); 118 } 119 for(String key: sortedKeys) { 120 out.print(key); 121 out.print(':'); 122 out.print(request.getParameter(key)); 123 out.print('\n'); 124 } 125 out.close(); 126 } 127 } 128 129 @SuppressWarnings("serial") 130 public static class LongHeaderServlet extends HttpServlet { 131 @Override 132 public void doGet(HttpServletRequest request, HttpServletResponse response) { 133 Assert.assertEquals(63 * 1024, request.getHeader("longheader").length()); 134 response.setStatus(HttpServletResponse.SC_OK); 135 } 136 } 137 138 @SuppressWarnings("serial") 139 public static class HtmlContentServlet extends HttpServlet { 140 @Override 141 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 142 response.setContentType("text/html"); 143 PrintWriter out = response.getWriter(); 144 out.print("hello world"); 145 out.close(); 146 } 147 } 148 149 @BeforeClass public static void setup() throws Exception { 150 Configuration conf = new Configuration(); 151 conf.setInt(HttpServer.HTTP_MAX_THREADS, MAX_THREADS); 152 server = createTestServer(conf); 153 server.addUnprivilegedServlet("echo", "/echo", EchoServlet.class); 154 server.addUnprivilegedServlet("echomap", "/echomap", EchoMapServlet.class); 155 server.addUnprivilegedServlet("htmlcontent", "/htmlcontent", HtmlContentServlet.class); 156 server.addUnprivilegedServlet("longheader", "/longheader", LongHeaderServlet.class); 157 server.addJerseyResourcePackage( 158 JerseyResource.class.getPackage().getName(), "/jersey/*"); 159 server.start(); 160 baseUrl = getServerURL(server); 161 LOG.info("HTTP server started: "+ baseUrl); 162 } 163 164 @AfterClass public static void cleanup() throws Exception { 165 server.stop(); 166 } 167 168 /** 169 * Test the maximum number of threads cannot be exceeded. 170 */ 171 @Test 172 public void testMaxThreads() throws Exception { 173 int clientThreads = MAX_THREADS * 10; 174 Executor executor = Executors.newFixedThreadPool(clientThreads); 175 // Run many clients to make server reach its maximum number of threads 176 final CountDownLatch ready = new CountDownLatch(clientThreads); 177 final CountDownLatch start = new CountDownLatch(1); 178 for (int i = 0; i < clientThreads; i++) { 179 executor.execute(() -> { 180 ready.countDown(); 181 try { 182 start.await(); 183 assertEquals("a:b\nc:d\n", 184 readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); 185 int serverThreads = server.webServer.getThreadPool().getThreads(); 186 assertTrue("More threads are started than expected, Server Threads count: " 187 + serverThreads, serverThreads <= MAX_THREADS); 188 LOG.info("Number of threads = " + serverThreads + 189 " which is less or equal than the max = " + MAX_THREADS); 190 } catch (Exception e) { 191 // do nothing 192 } 193 }); 194 } 195 // Start the client threads when they are all ready 196 ready.await(); 197 start.countDown(); 198 } 199 200 @Test public void testEcho() throws Exception { 201 assertEquals("a:b\nc:d\n", 202 readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); 203 assertEquals("a:b\nc<:d\ne:>\n", 204 readOutput(new URL(baseUrl, "/echo?a=b&c<=d&e=>"))); 205 } 206 207 /** Test the echo map servlet that uses getParameterMap. */ 208 @Test public void testEchoMap() throws Exception { 209 assertEquals("a:b\nc:d\n", 210 readOutput(new URL(baseUrl, "/echomap?a=b&c=d"))); 211 assertEquals("a:b,>\nc<:d\n", 212 readOutput(new URL(baseUrl, "/echomap?a=b&c<=d&a=>"))); 213 } 214 215 /** 216 * Test that verifies headers can be up to 64K long. 217 * The test adds a 63K header leaving 1K for other headers. 218 * This is because the header buffer setting is for ALL headers, 219 * names and values included. */ 220 @Test public void testLongHeader() throws Exception { 221 URL url = new URL(baseUrl, "/longheader"); 222 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 223 StringBuilder sb = new StringBuilder(); 224 for (int i = 0 ; i < 63 * 1024; i++) { 225 sb.append("a"); 226 } 227 conn.setRequestProperty("longheader", sb.toString()); 228 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 229 } 230 231 @Test 232 public void testContentTypes() throws Exception { 233 // Static CSS files should have text/css 234 URL cssUrl = new URL(baseUrl, "/static/test.css"); 235 HttpURLConnection conn = (HttpURLConnection)cssUrl.openConnection(); 236 conn.connect(); 237 assertEquals(200, conn.getResponseCode()); 238 assertEquals("text/css", conn.getContentType()); 239 240 // Servlets should have text/plain with proper encoding by default 241 URL servletUrl = new URL(baseUrl, "/echo?a=b"); 242 conn = (HttpURLConnection)servletUrl.openConnection(); 243 conn.connect(); 244 assertEquals(200, conn.getResponseCode()); 245 assertEquals("text/plain;charset=utf-8", conn.getContentType()); 246 247 // We should ignore parameters for mime types - ie a parameter 248 // ending in .css should not change mime type 249 servletUrl = new URL(baseUrl, "/echo?a=b.css"); 250 conn = (HttpURLConnection)servletUrl.openConnection(); 251 conn.connect(); 252 assertEquals(200, conn.getResponseCode()); 253 assertEquals("text/plain;charset=utf-8", conn.getContentType()); 254 255 // Servlets that specify text/html should get that content type 256 servletUrl = new URL(baseUrl, "/htmlcontent"); 257 conn = (HttpURLConnection)servletUrl.openConnection(); 258 conn.connect(); 259 assertEquals(200, conn.getResponseCode()); 260 assertEquals("text/html;charset=utf-8", conn.getContentType()); 261 262 // JSPs should default to text/html with utf8 263 // JSPs do not work from unit tests 264 // servletUrl = new URL(baseUrl, "/testjsp.jsp"); 265 // conn = (HttpURLConnection)servletUrl.openConnection(); 266 // conn.connect(); 267 // assertEquals(200, conn.getResponseCode()); 268 // assertEquals("text/html; charset=utf-8", conn.getContentType()); 269 } 270 271 /** 272 * Dummy filter that mimics as an authentication filter. Obtains user identity 273 * from the request parameter user.name. Wraps around the request so that 274 * request.getRemoteUser() returns the user identity. 275 * 276 */ 277 public static class DummyServletFilter implements Filter { 278 @Override 279 public void destroy() { } 280 281 @Override 282 public void doFilter(ServletRequest request, ServletResponse response, 283 FilterChain filterChain) throws IOException, ServletException { 284 final String userName = request.getParameter("user.name"); 285 ServletRequest requestModified = 286 new HttpServletRequestWrapper((HttpServletRequest) request) { 287 @Override 288 public String getRemoteUser() { 289 return userName; 290 } 291 }; 292 filterChain.doFilter(requestModified, response); 293 } 294 295 @Override 296 public void init(FilterConfig arg0) { } 297 } 298 299 /** 300 * FilterInitializer that initialized the DummyFilter. 301 * 302 */ 303 public static class DummyFilterInitializer extends FilterInitializer { 304 public DummyFilterInitializer() { 305 } 306 307 @Override 308 public void initFilter(FilterContainer container, Configuration conf) { 309 container.addFilter("DummyFilter", DummyServletFilter.class.getName(), null); 310 } 311 } 312 313 /** 314 * Access a URL and get the corresponding return Http status code. The URL 315 * will be accessed as the passed user, by sending user.name request 316 * parameter. 317 * 318 * @param urlstring The url to access 319 * @param userName The user to perform access as 320 * @return The HTTP response code 321 * @throws IOException if there is a problem communicating with the server 322 */ 323 private static int getHttpStatusCode(String urlstring, String userName) throws IOException { 324 URL url = new URL(urlstring + "?user.name=" + userName); 325 System.out.println("Accessing " + url + " as user " + userName); 326 HttpURLConnection connection = (HttpURLConnection)url.openConnection(); 327 connection.connect(); 328 return connection.getResponseCode(); 329 } 330 331 /** 332 * Custom user->group mapping service. 333 */ 334 public static class MyGroupsProvider extends ShellBasedUnixGroupsMapping { 335 static Map<String, List<String>> mapping = new HashMap<>(); 336 337 static void clearMapping() { 338 mapping.clear(); 339 } 340 341 @Override 342 public List<String> getGroups(String user) { 343 return mapping.get(user); 344 } 345 } 346 347 /** 348 * Verify the access for /logs, /stacks, /conf, /logLevel and /metrics 349 * servlets, when authentication filters are set, but authorization is not 350 * enabled. 351 */ 352 @Test 353 @Ignore 354 public void testDisabledAuthorizationOfDefaultServlets() throws Exception { 355 Configuration conf = new Configuration(); 356 357 // Authorization is disabled by default 358 conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, 359 DummyFilterInitializer.class.getName()); 360 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, 361 MyGroupsProvider.class.getName()); 362 Groups.getUserToGroupsMappingService(conf); 363 MyGroupsProvider.clearMapping(); 364 MyGroupsProvider.mapping.put("userA", Collections.singletonList("groupA")); 365 MyGroupsProvider.mapping.put("userB", Collections.singletonList("groupB")); 366 367 HttpServer myServer = new HttpServer.Builder().setName("test") 368 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).build(); 369 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 370 myServer.start(); 371 String serverURL = "http://" + NetUtils.getHostPortString(myServer.getConnectorAddress(0)) + "/"; 372 for (String servlet : new String[] { "conf", "logs", "stacks", "logLevel", "metrics" }) { 373 for (String user : new String[] { "userA", "userB" }) { 374 assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL 375 + servlet, user)); 376 } 377 } 378 myServer.stop(); 379 } 380 381 /** 382 * Verify the administrator access for /logs, /stacks, /conf, /logLevel and 383 * /metrics servlets. 384 */ 385 @Test 386 @Ignore 387 public void testAuthorizationOfDefaultServlets() throws Exception { 388 Configuration conf = new Configuration(); 389 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, 390 true); 391 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, 392 true); 393 conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, 394 DummyFilterInitializer.class.getName()); 395 396 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, 397 MyGroupsProvider.class.getName()); 398 Groups.getUserToGroupsMappingService(conf); 399 MyGroupsProvider.clearMapping(); 400 MyGroupsProvider.mapping.put("userA", Collections.singletonList("groupA")); 401 MyGroupsProvider.mapping.put("userB", Collections.singletonList("groupB")); 402 MyGroupsProvider.mapping.put("userC", Collections.singletonList("groupC")); 403 MyGroupsProvider.mapping.put("userD", Collections.singletonList("groupD")); 404 MyGroupsProvider.mapping.put("userE", Collections.singletonList("groupE")); 405 406 HttpServer myServer = new HttpServer.Builder().setName("test") 407 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).setConf(conf) 408 .setACL(new AccessControlList("userA,userB groupC,groupD")).build(); 409 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 410 myServer.start(); 411 412 String serverURL = "http://" 413 + NetUtils.getHostPortString(myServer.getConnectorAddress(0)) + "/"; 414 for (String servlet : new String[] { "conf", "logs", "stacks", "logLevel", "metrics" }) { 415 for (String user : new String[] { "userA", "userB", "userC", "userD" }) { 416 assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL 417 + servlet, user)); 418 } 419 assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, getHttpStatusCode( 420 serverURL + servlet, "userE")); 421 } 422 myServer.stop(); 423 } 424 425 @Test 426 public void testRequestQuoterWithNull() { 427 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 428 Mockito.doReturn(null).when(request).getParameterValues("dummy"); 429 RequestQuoter requestQuoter = new RequestQuoter(request); 430 String[] parameterValues = requestQuoter.getParameterValues("dummy"); 431 Assert.assertNull("It should return null " 432 + "when there are no values for the parameter", parameterValues); 433 } 434 435 @Test 436 public void testRequestQuoterWithNotNull() { 437 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 438 String[] values = new String[] { "abc", "def" }; 439 Mockito.doReturn(values).when(request).getParameterValues("dummy"); 440 RequestQuoter requestQuoter = new RequestQuoter(request); 441 String[] parameterValues = requestQuoter.getParameterValues("dummy"); 442 Assert.assertTrue("It should return Parameter Values", Arrays.equals( 443 values, parameterValues)); 444 } 445 446 @SuppressWarnings("unchecked") 447 private static Map<String, Object> parse(String jsonString) { 448 return (Map<String, Object>)JSON.parse(jsonString); 449 } 450 451 @Test public void testJersey() throws Exception { 452 LOG.info("BEGIN testJersey()"); 453 final String js = readOutput(new URL(baseUrl, "/jersey/foo?op=bar")); 454 final Map<String, Object> m = parse(js); 455 LOG.info("m=" + m); 456 assertEquals("foo", m.get(JerseyResource.PATH)); 457 assertEquals("bar", m.get(JerseyResource.OP)); 458 LOG.info("END testJersey()"); 459 } 460 461 @Test 462 public void testHasAdministratorAccess() throws Exception { 463 Configuration conf = new Configuration(); 464 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false); 465 ServletContext context = Mockito.mock(ServletContext.class); 466 Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); 467 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(null); 468 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 469 Mockito.when(request.getRemoteUser()).thenReturn(null); 470 HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 471 472 //authorization OFF 473 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 474 475 //authorization ON & user NULL 476 response = Mockito.mock(HttpServletResponse.class); 477 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 478 Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); 479 Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), 480 Mockito.anyString()); 481 482 //authorization ON & user NOT NULL & ACLs NULL 483 response = Mockito.mock(HttpServletResponse.class); 484 Mockito.when(request.getRemoteUser()).thenReturn("foo"); 485 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 486 487 //authorization ON & user NOT NULL & ACLs NOT NULL & user not in ACLs 488 response = Mockito.mock(HttpServletResponse.class); 489 AccessControlList acls = Mockito.mock(AccessControlList.class); 490 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(false); 491 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 492 Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); 493 Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_FORBIDDEN), 494 Mockito.anyString()); 495 496 //authorization ON & user NOT NULL & ACLs NOT NULL & user in in ACLs 497 response = Mockito.mock(HttpServletResponse.class); 498 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(true); 499 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 500 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 501 502 } 503 504 @Test 505 public void testRequiresAuthorizationAccess() throws Exception { 506 Configuration conf = new Configuration(); 507 ServletContext context = Mockito.mock(ServletContext.class); 508 Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); 509 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 510 HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 511 512 //requires admin access to instrumentation, FALSE by default 513 Assert.assertTrue(HttpServer.isInstrumentationAccessAllowed(context, request, response)); 514 515 //requires admin access to instrumentation, TRUE 516 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, true); 517 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 518 AccessControlList acls = Mockito.mock(AccessControlList.class); 519 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(false); 520 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 521 Assert.assertFalse(HttpServer.isInstrumentationAccessAllowed(context, request, response)); 522 } 523 524 @Test 525 public void testBindAddress() throws Exception { 526 checkBindAddress("localhost", 0, false).stop(); 527 // hang onto this one for a bit more testing 528 HttpServer myServer = checkBindAddress("localhost", 0, false); 529 HttpServer myServer2 = null; 530 try { 531 int port = myServer.getConnectorAddress(0).getPort(); 532 // it's already in use, true = expect a higher port 533 myServer2 = checkBindAddress("localhost", port, true); 534 // try to reuse the port 535 port = myServer2.getConnectorAddress(0).getPort(); 536 myServer2.stop(); 537 assertNull(myServer2.getConnectorAddress(0)); // not bound 538 myServer2.openListeners(); 539 assertEquals(port, myServer2.getConnectorAddress(0).getPort()); // expect same port 540 } finally { 541 myServer.stop(); 542 if (myServer2 != null) { 543 myServer2.stop(); 544 } 545 } 546 } 547 548 private HttpServer checkBindAddress(String host, int port, boolean findPort) 549 throws Exception { 550 HttpServer server = createServer(host, port); 551 try { 552 // not bound, ephemeral should return requested port (0 for ephemeral) 553 ServerConnector listener = server.getServerConnectors().get(0); 554 555 assertEquals(port, listener.getPort()); 556 // verify hostname is what was given 557 server.openListeners(); 558 assertEquals(host, server.getConnectorAddress(0).getHostName()); 559 560 int boundPort = server.getConnectorAddress(0).getPort(); 561 if (port == 0) { 562 assertTrue(boundPort != 0); // ephemeral should now return bound port 563 } else if (findPort) { 564 assertTrue(boundPort > port); 565 // allow a little wiggle room to prevent random test failures if 566 // some consecutive ports are already in use 567 assertTrue(boundPort - port < 8); 568 } 569 } catch (Exception e) { 570 server.stop(); 571 throw e; 572 } 573 return server; 574 } 575 576 @Test 577 public void testXFrameHeaderSameOrigin() throws Exception { 578 Configuration conf = new Configuration(); 579 conf.set("hbase.http.filter.xframeoptions.mode", "SAMEORIGIN"); 580 581 HttpServer myServer = new HttpServer.Builder().setName("test") 582 .addEndpoint(new URI("http://localhost:0")) 583 .setFindPort(true).setConf(conf).build(); 584 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 585 myServer.addUnprivilegedServlet("echo", "/echo", EchoServlet.class); 586 myServer.start(); 587 588 String serverURL = "http://" 589 + NetUtils.getHostPortString(myServer.getConnectorAddress(0)); 590 URL url = new URL(new URL(serverURL), "/echo?a=b&c=d"); 591 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 592 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 593 assertEquals("SAMEORIGIN", conn.getHeaderField("X-Frame-Options")); 594 myServer.stop(); 595 } 596 597 @Test 598 public void testNoCacheHeader() throws Exception { 599 URL url = new URL(baseUrl, "/echo?a=b&c=d"); 600 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 601 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 602 assertEquals("no-cache", conn.getHeaderField("Cache-Control")); 603 assertEquals("no-cache", conn.getHeaderField("Pragma")); 604 assertNotNull(conn.getHeaderField("Expires")); 605 assertNotNull(conn.getHeaderField("Date")); 606 assertEquals(conn.getHeaderField("Expires"), conn.getHeaderField("Date")); 607 assertEquals("DENY", conn.getHeaderField("X-Frame-Options")); 608 } 609 610 @Test 611 public void testHttpMethods() throws Exception { 612 // HTTP TRACE method should be disabled for security 613 // See https://www.owasp.org/index.php/Cross_Site_Tracing 614 URL url = new URL(baseUrl, "/echo?a=b"); 615 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 616 conn.setRequestMethod("TRACE"); 617 conn.connect(); 618 assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); 619 } 620}