KEYCLOAK-7279 Camel/Undertow integration
This commit is contained in:
parent
d55b1d7259
commit
cace03c3cc
7 changed files with 705 additions and 0 deletions
144
adapters/oidc/fuse7/camel-undertow/pom.xml
Normal file
144
adapters/oidc/fuse7/camel-undertow/pom.xml
Normal file
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
class=org.keycloak.adapters.camel.undertow.UndertowKeycloakComponent
|
|
@ -37,6 +37,7 @@
|
|||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>camel-undertow</module>
|
||||
<module>undertow</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -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)">
|
||||
|
|
Loading…
Reference in a new issue