keycloak-uncached
Changes
adapters/oidc/fuse7/camel-undertow/pom.xml 144(+144 -0)
adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakComponent.java 187(+187 -0)
adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakConsumer.java 219(+219 -0)
adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakEndpoint.java 152(+152 -0)
adapters/oidc/fuse7/camel-undertow/src/main/resources/META-INF/services/org/apache/camel/component/undertow-keycloak 1(+1 -0)
adapters/oidc/fuse7/pom.xml 1(+1 -0)
Details
adapters/oidc/fuse7/camel-undertow/pom.xml 144(+144 -0)
diff --git a/adapters/oidc/fuse7/camel-undertow/pom.xml b/adapters/oidc/fuse7/camel-undertow/pom.xml
new file mode 100644
index 0000000..4be87d5
--- /dev/null
+++ b/adapters/oidc/fuse7/camel-undertow/pom.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0"?>
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-fuse7-integration-pom</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>4.0.0.Beta3-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-camel-undertow</artifactId>
+ <name>Keycloak Fuse 7.0 Adapter - Camel + Undertow</name>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <keycloak.osgi.export>
+ org.keycloak.adapters.camel.undertow;version="${project.version}"
+ </keycloak.osgi.export>
+ <keycloak.osgi.import>
+ org.keycloak.*;version="${project.version}",
+ org.apache.camel.*,
+ org.apache.camel.component.undertow,
+ io.undertow.*,
+ *;resolution:=optional
+ </keycloak.osgi.import>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.enterprise</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-runtime</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.web</groupId>
+ <artifactId>pax-web-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-undertow</artifactId>
+ <version>2.21.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core</artifactId>
+ <version>2.21.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+
+ <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>${keycloak.osgi.import}</Import-Package>
+ <Export-Package>${keycloak.osgi.export}</Export-Package>
+ <Export-Service>org.apache.camel.spi.ComponentResolver;component=undertow-keycloak</Export-Service>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakComponent.java b/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakComponent.java
new file mode 100644
index 0000000..9b9219b
--- /dev/null
+++ b/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakComponent.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other 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 org.keycloak.adapters.camel.undertow;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.Processor;
+import org.apache.camel.component.undertow.RestUndertowHttpBinding;
+import org.apache.camel.component.undertow.UndertowComponent;
+import org.apache.camel.component.undertow.UndertowEndpoint;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.HostUtils;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class UndertowKeycloakComponent extends UndertowComponent {
+
+ public UndertowKeycloakComponent() {
+ }
+
+ public UndertowKeycloakComponent(CamelContext context) {
+ super(context);
+ }
+
+ @Override
+ protected UndertowEndpoint createEndpointInstance(URI endpointUri, UndertowComponent component) throws URISyntaxException {
+ return new UndertowKeycloakEndpoint(endpointUri.toString(), component);
+ }
+
+ // TODO: uncomment line below after backport of https://issues.apache.org/jira/browse/CAMEL-12514 into fuse
+// @Override
+ protected String getComponentName() {
+ return "undertow-keycloak";
+ }
+
+ // TODO: remove all below this line after backport of https://issues.apache.org/jira/browse/CAMEL-12514 into fuse
+ @Override
+ public Consumer createConsumer(CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
+ String consumes, String produces, RestConfiguration configuration, Map<String, Object> parameters) throws Exception {
+ return doCreateConsumer(camelContext, processor, verb, basePath, uriTemplate, consumes, produces, configuration, parameters, false);
+ }
+
+ @Override
+ public Consumer createApiConsumer(CamelContext camelContext, Processor processor, String contextPath,
+ RestConfiguration configuration, Map<String, Object> parameters) throws Exception {
+ // reuse the createConsumer method we already have. The api need to use GET and match on uri prefix
+ return doCreateConsumer(camelContext, processor, "GET", contextPath, null, null, null, configuration, parameters, true);
+ }
+
+ Consumer doCreateConsumer(CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
+ String consumes, String produces, RestConfiguration configuration, Map<String, Object> parameters, boolean api) throws Exception {
+ String path = basePath;
+ if (uriTemplate != null) {
+ // make sure to avoid double slashes
+ if (uriTemplate.startsWith("/")) {
+ path = path + uriTemplate;
+ } else {
+ path = path + "/" + uriTemplate;
+ }
+ }
+ path = FileUtil.stripLeadingSeparator(path);
+ String scheme = "http";
+ String host = "";
+ int port = 0;
+
+ RestConfiguration config = configuration;
+ if (config == null) {
+ config = camelContext.getRestConfiguration(getComponentName(), true);
+ }
+ if (config.getScheme() != null) {
+ scheme = config.getScheme();
+ }
+ if (config.getHost() != null) {
+ host = config.getHost();
+ }
+ int num = config.getPort();
+ if (num > 0) {
+ port = num;
+ }
+
+ // prefix path with context-path if configured in rest-dsl configuration
+ String contextPath = config.getContextPath();
+ if (ObjectHelper.isNotEmpty(contextPath)) {
+ contextPath = FileUtil.stripTrailingSeparator(contextPath);
+ contextPath = FileUtil.stripLeadingSeparator(contextPath);
+ if (ObjectHelper.isNotEmpty(contextPath)) {
+ path = contextPath + "/" + path;
+ }
+ }
+
+ // if no explicit hostname set then resolve the hostname
+ if (ObjectHelper.isEmpty(host)) {
+ if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) {
+ host = "0.0.0.0";
+ } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) {
+ host = HostUtils.getLocalHostName();
+ } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) {
+ host = HostUtils.getLocalIp();
+ }
+ }
+
+ Map<String, Object> map = new HashMap<String, Object>();
+ // build query string, and append any endpoint configuration properties
+ if (config.getComponent() == null || config.getComponent().equals(getComponentName())) {
+ // setup endpoint options
+ if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) {
+ map.putAll(config.getEndpointProperties());
+ }
+ }
+
+ boolean explicitOptions = true;
+ // must use upper case for restrict
+ String restrict = verb.toUpperCase(Locale.US);
+ // allow OPTIONS in rest-dsl to allow clients to call the API and have responses with ALLOW headers
+ if (!restrict.contains("OPTIONS")) {
+ restrict += ",OPTIONS";
+ // this is not an explicit OPTIONS path in the rest-dsl
+ explicitOptions = false;
+ }
+
+ boolean cors = config.isEnableCORS();
+ if (cors) {
+ // allow HTTP Options as we want to handle CORS in rest-dsl
+ map.put("optionsEnabled", "true");
+ } else if (explicitOptions) {
+ // the rest-dsl is using OPTIONS
+ map.put("optionsEnabled", "true");
+ }
+
+ String query = URISupport.createQueryString(map);
+
+ String url;
+ if (api) {
+ url = getComponentName() + ":%s://%s:%s/%s?matchOnUriPrefix=true&httpMethodRestrict=%s";
+ } else {
+ url = getComponentName() + ":%s://%s:%s/%s?matchOnUriPrefix=false&httpMethodRestrict=%s";
+ }
+
+ // get the endpoint
+ url = String.format(url, scheme, host, port, path, restrict);
+
+ if (!query.isEmpty()) {
+ url = url + "&" + query;
+ }
+
+ UndertowEndpoint endpoint = camelContext.getEndpoint(url, UndertowEndpoint.class);
+ setProperties(camelContext, endpoint, parameters);
+
+ if (!map.containsKey("undertowHttpBinding")) {
+ // use the rest binding, if not using a custom http binding
+ endpoint.setUndertowHttpBinding(new RestUndertowHttpBinding());
+ }
+
+ // configure consumer properties
+ Consumer consumer = endpoint.createConsumer(processor);
+ if (config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) {
+ setProperties(camelContext, consumer, config.getConsumerProperties());
+ }
+
+ return consumer;
+ }
+}
diff --git a/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakConsumer.java b/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakConsumer.java
new file mode 100644
index 0000000..8ce2330
--- /dev/null
+++ b/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakConsumer.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other 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 org.keycloak.adapters.camel.undertow;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
+import org.keycloak.adapters.undertow.OIDCUndertowHttpFacade;
+import org.keycloak.adapters.undertow.SessionManagementBridge;
+import org.keycloak.adapters.undertow.UndertowCookieTokenStore;
+import org.keycloak.adapters.undertow.UndertowRequestAuthenticator;
+import org.keycloak.adapters.undertow.UndertowSessionTokenStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+import org.keycloak.enums.TokenStore;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.security.impl.SecurityContextImpl;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.InMemorySessionManager;
+import io.undertow.server.session.SessionManager;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.StatusCodes;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import org.apache.camel.Processor;
+import org.apache.camel.component.undertow.UndertowConsumer;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class UndertowKeycloakConsumer extends UndertowConsumer {
+
+ private static final Logger LOG = Logger.getLogger(UndertowKeycloakConsumer.class.getName());
+
+ public static final AttachmentKey<KeycloakPrincipal> KEYCLOAK_PRINCIPAL_KEY = AttachmentKey.create(KeycloakPrincipal.class);
+
+ private static final IdentityManager IDENTITY_MANAGER = new IdentityManager() {
+ @Override
+ public Account verify(Account account) {
+ return account;
+ }
+
+ @Override
+ public Account verify(String id, Credential credential) {
+ throw new IllegalStateException("Should never be called in Keycloak flow");
+ }
+
+ @Override
+ public Account verify(Credential credential) {
+ throw new IllegalStateException("Should never be called in Keycloak flow");
+ }
+ };
+
+ protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
+
+ protected final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
+
+ private final UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
+
+ protected final AdapterDeploymentContext deploymentContext;
+
+ protected final SessionManager sessionManager;
+
+ protected final List<String> allowedRoles;
+
+ private final int confidentialPort;
+
+ private final Pattern skipPattern;
+
+ public UndertowKeycloakConsumer(UndertowKeycloakEndpoint endpoint, Processor processor,
+ AdapterDeploymentContext deploymentContext, Pattern skipPattern, List<String> allowedRoles, int confidentialPort) {
+ super(endpoint, processor);
+ this.sessionManager = new InMemorySessionManager(endpoint.getEndpointUri());
+ this.deploymentContext = deploymentContext;
+ this.skipPattern = skipPattern;
+ this.confidentialPort = confidentialPort;
+ this.allowedRoles = allowedRoles == null ? Collections.<String>emptyList() : allowedRoles;
+ }
+
+ public int getConfidentialPort() {
+ return confidentialPort;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange httpExchange) throws Exception {
+ if (shouldSkip(httpExchange.getRequestPath())) {
+ super.handleRequest(httpExchange);
+ return;
+ }
+
+ //perform only non-blocking operation on exchange
+ if (httpExchange.isInIoThread()) {
+ httpExchange.dispatch(this);
+ return;
+ }
+
+ OIDCUndertowHttpFacade facade = new OIDCUndertowHttpFacade(httpExchange);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+
+ if (deployment == null || !deployment.isConfigured()) {
+ httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
+ LOG.fine("deployment not configured");
+ return;
+ }
+
+ LOG.fine("executing PreAuthActionsHandler");
+ SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
+ PreAuthActionsHandler preAuth = new PreAuthActionsHandler(bridge, deploymentContext, facade);
+ if (preAuth.handleRequest()) return;
+
+ SecurityContext securityContext = httpExchange.getSecurityContext();
+ if (securityContext == null) {
+ securityContext = new SecurityContextImpl(httpExchange, IDENTITY_MANAGER);
+ }
+ AdapterTokenStore tokenStore = getTokenStore(httpExchange, facade, deployment, securityContext);
+ tokenStore.checkCurrentToken();
+
+ LOG.fine("executing AuthenticatedActionsHandler");
+ RequestAuthenticator authenticator = new UndertowRequestAuthenticator(facade, deployment, confidentialPort, securityContext, httpExchange, tokenStore);
+ AuthOutcome outcome = authenticator.authenticate();
+
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ LOG.fine("AUTHENTICATED");
+ if (httpExchange.isResponseComplete()) {
+ return;
+ }
+ AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
+ if (actions.handledRequest()) {
+ return;
+ } else {
+ final Account authenticatedAccount = securityContext.getAuthenticatedAccount();
+ if (authenticatedAccount instanceof KeycloakUndertowAccount) {
+ final KeycloakUndertowAccount kua = (KeycloakUndertowAccount) authenticatedAccount;
+ httpExchange.putAttachment(KEYCLOAK_PRINCIPAL_KEY, (KeycloakPrincipal) kua.getPrincipal());
+ }
+
+ Set<String> roles = Optional
+ .ofNullable(authenticatedAccount.getRoles())
+ .orElse((Set<String>) Collections.EMPTY_SET);
+
+ LOG.log(Level.FINE, "Allowed roles: {0}, current roles: {1}", new Object[] {allowedRoles, roles});
+
+ if (isRoleAllowed(roles, httpExchange)) {
+ super.handleRequest(httpExchange);
+ } else {
+ httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
+ }
+
+ return;
+ }
+ }
+
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ LOG.fine("challenge");
+ challenge.challenge(facade);
+ return;
+ }
+
+ httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
+ }
+
+ public boolean isRoleAllowed(Set<String> roles, HttpServerExchange httpExchange) throws Exception {
+ for (String role : allowedRoles) {
+ if (roles.contains(role)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ return new UndertowSessionTokenStore(exchange, deployment, userSessionManagement, securityContext);
+ } else {
+ return new UndertowCookieTokenStore(facade, deployment, securityContext);
+ }
+ }
+
+ private boolean shouldSkip(String requestPath) {
+ return skipPattern != null && skipPattern.matcher(requestPath).matches();
+ }
+
+}
diff --git a/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakEndpoint.java b/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakEndpoint.java
new file mode 100644
index 0000000..a98d5ab
--- /dev/null
+++ b/adapters/oidc/fuse7/camel-undertow/src/main/java/org/keycloak/adapters/camel/undertow/UndertowKeycloakEndpoint.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other 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 org.keycloak.adapters.camel.undertow;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import io.undertow.server.HttpServerExchange;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import org.apache.camel.Consumer;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.component.undertow.UndertowComponent;
+import org.apache.camel.component.undertow.UndertowEndpoint;
+import static org.keycloak.adapters.camel.undertow.UndertowKeycloakConsumer.KEYCLOAK_PRINCIPAL_KEY;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class UndertowKeycloakEndpoint extends UndertowEndpoint {
+
+ private static final Logger LOG = Logger.getLogger(UndertowKeycloakEndpoint.class.getName());
+
+ private KeycloakConfigResolver configResolver;
+
+ private AdapterConfig adapterConfig;
+
+ private String skipPattern;
+
+ private List<String> allowedRoles = Collections.emptyList();
+
+ private int confidentialPort = 8443;
+
+ public UndertowKeycloakEndpoint(String uri, UndertowComponent component) {
+ super(uri, component);
+ }
+
+ public AdapterConfig getAdapterConfig() {
+ return adapterConfig;
+ }
+
+ public void setAdapterConfig(AdapterConfig adapterConfig) {
+ LOG.info("adapterConfig");
+ this.adapterConfig = adapterConfig;
+ }
+
+ public String getSkipPattern() {
+ return skipPattern;
+ }
+
+ public void setSkipPattern(String skipPattern) {
+ this.skipPattern = skipPattern;
+ }
+
+ public List<String> getAllowedRoles() {
+ return allowedRoles;
+ }
+
+ public void setAllowedRoles(List<String> allowedRoles) {
+ this.allowedRoles = allowedRoles;
+ }
+
+ public void setAllowedRoles(String allowedRoles) {
+ this.allowedRoles = allowedRoles == null ? null : Arrays.asList(allowedRoles.split("\\s*,\\s*"));
+ }
+
+ public int getConfidentialPort() {
+ return confidentialPort;
+ }
+
+ public void setConfidentialPort(int confidentialPort) {
+ this.confidentialPort = confidentialPort;
+ }
+
+ public KeycloakConfigResolver getConfigResolver() {
+ return configResolver;
+ }
+
+ public void setConfigResolver(KeycloakConfigResolver configResolver) {
+ this.configResolver = configResolver;
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) throws Exception {
+ return new UndertowKeycloakConsumer(this, processor, getDeploymentContext(), getSkipPatternAsPattern(), computeAllowedRoles(), this.confidentialPort);
+ }
+
+ public List<String> computeAllowedRoles() {
+ List<String> res = this.allowedRoles == null ? Collections.<String>emptyList() : this.allowedRoles;
+ if (res.isEmpty()) {
+ LOG.warning("No roles were configured, Keycloak will deny every request");
+ }
+ LOG.log(Level.FINE, "Allowed roles: {0}", res);
+ return res;
+ }
+
+ @Override
+ public Exchange createExchange(HttpServerExchange httpExchange) throws Exception {
+ final Exchange res = super.createExchange(httpExchange);
+
+ KeycloakPrincipal principal = httpExchange.getAttachment(KEYCLOAK_PRINCIPAL_KEY);
+ LOG.log(Level.FINE, "principal: {0}", principal);
+ if (principal != null) {
+ res.setProperty(KeycloakPrincipal.class.getName(), principal);
+ }
+
+ return res;
+ }
+
+ private AdapterDeploymentContext getDeploymentContext() {
+ if (configResolver != null) {
+ LOG.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolver.getClass());
+ return new AdapterDeploymentContext(configResolver);
+ } else if (adapterConfig != null) {
+ KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig);
+ return new AdapterDeploymentContext(kd);
+ }
+
+ LOG.warning("Adapter is unconfigured, Keycloak will deny every request");
+ return new AdapterDeploymentContext();
+ }
+
+ private Pattern getSkipPatternAsPattern() {
+ return skipPattern == null
+ ? null
+ : Pattern.compile(skipPattern, Pattern.DOTALL);
+ }
+}
diff --git a/adapters/oidc/fuse7/camel-undertow/src/main/resources/META-INF/services/org/apache/camel/component/undertow-keycloak b/adapters/oidc/fuse7/camel-undertow/src/main/resources/META-INF/services/org/apache/camel/component/undertow-keycloak
new file mode 100644
index 0000000..145c1f8
--- /dev/null
+++ b/adapters/oidc/fuse7/camel-undertow/src/main/resources/META-INF/services/org/apache/camel/component/undertow-keycloak
@@ -0,0 +1 @@
+class=org.keycloak.adapters.camel.undertow.UndertowKeycloakComponent
\ No newline at end of file
adapters/oidc/fuse7/pom.xml 1(+1 -0)
diff --git a/adapters/oidc/fuse7/pom.xml b/adapters/oidc/fuse7/pom.xml
index d289c1f..fad9726 100644
--- a/adapters/oidc/fuse7/pom.xml
+++ b/adapters/oidc/fuse7/pom.xml
@@ -37,6 +37,7 @@
</properties>
<modules>
+ <module>camel-undertow</module>
<module>undertow</module>
</modules>
</project>
diff --git a/distribution/adapters/osgi/features/src/main/resources/features.xml b/distribution/adapters/osgi/features/src/main/resources/features.xml
index 00207f3..c510990 100755
--- a/distribution/adapters/osgi/features/src/main/resources/features.xml
+++ b/distribution/adapters/osgi/features/src/main/resources/features.xml
@@ -70,6 +70,7 @@
<bundle>mvn:org.keycloak/keycloak-undertow-adapter/${project.version}</bundle>
<bundle>mvn:org.keycloak/keycloak-undertow-adapter-spi/${project.version}</bundle>
<bundle>mvn:org.keycloak/keycloak-pax-web-undertow/${project.version}</bundle>
+ <bundle>mvn:org.keycloak/keycloak-camel-undertow/${project.version}</bundle>
</feature>
<feature name="keycloak-jaas" version="${project.version}" resolver="(obr)">