2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package org
.apache
.hadoop
.hbase
.http
;
20 import static org
.hamcrest
.Matchers
.greaterThan
;
21 import java
.io
.BufferedReader
;
22 import java
.io
.IOException
;
23 import java
.io
.InputStream
;
24 import java
.io
.InputStreamReader
;
25 import java
.io
.PrintWriter
;
26 import java
.net
.HttpURLConnection
;
29 import java
.nio
.CharBuffer
;
30 import java
.util
.Arrays
;
31 import java
.util
.Collections
;
32 import java
.util
.Enumeration
;
33 import java
.util
.HashMap
;
34 import java
.util
.List
;
36 import java
.util
.SortedSet
;
37 import java
.util
.TreeSet
;
38 import java
.util
.concurrent
.CountDownLatch
;
39 import java
.util
.concurrent
.Executor
;
40 import java
.util
.concurrent
.Executors
;
41 import javax
.servlet
.Filter
;
42 import javax
.servlet
.FilterChain
;
43 import javax
.servlet
.FilterConfig
;
44 import javax
.servlet
.ServletContext
;
45 import javax
.servlet
.ServletException
;
46 import javax
.servlet
.ServletRequest
;
47 import javax
.servlet
.ServletResponse
;
48 import javax
.servlet
.http
.HttpServlet
;
49 import javax
.servlet
.http
.HttpServletRequest
;
50 import javax
.servlet
.http
.HttpServletRequestWrapper
;
51 import javax
.servlet
.http
.HttpServletResponse
;
52 import org
.apache
.hadoop
.conf
.Configuration
;
53 import org
.apache
.hadoop
.fs
.CommonConfigurationKeys
;
54 import org
.apache
.hadoop
.hbase
.HBaseClassTestRule
;
55 import org
.apache
.hadoop
.hbase
.http
.HttpServer
.QuotingInputFilter
.RequestQuoter
;
56 import org
.apache
.hadoop
.hbase
.http
.resource
.JerseyResource
;
57 import org
.apache
.hadoop
.hbase
.testclassification
.MiscTests
;
58 import org
.apache
.hadoop
.hbase
.testclassification
.SmallTests
;
59 import org
.apache
.hadoop
.net
.NetUtils
;
60 import org
.apache
.hadoop
.security
.Groups
;
61 import org
.apache
.hadoop
.security
.ShellBasedUnixGroupsMapping
;
62 import org
.apache
.hadoop
.security
.UserGroupInformation
;
63 import org
.apache
.hadoop
.security
.authorize
.AccessControlList
;
64 import org
.apache
.http
.HttpEntity
;
65 import org
.apache
.http
.HttpHeaders
;
66 import org
.apache
.http
.client
.methods
.CloseableHttpResponse
;
67 import org
.apache
.http
.client
.methods
.HttpGet
;
68 import org
.apache
.http
.impl
.client
.CloseableHttpClient
;
69 import org
.apache
.http
.impl
.client
.HttpClients
;
70 import org
.hamcrest
.MatcherAssert
;
71 import org
.junit
.AfterClass
;
72 import org
.junit
.Assert
;
73 import org
.junit
.BeforeClass
;
74 import org
.junit
.ClassRule
;
75 import org
.junit
.Ignore
;
76 import org
.junit
.Test
;
77 import org
.junit
.experimental
.categories
.Category
;
78 import org
.mockito
.Mockito
;
79 import org
.slf4j
.Logger
;
80 import org
.slf4j
.LoggerFactory
;
81 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.ServerConnector
;
82 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.util
.ajax
.JSON
;
84 @Category({MiscTests
.class, SmallTests
.class})
85 public class TestHttpServer
extends HttpServerFunctionalTest
{
87 public static final HBaseClassTestRule CLASS_RULE
=
88 HBaseClassTestRule
.forClass(TestHttpServer
.class);
90 private static final Logger LOG
= LoggerFactory
.getLogger(TestHttpServer
.class);
91 private static HttpServer server
;
92 private static URL baseUrl
;
93 // jetty 9.4.x needs this many threads to start, even in the small.
94 static final int MAX_THREADS
= 16;
96 @SuppressWarnings("serial")
97 public static class EchoMapServlet
extends HttpServlet
{
99 public void doGet(HttpServletRequest request
, HttpServletResponse response
) throws IOException
{
100 PrintWriter out
= response
.getWriter();
101 Map
<String
, String
[]> params
= request
.getParameterMap();
102 SortedSet
<String
> keys
= new TreeSet
<>(params
.keySet());
103 for(String key
: keys
) {
106 String
[] values
= params
.get(key
);
107 if (values
.length
> 0) {
108 out
.print(values
[0]);
109 for(int i
=1; i
< values
.length
; ++i
) {
111 out
.print(values
[i
]);
120 @SuppressWarnings("serial")
121 public static class EchoServlet
extends HttpServlet
{
123 public void doGet(HttpServletRequest request
, HttpServletResponse response
)
125 PrintWriter out
= response
.getWriter();
126 SortedSet
<String
> sortedKeys
= new TreeSet
<>();
127 Enumeration
<String
> keys
= request
.getParameterNames();
128 while(keys
.hasMoreElements()) {
129 sortedKeys
.add(keys
.nextElement());
131 for(String key
: sortedKeys
) {
134 out
.print(request
.getParameter(key
));
141 @SuppressWarnings("serial")
142 public static class LongHeaderServlet
extends HttpServlet
{
144 public void doGet(HttpServletRequest request
, HttpServletResponse response
) {
145 Assert
.assertEquals(63 * 1024, request
.getHeader("longheader").length());
146 response
.setStatus(HttpServletResponse
.SC_OK
);
150 @SuppressWarnings("serial")
151 public static class HtmlContentServlet
extends HttpServlet
{
153 public void doGet(HttpServletRequest request
, HttpServletResponse response
) throws IOException
{
154 response
.setContentType("text/html");
155 PrintWriter out
= response
.getWriter();
156 out
.print("hello world");
161 @BeforeClass public static void setup() throws Exception
{
162 Configuration conf
= new Configuration();
163 conf
.setInt(HttpServer
.HTTP_MAX_THREADS
, MAX_THREADS
);
164 server
= createTestServer(conf
);
165 server
.addUnprivilegedServlet("echo", "/echo", EchoServlet
.class);
166 server
.addUnprivilegedServlet("echomap", "/echomap", EchoMapServlet
.class);
167 server
.addUnprivilegedServlet("htmlcontent", "/htmlcontent", HtmlContentServlet
.class);
168 server
.addUnprivilegedServlet("longheader", "/longheader", LongHeaderServlet
.class);
169 server
.addJerseyResourcePackage(
170 JerseyResource
.class.getPackage().getName(), "/jersey/*");
172 baseUrl
= getServerURL(server
);
173 LOG
.info("HTTP server started: "+ baseUrl
);
176 @AfterClass public static void cleanup() throws Exception
{
181 * Test the maximum number of threads cannot be exceeded.
184 public void testMaxThreads() throws Exception
{
185 int clientThreads
= MAX_THREADS
* 10;
186 Executor executor
= Executors
.newFixedThreadPool(clientThreads
);
187 // Run many clients to make server reach its maximum number of threads
188 final CountDownLatch ready
= new CountDownLatch(clientThreads
);
189 final CountDownLatch start
= new CountDownLatch(1);
190 for (int i
= 0; i
< clientThreads
; i
++) {
191 executor
.execute(() -> {
195 assertEquals("a:b\nc:d\n",
196 readOutput(new URL(baseUrl
, "/echo?a=b&c=d")));
197 int serverThreads
= server
.webServer
.getThreadPool().getThreads();
198 assertTrue("More threads are started than expected, Server Threads count: "
199 + serverThreads
, serverThreads
<= MAX_THREADS
);
200 LOG
.info("Number of threads = " + serverThreads
+
201 " which is less or equal than the max = " + MAX_THREADS
);
202 } catch (Exception e
) {
207 // Start the client threads when they are all ready
212 @Test public void testEcho() throws Exception
{
213 assertEquals("a:b\nc:d\n",
214 readOutput(new URL(baseUrl
, "/echo?a=b&c=d")));
215 assertEquals("a:b\nc<:d\ne:>\n",
216 readOutput(new URL(baseUrl
, "/echo?a=b&c<=d&e=>")));
219 /** Test the echo map servlet that uses getParameterMap. */
220 @Test public void testEchoMap() throws Exception
{
221 assertEquals("a:b\nc:d\n",
222 readOutput(new URL(baseUrl
, "/echomap?a=b&c=d")));
223 assertEquals("a:b,>\nc<:d\n",
224 readOutput(new URL(baseUrl
, "/echomap?a=b&c<=d&a=>")));
228 * Test that verifies headers can be up to 64K long.
229 * The test adds a 63K header leaving 1K for other headers.
230 * This is because the header buffer setting is for ALL headers,
231 * names and values included. */
232 @Test public void testLongHeader() throws Exception
{
233 URL url
= new URL(baseUrl
, "/longheader");
234 HttpURLConnection conn
= (HttpURLConnection
) url
.openConnection();
235 StringBuilder sb
= new StringBuilder();
236 for (int i
= 0 ; i
< 63 * 1024; i
++) {
239 conn
.setRequestProperty("longheader", sb
.toString());
240 assertEquals(HttpURLConnection
.HTTP_OK
, conn
.getResponseCode());
244 public void testContentTypes() throws Exception
{
245 // Static CSS files should have text/css
246 URL cssUrl
= new URL(baseUrl
, "/static/test.css");
247 HttpURLConnection conn
= (HttpURLConnection
)cssUrl
.openConnection();
249 assertEquals(200, conn
.getResponseCode());
250 assertEquals("text/css", conn
.getContentType());
252 // Servlets should have text/plain with proper encoding by default
253 URL servletUrl
= new URL(baseUrl
, "/echo?a=b");
254 conn
= (HttpURLConnection
)servletUrl
.openConnection();
256 assertEquals(200, conn
.getResponseCode());
257 assertEquals("text/plain;charset=utf-8", conn
.getContentType());
259 // We should ignore parameters for mime types - ie a parameter
260 // ending in .css should not change mime type
261 servletUrl
= new URL(baseUrl
, "/echo?a=b.css");
262 conn
= (HttpURLConnection
)servletUrl
.openConnection();
264 assertEquals(200, conn
.getResponseCode());
265 assertEquals("text/plain;charset=utf-8", conn
.getContentType());
267 // Servlets that specify text/html should get that content type
268 servletUrl
= new URL(baseUrl
, "/htmlcontent");
269 conn
= (HttpURLConnection
)servletUrl
.openConnection();
271 assertEquals(200, conn
.getResponseCode());
272 assertEquals("text/html;charset=utf-8", conn
.getContentType());
274 // JSPs should default to text/html with utf8
275 // JSPs do not work from unit tests
276 // servletUrl = new URL(baseUrl, "/testjsp.jsp");
277 // conn = (HttpURLConnection)servletUrl.openConnection();
279 // assertEquals(200, conn.getResponseCode());
280 // assertEquals("text/html; charset=utf-8", conn.getContentType());
284 public void testNegotiatesEncodingGzip() throws IOException
{
285 final InputStream stream
= ClassLoader
.getSystemResourceAsStream("webapps/static/test.css");
286 assertNotNull(stream
);
287 final String sourceContent
= readFully(stream
);
289 try (final CloseableHttpClient client
= HttpClients
.createMinimal()) {
290 final HttpGet request
= new HttpGet(new URL(baseUrl
, "/static/test.css").toString());
292 request
.setHeader(HttpHeaders
.ACCEPT_ENCODING
, null);
293 final long unencodedContentLength
;
294 try (final CloseableHttpResponse response
= client
.execute(request
)) {
295 final HttpEntity entity
= response
.getEntity();
296 assertNotNull(entity
);
297 assertNull(entity
.getContentEncoding());
298 unencodedContentLength
= entity
.getContentLength();
299 MatcherAssert
.assertThat(unencodedContentLength
, greaterThan(0L));
300 final String unencodedEntityBody
= readFully(entity
.getContent());
301 assertEquals(sourceContent
, unencodedEntityBody
);
304 request
.setHeader(HttpHeaders
.ACCEPT_ENCODING
, "gzip");
305 final long encodedContentLength
;
306 try (final CloseableHttpResponse response
= client
.execute(request
)) {
307 final HttpEntity entity
= response
.getEntity();
308 assertNotNull(entity
);
309 assertNotNull(entity
.getContentEncoding());
310 assertEquals("gzip", entity
.getContentEncoding().getValue());
311 encodedContentLength
= entity
.getContentLength();
312 MatcherAssert
.assertThat(encodedContentLength
, greaterThan(0L));
313 final String encodedEntityBody
= readFully(entity
.getContent());
314 // the encoding/decoding process, as implemented in this specific combination of dependency
315 // versions, does not perfectly preserve trailing whitespace. thus, `trim()`.
316 assertEquals(sourceContent
.trim(), encodedEntityBody
.trim());
318 MatcherAssert
.assertThat(unencodedContentLength
, greaterThan(encodedContentLength
));
322 private static String
readFully(final InputStream input
) throws IOException
{
323 // TODO: when the time comes, delete me and replace with a JDK11 IO helper API.
324 try (final BufferedReader reader
= new BufferedReader(new InputStreamReader(input
))) {
325 final StringBuilder sb
= new StringBuilder();
326 final CharBuffer buffer
= CharBuffer
.allocate(1024 * 2);
327 while (reader
.read(buffer
) > 0) {
331 return sb
.toString();
338 * Dummy filter that mimics as an authentication filter. Obtains user identity
339 * from the request parameter user.name. Wraps around the request so that
340 * request.getRemoteUser() returns the user identity.
343 public static class DummyServletFilter
implements Filter
{
345 public void destroy() { }
348 public void doFilter(ServletRequest request
, ServletResponse response
,
349 FilterChain filterChain
) throws IOException
, ServletException
{
350 final String userName
= request
.getParameter("user.name");
351 ServletRequest requestModified
=
352 new HttpServletRequestWrapper((HttpServletRequest
) request
) {
354 public String
getRemoteUser() {
358 filterChain
.doFilter(requestModified
, response
);
362 public void init(FilterConfig arg0
) { }
366 * FilterInitializer that initialized the DummyFilter.
369 public static class DummyFilterInitializer
extends FilterInitializer
{
370 public DummyFilterInitializer() {
374 public void initFilter(FilterContainer container
, Configuration conf
) {
375 container
.addFilter("DummyFilter", DummyServletFilter
.class.getName(), null);
380 * Access a URL and get the corresponding return Http status code. The URL
381 * will be accessed as the passed user, by sending user.name request
384 * @param urlstring The url to access
385 * @param userName The user to perform access as
386 * @return The HTTP response code
387 * @throws IOException if there is a problem communicating with the server
389 private static int getHttpStatusCode(String urlstring
, String userName
) throws IOException
{
390 URL url
= new URL(urlstring
+ "?user.name=" + userName
);
391 System
.out
.println("Accessing " + url
+ " as user " + userName
);
392 HttpURLConnection connection
= (HttpURLConnection
)url
.openConnection();
393 connection
.connect();
394 return connection
.getResponseCode();
398 * Custom user->group mapping service.
400 public static class MyGroupsProvider
extends ShellBasedUnixGroupsMapping
{
401 static Map
<String
, List
<String
>> mapping
= new HashMap
<>();
403 static void clearMapping() {
408 public List
<String
> getGroups(String user
) {
409 return mapping
.get(user
);
414 * Verify the access for /logs, /stacks, /conf, /logLevel and /metrics
415 * servlets, when authentication filters are set, but authorization is not
420 public void testDisabledAuthorizationOfDefaultServlets() throws Exception
{
421 Configuration conf
= new Configuration();
423 // Authorization is disabled by default
424 conf
.set(HttpServer
.FILTER_INITIALIZERS_PROPERTY
,
425 DummyFilterInitializer
.class.getName());
426 conf
.set(CommonConfigurationKeys
.HADOOP_SECURITY_GROUP_MAPPING
,
427 MyGroupsProvider
.class.getName());
428 Groups
.getUserToGroupsMappingService(conf
);
429 MyGroupsProvider
.clearMapping();
430 MyGroupsProvider
.mapping
.put("userA", Collections
.singletonList("groupA"));
431 MyGroupsProvider
.mapping
.put("userB", Collections
.singletonList("groupB"));
433 HttpServer myServer
= new HttpServer
.Builder().setName("test")
434 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).build();
435 myServer
.setAttribute(HttpServer
.CONF_CONTEXT_ATTRIBUTE
, conf
);
437 String serverURL
= "http://" + NetUtils
.getHostPortString(myServer
.getConnectorAddress(0)) + "/";
438 for (String servlet
: new String
[] { "conf", "logs", "stacks", "logLevel", "metrics" }) {
439 for (String user
: new String
[] { "userA", "userB" }) {
440 assertEquals(HttpURLConnection
.HTTP_OK
, getHttpStatusCode(serverURL
448 * Verify the administrator access for /logs, /stacks, /conf, /logLevel and
453 public void testAuthorizationOfDefaultServlets() throws Exception
{
454 Configuration conf
= new Configuration();
455 conf
.setBoolean(CommonConfigurationKeys
.HADOOP_SECURITY_AUTHORIZATION
,
457 conf
.setBoolean(CommonConfigurationKeys
.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN
,
459 conf
.set(HttpServer
.FILTER_INITIALIZERS_PROPERTY
,
460 DummyFilterInitializer
.class.getName());
462 conf
.set(CommonConfigurationKeys
.HADOOP_SECURITY_GROUP_MAPPING
,
463 MyGroupsProvider
.class.getName());
464 Groups
.getUserToGroupsMappingService(conf
);
465 MyGroupsProvider
.clearMapping();
466 MyGroupsProvider
.mapping
.put("userA", Collections
.singletonList("groupA"));
467 MyGroupsProvider
.mapping
.put("userB", Collections
.singletonList("groupB"));
468 MyGroupsProvider
.mapping
.put("userC", Collections
.singletonList("groupC"));
469 MyGroupsProvider
.mapping
.put("userD", Collections
.singletonList("groupD"));
470 MyGroupsProvider
.mapping
.put("userE", Collections
.singletonList("groupE"));
472 HttpServer myServer
= new HttpServer
.Builder().setName("test")
473 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).setConf(conf
)
474 .setACL(new AccessControlList("userA,userB groupC,groupD")).build();
475 myServer
.setAttribute(HttpServer
.CONF_CONTEXT_ATTRIBUTE
, conf
);
478 String serverURL
= "http://"
479 + NetUtils
.getHostPortString(myServer
.getConnectorAddress(0)) + "/";
480 for (String servlet
: new String
[] { "conf", "logs", "stacks", "logLevel", "metrics" }) {
481 for (String user
: new String
[] { "userA", "userB", "userC", "userD" }) {
482 assertEquals(HttpURLConnection
.HTTP_OK
, getHttpStatusCode(serverURL
485 assertEquals(HttpURLConnection
.HTTP_UNAUTHORIZED
, getHttpStatusCode(
486 serverURL
+ servlet
, "userE"));
492 public void testRequestQuoterWithNull() {
493 HttpServletRequest request
= Mockito
.mock(HttpServletRequest
.class);
494 Mockito
.doReturn(null).when(request
).getParameterValues("dummy");
495 RequestQuoter requestQuoter
= new RequestQuoter(request
);
496 String
[] parameterValues
= requestQuoter
.getParameterValues("dummy");
497 Assert
.assertNull("It should return null "
498 + "when there are no values for the parameter", parameterValues
);
502 public void testRequestQuoterWithNotNull() {
503 HttpServletRequest request
= Mockito
.mock(HttpServletRequest
.class);
504 String
[] values
= new String
[] { "abc", "def" };
505 Mockito
.doReturn(values
).when(request
).getParameterValues("dummy");
506 RequestQuoter requestQuoter
= new RequestQuoter(request
);
507 String
[] parameterValues
= requestQuoter
.getParameterValues("dummy");
508 Assert
.assertTrue("It should return Parameter Values", Arrays
.equals(
509 values
, parameterValues
));
512 @SuppressWarnings("unchecked")
513 private static Map
<String
, Object
> parse(String jsonString
) {
514 return (Map
<String
, Object
>)JSON
.parse(jsonString
);
517 @Test public void testJersey() throws Exception
{
518 LOG
.info("BEGIN testJersey()");
519 final String js
= readOutput(new URL(baseUrl
, "/jersey/foo?op=bar"));
520 final Map
<String
, Object
> m
= parse(js
);
522 assertEquals("foo", m
.get(JerseyResource
.PATH
));
523 assertEquals("bar", m
.get(JerseyResource
.OP
));
524 LOG
.info("END testJersey()");
528 public void testHasAdministratorAccess() throws Exception
{
529 Configuration conf
= new Configuration();
530 conf
.setBoolean(CommonConfigurationKeys
.HADOOP_SECURITY_AUTHORIZATION
, false);
531 ServletContext context
= Mockito
.mock(ServletContext
.class);
532 Mockito
.when(context
.getAttribute(HttpServer
.CONF_CONTEXT_ATTRIBUTE
)).thenReturn(conf
);
533 Mockito
.when(context
.getAttribute(HttpServer
.ADMINS_ACL
)).thenReturn(null);
534 HttpServletRequest request
= Mockito
.mock(HttpServletRequest
.class);
535 Mockito
.when(request
.getRemoteUser()).thenReturn(null);
536 HttpServletResponse response
= Mockito
.mock(HttpServletResponse
.class);
539 Assert
.assertTrue(HttpServer
.hasAdministratorAccess(context
, request
, response
));
541 //authorization ON & user NULL
542 response
= Mockito
.mock(HttpServletResponse
.class);
543 conf
.setBoolean(CommonConfigurationKeys
.HADOOP_SECURITY_AUTHORIZATION
, true);
544 Assert
.assertFalse(HttpServer
.hasAdministratorAccess(context
, request
, response
));
545 Mockito
.verify(response
).sendError(Mockito
.eq(HttpServletResponse
.SC_UNAUTHORIZED
),
546 Mockito
.anyString());
548 //authorization ON & user NOT NULL & ACLs NULL
549 response
= Mockito
.mock(HttpServletResponse
.class);
550 Mockito
.when(request
.getRemoteUser()).thenReturn("foo");
551 Assert
.assertTrue(HttpServer
.hasAdministratorAccess(context
, request
, response
));
553 //authorization ON & user NOT NULL & ACLs NOT NULL & user not in ACLs
554 response
= Mockito
.mock(HttpServletResponse
.class);
555 AccessControlList acls
= Mockito
.mock(AccessControlList
.class);
556 Mockito
.when(acls
.isUserAllowed(Mockito
.<UserGroupInformation
>any())).thenReturn(false);
557 Mockito
.when(context
.getAttribute(HttpServer
.ADMINS_ACL
)).thenReturn(acls
);
558 Assert
.assertFalse(HttpServer
.hasAdministratorAccess(context
, request
, response
));
559 Mockito
.verify(response
).sendError(Mockito
.eq(HttpServletResponse
.SC_FORBIDDEN
),
560 Mockito
.anyString());
562 //authorization ON & user NOT NULL & ACLs NOT NULL & user in in ACLs
563 response
= Mockito
.mock(HttpServletResponse
.class);
564 Mockito
.when(acls
.isUserAllowed(Mockito
.<UserGroupInformation
>any())).thenReturn(true);
565 Mockito
.when(context
.getAttribute(HttpServer
.ADMINS_ACL
)).thenReturn(acls
);
566 Assert
.assertTrue(HttpServer
.hasAdministratorAccess(context
, request
, response
));
571 public void testRequiresAuthorizationAccess() throws Exception
{
572 Configuration conf
= new Configuration();
573 ServletContext context
= Mockito
.mock(ServletContext
.class);
574 Mockito
.when(context
.getAttribute(HttpServer
.CONF_CONTEXT_ATTRIBUTE
)).thenReturn(conf
);
575 HttpServletRequest request
= Mockito
.mock(HttpServletRequest
.class);
576 HttpServletResponse response
= Mockito
.mock(HttpServletResponse
.class);
578 //requires admin access to instrumentation, FALSE by default
579 Assert
.assertTrue(HttpServer
.isInstrumentationAccessAllowed(context
, request
, response
));
581 //requires admin access to instrumentation, TRUE
582 conf
.setBoolean(CommonConfigurationKeys
.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN
, true);
583 conf
.setBoolean(CommonConfigurationKeys
.HADOOP_SECURITY_AUTHORIZATION
, true);
584 AccessControlList acls
= Mockito
.mock(AccessControlList
.class);
585 Mockito
.when(acls
.isUserAllowed(Mockito
.<UserGroupInformation
>any())).thenReturn(false);
586 Mockito
.when(context
.getAttribute(HttpServer
.ADMINS_ACL
)).thenReturn(acls
);
587 Assert
.assertFalse(HttpServer
.isInstrumentationAccessAllowed(context
, request
, response
));
591 public void testBindAddress() throws Exception
{
592 checkBindAddress("localhost", 0, false).stop();
593 // hang onto this one for a bit more testing
594 HttpServer myServer
= checkBindAddress("localhost", 0, false);
595 HttpServer myServer2
= null;
597 int port
= myServer
.getConnectorAddress(0).getPort();
598 // it's already in use, true = expect a higher port
599 myServer2
= checkBindAddress("localhost", port
, true);
600 // try to reuse the port
601 port
= myServer2
.getConnectorAddress(0).getPort();
603 assertNull(myServer2
.getConnectorAddress(0)); // not bound
604 myServer2
.openListeners();
605 assertEquals(port
, myServer2
.getConnectorAddress(0).getPort()); // expect same port
608 if (myServer2
!= null) {
614 private HttpServer
checkBindAddress(String host
, int port
, boolean findPort
)
616 HttpServer server
= createServer(host
, port
);
618 // not bound, ephemeral should return requested port (0 for ephemeral)
619 ServerConnector listener
= server
.getServerConnectors().get(0);
621 assertEquals(port
, listener
.getPort());
622 // verify hostname is what was given
623 server
.openListeners();
624 assertEquals(host
, server
.getConnectorAddress(0).getHostName());
626 int boundPort
= server
.getConnectorAddress(0).getPort();
628 assertTrue(boundPort
!= 0); // ephemeral should now return bound port
629 } else if (findPort
) {
630 assertTrue(boundPort
> port
);
631 // allow a little wiggle room to prevent random test failures if
632 // some consecutive ports are already in use
633 assertTrue(boundPort
- port
< 8);
635 } catch (Exception e
) {
643 public void testXFrameHeaderSameOrigin() throws Exception
{
644 Configuration conf
= new Configuration();
645 conf
.set("hbase.http.filter.xframeoptions.mode", "SAMEORIGIN");
647 HttpServer myServer
= new HttpServer
.Builder().setName("test")
648 .addEndpoint(new URI("http://localhost:0"))
649 .setFindPort(true).setConf(conf
).build();
650 myServer
.setAttribute(HttpServer
.CONF_CONTEXT_ATTRIBUTE
, conf
);
651 myServer
.addUnprivilegedServlet("echo", "/echo", EchoServlet
.class);
654 String serverURL
= "http://"
655 + NetUtils
.getHostPortString(myServer
.getConnectorAddress(0));
656 URL url
= new URL(new URL(serverURL
), "/echo?a=b&c=d");
657 HttpURLConnection conn
= (HttpURLConnection
) url
.openConnection();
658 assertEquals(HttpURLConnection
.HTTP_OK
, conn
.getResponseCode());
659 assertEquals("SAMEORIGIN", conn
.getHeaderField("X-Frame-Options"));
664 public void testNoCacheHeader() throws Exception
{
665 URL url
= new URL(baseUrl
, "/echo?a=b&c=d");
666 HttpURLConnection conn
= (HttpURLConnection
) url
.openConnection();
667 assertEquals(HttpURLConnection
.HTTP_OK
, conn
.getResponseCode());
668 assertEquals("no-cache", conn
.getHeaderField("Cache-Control"));
669 assertEquals("no-cache", conn
.getHeaderField("Pragma"));
670 assertNotNull(conn
.getHeaderField("Expires"));
671 assertNotNull(conn
.getHeaderField("Date"));
672 assertEquals(conn
.getHeaderField("Expires"), conn
.getHeaderField("Date"));
673 assertEquals("DENY", conn
.getHeaderField("X-Frame-Options"));
677 public void testHttpMethods() throws Exception
{
678 // HTTP TRACE method should be disabled for security
679 // See https://www.owasp.org/index.php/Cross_Site_Tracing
680 URL url
= new URL(baseUrl
, "/echo?a=b");
681 HttpURLConnection conn
= (HttpURLConnection
) url
.openConnection();
682 conn
.setRequestMethod("TRACE");
684 assertEquals(HttpURLConnection
.HTTP_FORBIDDEN
, conn
.getResponseCode());