/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.core; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.annotation.HttpConstraint; import javax.servlet.annotation.HttpMethodConstraint; import javax.servlet.annotation.ServletSecurity; import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; import org.apache.catalina.Context; import org.apache.catalina.Wrapper; import org.apache.catalina.authenticator.BasicAuthenticator; import org.apache.catalina.startup.TesterMapRealm; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.descriptor.web.LoginConfig; public class TestStandardWrapper extends TomcatBaseTest { @Test public void testSecurityAnnotationsSimple() throws Exception { doTest(DenyAllServlet.class.getName(), false, false, false, false); } @Test public void testSecurityAnnotationsSubclass1() throws Exception { doTest(SubclassDenyAllServlet.class.getName(), false, false, false,false); } @Test public void testSecurityAnnotationsSubclass2() throws Exception { doTest(SubclassAllowAllServlet.class.getName(), false, false, true, false); } @Test public void testSecurityAnnotationsMethods1() throws Exception { doTest(MethodConstraintServlet.class.getName(), false, false, false, false); } @Test public void testSecurityAnnotationsMethods2() throws Exception { doTest(MethodConstraintServlet.class.getName(), true, false, true, false); } @Test public void testSecurityAnnotationsRole1() throws Exception { doTest(RoleAllowServlet.class.getName(), false, true, true, false); } @Test public void testSecurityAnnotationsRole2() throws Exception { doTest(RoleDenyServlet.class.getName(), false, true, false, false); } @Test public void testSecurityAnnotationsUncoveredGet01() throws Exception { // Use a POST with role - should be allowed doTest(UncoveredGetServlet.class.getName(), true, true, true, false); } @Test public void testSecurityAnnotationsUncoveredGet02() throws Exception { // Use a POST with role - should be allowed doTest(UncoveredGetServlet.class.getName(), true, true, true, true); } @Test public void testSecurityAnnotationsUncoveredGet03() throws Exception { // Use a POST no role - should be blocked doTest(UncoveredGetServlet.class.getName(), true, false, false, false); } @Test public void testSecurityAnnotationsUncoveredGet04() throws Exception { // Use a POST no role - should be blocked doTest(UncoveredGetServlet.class.getName(), true, false, false, true); } @Test public void testSecurityAnnotationsUncoveredGet05() throws Exception { // Use a GET with role - should be allowed as denyUncovered is false doTest(UncoveredGetServlet.class.getName(), false, true, true, false); } @Test public void testSecurityAnnotationsUncoveredGet06() throws Exception { // Use a GET with role - should be blocked as denyUncovered is true doTest(UncoveredGetServlet.class.getName(), false, true, false, true); } @Test public void testSecurityAnnotationsUncoveredGet07() throws Exception { // Use a GET no role - should be allowed as denyUncovered is false doTest(UncoveredGetServlet.class.getName(), false, false, true, false); } @Test public void testSecurityAnnotationsUncoveredGet08() throws Exception { // Use a GET no role - should be blocked as denyUncovered is true doTest(UncoveredGetServlet.class.getName(), true, false, false, true); } @Test public void testSecurityAnnotationsWebXmlPriority() throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); File appDir = new File("test/webapp-fragments"); Context ctx = tomcat.addWebapp(null, "", appDir.getAbsolutePath()); skipTldsForResourceJars(ctx); tomcat.start(); ByteChunk bc = new ByteChunk(); int rc; rc = getUrl("http://localhost:" + getPort() + "/testStandardWrapper/securityAnnotationsWebXmlPriority", bc, null, null); assertTrue(bc.getLength() > 0); assertEquals(403, rc); } @Test public void testSecurityAnnotationsMetaDataPriority() throws Exception { getTomcatInstanceTestWebapp(false, true); ByteChunk bc = new ByteChunk(); int rc; rc = getUrl("http://localhost:" + getPort() + "/test/testStandardWrapper/securityAnnotationsMetaDataPriority", bc, null, null); assertEquals("OK", bc.toString()); assertEquals(200, rc); } @Test public void testSecurityAnnotationsAddServlet1() throws Exception { doTestSecurityAnnotationsAddServlet(false); } @Test public void testSecurityAnnotationsAddServlet2() throws Exception { doTestSecurityAnnotationsAddServlet(true); } @Test public void testSecurityAnnotationsNoWebXmlConstraints() throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); File appDir = new File("test/webapp-servletsecurity"); tomcat.addWebapp(null, "", appDir.getAbsolutePath()); tomcat.start(); ByteChunk bc = new ByteChunk(); int rc; rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null); assertTrue(bc.getLength() > 0); assertEquals(403, rc); } @Test public void testSecurityAnnotationsNoWebXmlLoginConfig() throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); File appDir = new File("test/webapp-servletsecurity2"); tomcat.addWebapp(null, "", appDir.getAbsolutePath()); tomcat.start(); ByteChunk bc = new ByteChunk(); int rc; rc = getUrl("http://localhost:" + getPort() + "/protected.jsp", bc, null, null); assertTrue(bc.getLength() > 0); assertEquals(403, rc); bc.recycle(); rc = getUrl("http://localhost:" + getPort() + "/unprotected.jsp", bc, null, null); assertEquals(200, rc); assertTrue(bc.toString().contains("00-OK")); } private void doTestSecurityAnnotationsAddServlet(boolean useCreateServlet) throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); // No file system docBase required Context ctx = tomcat.addContext("", null); Servlet s = new DenyAllServlet(); ServletContainerInitializer sci = new SCI(s, useCreateServlet); ctx.addServletContainerInitializer(sci, null); tomcat.start(); ByteChunk bc = new ByteChunk(); int rc; rc = getUrl("http://localhost:" + getPort() + "/", bc, null, null); if (useCreateServlet) { assertTrue(bc.getLength() > 0); assertEquals(403, rc); } else { assertEquals("OK", bc.toString()); assertEquals(200, rc); } } private void doTest(String servletClassName, boolean usePost, boolean useRole, boolean expect200, boolean denyUncovered) throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); // No file system docBase required Context ctx = tomcat.addContext("", null); ctx.setDenyUncoveredHttpMethods(denyUncovered); Wrapper wrapper = Tomcat.addServlet(ctx, "servlet", servletClassName); wrapper.setAsyncSupported(true); ctx.addServletMappingDecoded("/", "servlet"); if (useRole) { TesterMapRealm realm = new TesterMapRealm(); realm.addUser("testUser", "testPwd"); realm.addUserRole("testUser", "testRole"); ctx.setRealm(realm); ctx.setLoginConfig(new LoginConfig("BASIC", null, null, null)); ctx.getPipeline().addValve(new BasicAuthenticator()); } tomcat.start(); ByteChunk bc = new ByteChunk(); Map<String,List<String>> reqHeaders = null; if (useRole) { reqHeaders = new HashMap<>(); List<String> authHeaders = new ArrayList<>(); // testUser, testPwd authHeaders.add("Basic dGVzdFVzZXI6dGVzdFB3ZA=="); reqHeaders.put("Authorization", authHeaders); } int rc; if (usePost) { rc = postUrl(null, "http://localhost:" + getPort() + "/", bc, reqHeaders, null); } else { rc = getUrl("http://localhost:" + getPort() + "/", bc, reqHeaders, null); } if (expect200) { assertEquals("OK", bc.toString()); assertEquals(200, rc); } else { assertTrue(bc.getLength() > 0); assertEquals(403, rc); } } public static class TestServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); resp.getWriter().print("OK"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } @ServletSecurity(@HttpConstraint(EmptyRoleSemantic.DENY)) public static class DenyAllServlet extends TestServlet { private static final long serialVersionUID = 1L; } public static class SubclassDenyAllServlet extends DenyAllServlet { private static final long serialVersionUID = 1L; } @ServletSecurity(@HttpConstraint(EmptyRoleSemantic.PERMIT)) public static class SubclassAllowAllServlet extends DenyAllServlet { private static final long serialVersionUID = 1L; } @ServletSecurity(value= @HttpConstraint(EmptyRoleSemantic.PERMIT), httpMethodConstraints = { @HttpMethodConstraint(value="GET", emptyRoleSemantic = EmptyRoleSemantic.DENY) } ) public static class MethodConstraintServlet extends TestServlet { private static final long serialVersionUID = 1L; } @ServletSecurity(httpMethodConstraints = { @HttpMethodConstraint(value="POST",rolesAllowed = "testRole") } ) public static class UncoveredGetServlet extends TestServlet { private static final long serialVersionUID = 1L; } @ServletSecurity(@HttpConstraint(rolesAllowed = "testRole")) public static class RoleAllowServlet extends TestServlet { private static final long serialVersionUID = 1L; } @ServletSecurity(@HttpConstraint(rolesAllowed = "otherRole")) public static class RoleDenyServlet extends TestServlet { private static final long serialVersionUID = 1L; } public static class SCI implements ServletContainerInitializer { private Servlet servlet; private boolean createServlet; public SCI(Servlet servlet, boolean createServlet) { this.servlet = servlet; this.createServlet = createServlet; } @Override public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { Servlet s; if (createServlet) { s = ctx.createServlet(servlet.getClass()); } else { s = servlet; } ServletRegistration.Dynamic r = ctx.addServlet("servlet", s); r.addMapping("/"); } } public static final int BUG51445_THREAD_COUNT = 5; public static CountDownLatch latch = null; public static final AtomicInteger counter = new AtomicInteger(0); public static void initLatch() { latch = new CountDownLatch(BUG51445_THREAD_COUNT); } @Test public void testBug51445AddServlet() throws Exception { initLatch(); Tomcat tomcat = getTomcatInstance(); // No file system docBase required Context ctx = tomcat.addContext("", null); Tomcat.addServlet(ctx, "Bug51445", new Bug51445Servlet()); ctx.addServletMappingDecoded("/", "Bug51445"); tomcat.start(); // Start the threads Bug51445Thread[] threads = new Bug51445Thread[5]; for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { threads[i] = new Bug51445Thread(getPort()); threads[i].start(); } // Wait for threads to finish for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { threads[i].join(); } Set<String> servlets = new HashSet<>(); // Output the result for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { System.out.println(threads[i].getResult()); } // Check the result for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { String[] results = threads[i].getResult().split(","); assertEquals(2, results.length); assertEquals("10", results[0]); assertFalse(servlets.contains(results[1])); servlets.add(results[1]); } } @Test public void testBug51445AddChild() throws Exception { initLatch(); Tomcat tomcat = getTomcatInstance(); // No file system docBase required Context ctx = tomcat.addContext("", null); StandardWrapper wrapper = new StandardWrapper(); wrapper.setServletName("Bug51445"); wrapper.setServletClass(Bug51445Servlet.class.getName()); ctx.addChild(wrapper); ctx.addServletMappingDecoded("/", "Bug51445"); tomcat.start(); // Start the threads Bug51445Thread[] threads = new Bug51445Thread[5]; for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { threads[i] = new Bug51445Thread(getPort()); threads[i].start(); } // Wait for threads to finish for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { threads[i].join(); } Set<String> servlets = new HashSet<>(); // Output the result for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { System.out.println(threads[i].getResult()); } // Check the result for (int i = 0; i < BUG51445_THREAD_COUNT; i ++) { String[] results = threads[i].getResult().split(","); assertEquals(2, results.length); assertEquals("10", results[0]); assertFalse(servlets.contains(results[1])); servlets.add(results[1]); } } private static class Bug51445Thread extends Thread { private int port; private String result; public Bug51445Thread(int port) { this.port = port; } @Override public void run() { try { ByteChunk res = getUrl("http://localhost:" + port + "/"); result = res.toString(); } catch (IOException ioe) { result = ioe.getMessage(); } } public String getResult() { return result; } } /** * SingleThreadModel servlet that sets a value in the init() method. */ @SuppressWarnings("deprecation") public static class Bug51445Servlet extends HttpServlet implements javax.servlet.SingleThreadModel { private static final long serialVersionUID = 1L; private static final long LATCH_TIMEOUT = 60; private int data = 0; private int counter; public Bug51445Servlet() { // Use this rather than hashCode since in some environments, // multiple instances of this object were created with the same // hashCode counter = TestStandardWrapper.counter.incrementAndGet(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { boolean latchAwaitResult = false; // Ensure all threads have their own instance of the servlet latch.countDown(); try { latchAwaitResult = latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS); } catch (InterruptedException e) { // Ignore } resp.setContentType("text/plain"); if (latchAwaitResult) { resp.getWriter().print(data); } else { resp.getWriter().print("Latch await failed"); } resp.getWriter().print(","); resp.getWriter().print(counter); } @Override public void init(ServletConfig config) throws ServletException { super.init(config); data = 10; } } }