From 1f77acf020dea740f2e8e8c867d3cdc60891d120 Mon Sep 17 00:00:00 2001 From: baranowb Date: Thu, 25 Nov 2021 11:09:07 +0100 Subject: [PATCH] [UNDERTOW-1994] impl declareRoles in ServletContext + testcase --- .../servlet/UndertowServletMessages.java | 9 ++ .../servlet/spec/ServletContextImpl.java | 18 +++ .../servletcontext/CheckRolesServlet.java | 56 ++++++++ .../DeclareRolesServletContextListener.java | 36 +++++ .../ServletContextRolesTestCase.java | 134 ++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/CheckRolesServlet.java create mode 100644 servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/DeclareRolesServletContextListener.java create mode 100644 servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/ServletContextRolesTestCase.java diff --git a/servlet/src/main/java/io/undertow/servlet/UndertowServletMessages.java b/servlet/src/main/java/io/undertow/servlet/UndertowServletMessages.java index 7779cf7c38..43a4d829fa 100644 --- a/servlet/src/main/java/io/undertow/servlet/UndertowServletMessages.java +++ b/servlet/src/main/java/io/undertow/servlet/UndertowServletMessages.java @@ -235,4 +235,13 @@ public interface UndertowServletMessages { @Message(id = 10063, value = "Path %s must start with a / to get the request dispatcher") IllegalArgumentException pathMustStartWithSlashForRequestDispatcher(String path); + + @Message(id = 10064, value = "Servlet context for context path '%s' in deployment '%s' has already been initialized, can not declare roles.") + IllegalStateException servletAlreadyInitialize(String deploymentName, String contextPath); + + @Message(id = 10065, value = "Can not set empty/null role in servlet context for context path '%s' in deployment '%s' ") + IllegalArgumentException roleMustNotBeEmpty(String deploymentName, String contextPath); + + @Message(id = 10066, value = "Can not set invoke 'declareRoles' from dynamic listener in servlet context for context path '%s' in deployment '%s' ") + UnsupportedOperationException cantCallFromDynamicListener(String deploymentName, String contextPath); } diff --git a/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java b/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java index 7602a8e73f..be6a1d1972 100644 --- a/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java +++ b/servlet/src/main/java/io/undertow/servlet/spec/ServletContextImpl.java @@ -792,6 +792,24 @@ public ClassLoader getClassLoader() { @Override public void declareRoles(final String... roleNames) { + final DeploymentInfo di = this.getDeploymentInfo(); + if (isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletAlreadyInitialize(di.getDeploymentName(), di.getContextPath()); + } + + for (String role : roleNames) { + if (role == null || role.isEmpty()) { + throw UndertowServletMessages.MESSAGES.roleMustNotBeEmpty(di.getDeploymentName(), di.getContextPath()); + } + } + + if (ApplicationListeners.listenerState() == PROGRAMATIC_LISTENER) { + //NOTE: its either null or false for non-programatic? - null in case its not ApplicationListener + throw UndertowServletMessages.MESSAGES.cantCallFromDynamicListener(di.getDeploymentName(), di.getContextPath()); + } + + deploymentInfo.addSecurityRoles(roleNames); + } @Override diff --git a/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/CheckRolesServlet.java b/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/CheckRolesServlet.java new file mode 100644 index 0000000000..25a52d56bf --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/CheckRolesServlet.java @@ -0,0 +1,56 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.test.listener.servletcontext; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author baranowb + * + */ +public class CheckRolesServlet extends HttpServlet { + + + @Override + public void init(final ServletConfig config) throws ServletException { + super.init(config); + } + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + PrintWriter writer = resp.getWriter(); + for(String role:DeclareRolesServletContextListener.ROLES) { + final boolean isInRole = req.isUserInRole(role); + writer.write(role+":"+isInRole+"\n"); + } + writer.close(); + } + + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/DeclareRolesServletContextListener.java b/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/DeclareRolesServletContextListener.java new file mode 100644 index 0000000000..224d5d146a --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/DeclareRolesServletContextListener.java @@ -0,0 +1,36 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.test.listener.servletcontext; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +/** + * + * @author baranowb + * + */ +public class DeclareRolesServletContextListener implements ServletContextListener{ + + public static final String[] ROLES = new String[] {"dobby","was","here"}; + @Override + public void contextInitialized(ServletContextEvent sce) { + System.err.println(sce.getServletContext()); + sce.getServletContext().declareRoles(ROLES); + } + +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/ServletContextRolesTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/ServletContextRolesTestCase.java new file mode 100644 index 0000000000..45f13f829f --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/listener/servletcontext/ServletContextRolesTestCase.java @@ -0,0 +1,134 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.test.listener.servletcontext; + +import static io.undertow.util.Headers.AUTHORIZATION; +import static io.undertow.util.Headers.BASIC; +import static org.junit.Assert.assertEquals; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.AuthMethodConfig; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.LoginConfig; +import io.undertow.servlet.api.SecurityConstraint; +import io.undertow.servlet.api.SecurityInfo; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.WebResourceCollection; +import io.undertow.servlet.test.security.constraint.ServletIdentityManager; +import io.undertow.servlet.test.util.TestClassIntrospector; +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.HttpClientUtils; +import io.undertow.testutils.TestHttpClient; +import io.undertow.util.FlexBase64; +import io.undertow.util.Headers; + +/** + * @author baranowb + */ +@RunWith(DefaultServer.class) +public class ServletContextRolesTestCase { + private static final String REALM_NAME = "Servlet_Realm-1"; + static DeploymentManager manager; + + @BeforeClass + public static void setup() throws ServletException { + + final PathHandler root = new PathHandler(); + final ServletContainer container = ServletContainer.Factory.newInstance(); + final ServletIdentityManager identityManager = new ServletIdentityManager(); + identityManager.addUser("user1", "password1", "unspecified-role"); + + LoginConfig loginConfig = new LoginConfig(REALM_NAME); + Map props = new HashMap<>(); + props.put("charset", "ISO_8859_1"); + props.put("user-agent-charsets", "Chrome,UTF-8,OPR,UTF-8"); + loginConfig.addFirstAuthMethod(new AuthMethodConfig("BASIC", props)); + + DeploymentInfo builder = new DeploymentInfo() + .setClassLoader(ServletContextRolesTestCase.class.getClassLoader()) + .setContextPath("/servletContext") + .setClassIntrospecter(TestClassIntrospector.INSTANCE) + .setDeploymentName("servletContext.war") + .addServlet( + new ServletInfo("servlet", CheckRolesServlet.class) + .addMapping("/aa") + ) + .addListener(new ListenerInfo(DeclareRolesServletContextListener.class)) + .setIdentityManager(identityManager) + .setLoginConfig(loginConfig); + + builder.addPrincipalVsRoleMappings("user1", DeclareRolesServletContextListener.ROLES); + builder.addSecurityConstraint(new SecurityConstraint() + .addWebResourceCollection(new WebResourceCollection() + .addUrlPattern("/*")) + .addRolesAllowed(DeclareRolesServletContextListener.ROLES) + .setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.DENY)); + + manager = container.addDeployment(builder); + manager.deploy(); + root.addPrefixPath(builder.getContextPath(), manager.start()); + + DefaultServer.setRootHandler(root); + } + + @Test + public void testRoles() throws Exception { + final StringBuilder sb = new StringBuilder(40); + sb.append("dobby:true\n") + .append("was:true\n") + .append("here:true\n"); + testCall(sb.toString(), StandardCharsets.UTF_8, "Chrome", "user1", "password1", 200); + } + + public void testCall( final String expectedResponse, Charset charset, String userAgent, String user, String password, int expect) throws Exception { + TestHttpClient client = new TestHttpClient(); + try { + String url = DefaultServer.getDefaultServerURL() + "/servletContext/aa"; + HttpGet get = new HttpGet(url); + get = new HttpGet(url); + get.addHeader(Headers.USER_AGENT_STRING, userAgent); + get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString((user + ":" + password).getBytes(charset), false)); + HttpResponse result = client.execute(get); + assertEquals(expect, result.getStatusLine().getStatusCode()); + + final String response = HttpClientUtils.readResponse(result); + if(expect == 200) { + assertEquals(expectedResponse, response); + } + } finally { + client.getConnectionManager().shutdown(); + } + } +}