This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new fd9fa42 CAMEL-14950 camel-undertow: add an option to secure endpoints with spring-security 5 (with token bearer (#3767) fd9fa42 is described below commit fd9fa424fd319cde97878af99b807da93f2b21ca Author: JiriOndrusek <ondrusek.j...@gmail.com> AuthorDate: Tue Apr 28 09:29:06 2020 +0200 CAMEL-14950 camel-undertow: add an option to secure endpoints with spring-security 5 (with token bearer (#3767) --- components/camel-undertow/pom.xml | 5 + .../component/undertow/DefaultUndertowHost.java | 38 +++++++- .../undertow/spi/UndertowSecurityProvider.java | 8 ++ .../undertow/spi/AbstractProviderServletTest.java | 108 +++++++++++++++++++++ .../undertow/spi/ProviderWithServletTest.java | 60 ++++++++++++ .../undertow/spi/ProviderWithoutServletTest.java | 60 ++++++++++++ 6 files changed, 277 insertions(+), 2 deletions(-) diff --git a/components/camel-undertow/pom.xml b/components/camel-undertow/pom.xml index 30c34e5..ddc87ed 100644 --- a/components/camel-undertow/pom.xml +++ b/components/camel-undertow/pom.xml @@ -55,6 +55,11 @@ <artifactId>undertow-core</artifactId> <version>${undertow-version}</version> </dependency> + <dependency> + <groupId>io.undertow</groupId> + <artifactId>undertow-servlet</artifactId> + <version>${undertow-version}</version> + </dependency> <!-- testing --> <dependency> diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java index 4a938d5..5defbc9 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java @@ -18,12 +18,18 @@ package org.apache.camel.component.undertow; import java.net.URI; +import javax.servlet.ServletException; + import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; import org.apache.camel.component.undertow.handlers.CamelRootHandler; import org.apache.camel.component.undertow.handlers.NotFoundHandler; import org.apache.camel.component.undertow.handlers.RestRootHandler; +import org.apache.camel.component.undertow.spi.UndertowSecurityProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,9 +93,9 @@ public class DefaultUndertowHost implements UndertowHost { if (consumer != null && consumer.isRest()) { // use the rest handler as its a rest consumer - undertow = builder.setHandler(restHandler).build(); + undertow = registerHandler(consumer, builder, restHandler); } else { - undertow = builder.setHandler(rootHandler).build(); + undertow = registerHandler(consumer, builder, rootHandler); } LOG.info("Starting Undertow server on {}://{}:{}", key.getSslContext() != null ? "https" : "http", key.getHost(), key.getPort()); @@ -119,6 +125,34 @@ public class DefaultUndertowHost implements UndertowHost { } } + private Undertow registerHandler(UndertowConsumer consumer, Undertow.Builder builder, HttpHandler handler) { + UndertowSecurityProvider securityProvider = consumer == null + ? null : consumer.getEndpoint().getComponent().getSecurityProvider() != null + ? consumer.getEndpoint().getComponent().getSecurityProvider() : consumer.getEndpoint().getSecurityProvider(); + //if security provider needs servlet context, start empty servlet + if (securityProvider != null && securityProvider.requireServletContext()) { + DeploymentInfo deployment = Servlets.deployment() + .setContextPath("") + .setDisplayName("application") + .setDeploymentName("camel-undertow") + .setClassLoader(getClass().getClassLoader()) + //httpHandler for servlet is ignored, camel handler is used instead of it + .addOuterHandlerChainWrapper(h -> handler); + + DeploymentManager manager = Servlets.newContainer().addDeployment(deployment); + manager.deploy(); + try { + return builder.setHandler(manager.start()).build(); + } catch (ServletException e) { + LOG.warn("Failed to start Undertow server on {}://{}:{}, reason: {}", key.getSslContext() != null ? "https" : "http", key.getHost(), key.getPort(), e.getMessage()); + + throw new RuntimeException(e); + } + } + + return builder.setHandler(handler).build(); + } + @Override public synchronized void unregisterHandler(UndertowConsumer consumer, HttpHandlerRegistrationInfo registrationInfo) { if (undertow == null) { diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/spi/UndertowSecurityProvider.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/spi/UndertowSecurityProvider.java index 3d7b1c9..557217f 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/spi/UndertowSecurityProvider.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/spi/UndertowSecurityProvider.java @@ -72,4 +72,12 @@ public interface UndertowSecurityProvider { default HttpHandler wrapHttpHandler(HttpHandler httpHandler) throws Exception { return httpHandler; } + + /** + * Security provider may need for its functionality also working servlet context. + * This feature could be used for example in case of execution of servletFilters for security reasons. + */ + default boolean requireServletContext() { + return false; + } } diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/AbstractProviderServletTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/AbstractProviderServletTest.java new file mode 100644 index 0000000..71e821c --- /dev/null +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/AbstractProviderServletTest.java @@ -0,0 +1,108 @@ +/* + * 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.camel.component.undertow.spi; + +import java.io.File; +import java.io.FileWriter; +import java.io.Writer; +import java.net.URL; +import java.util.List; +import java.util.function.BiConsumer; + +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.util.AttachmentKey; +import io.undertow.util.StatusCodes; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.undertow.BaseUndertowTest; +import org.apache.camel.component.undertow.UndertowComponent; + + + +/** + * Abstract parent for test verifying that UndertowSecurityProvider.requireServletContext really causes servletContext to be created. + */ +public abstract class AbstractProviderServletTest extends BaseUndertowTest { + + private static final AttachmentKey<String> PRINCIPAL_NAME_KEY = AttachmentKey.create(String.class); + + public abstract static class MockSecurityProvider implements UndertowSecurityProvider { + + @Override + public void addHeader(BiConsumer<String, Object> consumer, HttpServerExchange httpExchange) throws Exception { + String principalName = httpExchange.getAttachment(PRINCIPAL_NAME_KEY); + consumer.accept(AbstractSecurityProviderTest.PRINCIPAL_PARAMETER, principalName); + } + + @Override + public int authenticate(HttpServerExchange httpExchange, List<String> allowedRoles) throws Exception { + ServletRequestContext servletRequestContext = httpExchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + assertServletContext(servletRequestContext); + + //put some information into servletContext + httpExchange.putAttachment(PRINCIPAL_NAME_KEY, "user"); + return StatusCodes.OK; + } + + @Override + public boolean acceptConfiguration(Object configuration, String endpointUri) { + return true; + } + + @Override + public abstract boolean requireServletContext(); + + abstract void assertServletContext(ServletRequestContext servletRequestContext); + } + + + static void createSecurtyProviderConfigurationFile(Class<? extends MockSecurityProvider> clazz) throws Exception { + URL location = MockSecurityProvider.class.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(location.getPath() + "META-INF/services/" + UndertowSecurityProvider.class.getName()); + file.getParentFile().mkdirs(); + + Writer output = new FileWriter(file); + output.write(clazz.getName()); + output.close(); + + file.deleteOnExit(); + } + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext camelContext = super.createCamelContext(); + UndertowComponent component = camelContext.getComponent("undertow", UndertowComponent.class); + + //put mock object asconfiguration, it is not used + component.setSecurityConfiguration(new Object()); + return camelContext; + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("undertow:http://localhost:{{port}}/foo?allowedRoles=user") + .to("mock:input") + .transform(simple("${in.header." + AbstractSecurityProviderTest.PRINCIPAL_PARAMETER + "}")); + } + }; + } + +} diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/ProviderWithServletTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/ProviderWithServletTest.java new file mode 100644 index 0000000..d9daad3 --- /dev/null +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/ProviderWithServletTest.java @@ -0,0 +1,60 @@ +/* + * 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.camel.component.undertow.spi; + +import io.undertow.servlet.handlers.ServletRequestContext; +import org.apache.camel.Exchange; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test for case that UndertowSecurityProvider.requireServletContext returns true. + * ServletContext has to be present in httpExchange. + */ +public class ProviderWithServletTest extends AbstractProviderServletTest { + + public static class MockSecurityProvider extends AbstractProviderServletTest.MockSecurityProvider { + + @Override + public boolean requireServletContext() { + return true; + } + + @Override + void assertServletContext(ServletRequestContext servletRequestContext) { + Assert.assertNotNull(servletRequestContext); + } + } + + @BeforeClass + public static void initProvider() throws Exception { + createSecurtyProviderConfigurationFile(org.apache.camel.component.undertow.spi.ProviderWithoutServletTest.MockSecurityProvider.class); + } + + @Test + public void test() throws Exception { + getMockEndpoint("mock:input").expectedHeaderReceived(Exchange.HTTP_METHOD, "GET"); + + String out = template.requestBody("undertow:http://localhost:{{port}}/foo", null, String.class); + + Assert.assertEquals("user", out); + + assertMockEndpointsSatisfied(); + } + +} diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/ProviderWithoutServletTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/ProviderWithoutServletTest.java new file mode 100644 index 0000000..782a871 --- /dev/null +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/spi/ProviderWithoutServletTest.java @@ -0,0 +1,60 @@ +/* + * 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.camel.component.undertow.spi; + +import io.undertow.servlet.handlers.ServletRequestContext; +import org.apache.camel.Exchange; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test for case that UndertowSecurityProvider.requireServletContext returns false. + * ServletContext can not be present in httpExchange. + */ +public class ProviderWithoutServletTest extends AbstractProviderServletTest { + + public static class MockSecurityProvider extends AbstractProviderServletTest.MockSecurityProvider { + + @Override + public boolean requireServletContext() { + return false; + } + + @Override + void assertServletContext(ServletRequestContext servletRequestContext) { + Assert.assertNull(servletRequestContext); + } + } + + @BeforeClass + public static void initProvider() throws Exception { + createSecurtyProviderConfigurationFile(MockSecurityProvider.class); + } + + @Test + public void test() throws Exception { + getMockEndpoint("mock:input").expectedHeaderReceived(Exchange.HTTP_METHOD, "GET"); + + String out = template.requestBody("undertow:http://localhost:{{port}}/foo", null, String.class); + + Assert.assertEquals("user", out); + + assertMockEndpointsSatisfied(); + } + +}