KEYCLOAK-17693 add config for loading custom IdMapper class
This commit is contained in:
parent
a0b01b6ef4
commit
d8cb279bc4
6 changed files with 218 additions and 56 deletions
|
@ -43,6 +43,9 @@ import java.io.FileInputStream;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -58,6 +61,8 @@ public class KeycloakOIDCFilter implements Filter {
|
|||
|
||||
public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";
|
||||
|
||||
public static final String ID_MAPPER_PARAM = "keycloak.config.idMapper";
|
||||
|
||||
public static final String CONFIG_RESOLVER_PARAM = "keycloak.config.resolver";
|
||||
|
||||
public static final String CONFIG_FILE_PARAM = "keycloak.config.file";
|
||||
|
@ -94,6 +99,28 @@ public class KeycloakOIDCFilter implements Filter {
|
|||
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
String idMapperClassName = filterConfig.getInitParameter(ID_MAPPER_PARAM);
|
||||
if (idMapperClassName != null) {
|
||||
try {
|
||||
final Class<?> idMapperClass = getClass().getClassLoader().loadClass(idMapperClassName);
|
||||
final Constructor<?> idMapperConstructor = idMapperClass.getDeclaredConstructor();
|
||||
Object idMapperInstance = null;
|
||||
// for KEYCLOAK-13745 test
|
||||
if (idMapperConstructor.getModifiers() == Modifier.PRIVATE) {
|
||||
idMapperInstance = idMapperClass.getMethod("getInstance").invoke(null);
|
||||
} else {
|
||||
idMapperInstance = idMapperConstructor.newInstance();
|
||||
}
|
||||
if(idMapperInstance instanceof SessionIdMapper) {
|
||||
this.idMapper = (SessionIdMapper) idMapperInstance;
|
||||
} else {
|
||||
log.log(Level.WARNING, "SessionIdMapper class {0} is not instance of org.keycloak.adapters.spi.SessionIdMapper", idMapperClassName);
|
||||
}
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
log.log(Level.WARNING, "SessionIdMapper class could not be instanced", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (definedconfigResolver != null) {
|
||||
deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
|
||||
|
@ -160,25 +187,7 @@ public class KeycloakOIDCFilter implements Filter {
|
|||
return;
|
||||
}
|
||||
|
||||
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
|
||||
@Override
|
||||
public void logoutAll() {
|
||||
if (idMapper != null) {
|
||||
idMapper.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutHttpSessions(List<String> ids) {
|
||||
log.fine("**************** logoutHttpSessions");
|
||||
//System.err.println("**************** logoutHttpSessions");
|
||||
for (String id : ids) {
|
||||
log.finest("removed idMapper: " + id);
|
||||
idMapper.removeSession(id);
|
||||
}
|
||||
|
||||
}
|
||||
}, deploymentContext, facade);
|
||||
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new IdMapperUserSessionManagement(), deploymentContext, facade);
|
||||
|
||||
if (preActions.handleRequest()) {
|
||||
//System.err.println("**************** preActions.handleRequest happened!");
|
||||
|
@ -241,4 +250,24 @@ public class KeycloakOIDCFilter implements Filter {
|
|||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
private class IdMapperUserSessionManagement implements UserSessionManagement {
|
||||
@Override
|
||||
public void logoutAll() {
|
||||
if (idMapper != null) {
|
||||
idMapper.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutHttpSessions(List<String> ids) {
|
||||
log.fine("**************** logoutHttpSessions");
|
||||
//System.err.println("**************** logoutHttpSessions");
|
||||
for (String id : ids) {
|
||||
log.finest("removed idMapper: " + id);
|
||||
idMapper.removeSession(id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package org.keycloak.testsuite.adapter.spi;
|
||||
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class TestSessionIdMapper implements SessionIdMapper {
|
||||
|
||||
private static final TestSessionIdMapper SINGLETON = new TestSessionIdMapper();
|
||||
|
||||
private static Set<String> whoCalled = new HashSet<>();
|
||||
|
||||
private TestSessionIdMapper() {
|
||||
}
|
||||
|
||||
public boolean isCalledBy(String className) {
|
||||
return whoCalled.contains(className);
|
||||
}
|
||||
|
||||
public static TestSessionIdMapper getInstance() {
|
||||
StackTraceElement[] ste = (new Throwable()).getStackTrace();
|
||||
for (int i = 0; i < ste.length; i++) {
|
||||
whoCalled.add(ste[i].getClassName());
|
||||
}
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
whoCalled.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getUserSessions(String principal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSessionFromSSO(String sso) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(String sso, String principal, String session) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSession(String session) {
|
||||
}
|
||||
}
|
|
@ -250,23 +250,8 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
|
|||
}
|
||||
|
||||
appendChildInDocument(webXmlDoc, "web-app", filter);
|
||||
|
||||
// Limitation that all deployments of annotated class use same skipPattern. Refactor if something more flexible is needed (would require more tricky web.xml parsing though...)
|
||||
String skipPattern = testClass.getAnnotation(UseServletFilter.class).skipPattern();
|
||||
if (skipPattern != null && !skipPattern.isEmpty()) {
|
||||
Element initParam = webXmlDoc.createElement("init-param");
|
||||
|
||||
Element paramName = webXmlDoc.createElement("param-name");
|
||||
paramName.setTextContent(KeycloakOIDCFilter.SKIP_PATTERN_PARAM);
|
||||
|
||||
Element paramValue = webXmlDoc.createElement("param-value");
|
||||
paramValue.setTextContent(skipPattern);
|
||||
|
||||
initParam.appendChild(paramName);
|
||||
initParam.appendChild(paramValue);
|
||||
|
||||
filter.appendChild(initParam);
|
||||
}
|
||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.SKIP_PATTERN_PARAM, testClass.getAnnotation(UseServletFilter.class).skipPattern());
|
||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.ID_MAPPER_PARAM, testClass.getAnnotation(UseServletFilter.class).idMapper());
|
||||
|
||||
appendChildInDocument(webXmlDoc, "web-app", filter);
|
||||
|
||||
|
@ -299,6 +284,24 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
|
|||
archive.add(new StringAsset((documentToString(webXmlDoc))), WEBXML_PATH);
|
||||
}
|
||||
|
||||
private void addInitParam(Document webXmlDoc, Element filter, String initParamName, String initParamValue) {
|
||||
// Limitation that all deployments of annotated class use same skipPattern. Refactor if something more flexible is needed (would require more tricky web.xml parsing though...)
|
||||
if (initParamValue != null && !initParamValue.isEmpty()) {
|
||||
Element initParam = webXmlDoc.createElement("init-param");
|
||||
|
||||
Element paramName = webXmlDoc.createElement("param-name");
|
||||
paramName.setTextContent(initParamName);
|
||||
|
||||
Element paramValue = webXmlDoc.createElement("param-value");
|
||||
paramValue.setTextContent(initParamValue);
|
||||
|
||||
initParam.appendChild(paramName);
|
||||
initParam.appendChild(paramValue);
|
||||
|
||||
filter.appendChild(initParam);
|
||||
}
|
||||
}
|
||||
|
||||
private String getKeycloakResolverClass(Document doc) {
|
||||
try {
|
||||
XPathFactory factory = XPathFactory.newInstance();
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.testsuite.adapter.servlet;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
||||
import org.keycloak.testsuite.adapter.page.CustomerPortal;
|
||||
import org.keycloak.testsuite.adapter.spi.TestSessionIdMapper;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||
import org.keycloak.testsuite.util.JavascriptBrowser;
|
||||
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
||||
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
||||
@UseServletFilter(filterName = "oidc-filter", filterClass = "org.keycloak.adapters.servlet.KeycloakOIDCFilter",
|
||||
filterDependency = "org.keycloak:keycloak-servlet-filter-adapter", skipPattern = "/error.html",
|
||||
idMapper = "org.keycloak.testsuite.adapter.spi.TestSessionIdMapper")
|
||||
public class DemoFilterServletAdapterTestForCustomizedIdMapper extends AbstractServletsAdapterTest {
|
||||
|
||||
@Drone
|
||||
@JavascriptBrowser
|
||||
protected WebDriver jsDriver;
|
||||
|
||||
@Page
|
||||
protected CustomerPortal customerPortal;
|
||||
|
||||
@Deployment(name = CustomerPortal.DEPLOYMENT_NAME)
|
||||
protected static WebArchive customerPortal() {
|
||||
return servletDeployment(CustomerPortal.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class);
|
||||
}
|
||||
|
||||
// KEYCLOAK-13745
|
||||
@Test
|
||||
public void testCustomizedSessionIdMapper() {
|
||||
customerPortal.navigateTo();
|
||||
TestSessionIdMapper singleton = TestSessionIdMapper.getInstance();
|
||||
assertTrue(singleton.isCalledBy(getClass().getAnnotation(UseServletFilter.class).filterClass()));
|
||||
singleton.clear();
|
||||
}
|
||||
}
|
|
@ -23,4 +23,5 @@ public @interface UseServletFilter {
|
|||
String filterPattern() default "/*";
|
||||
String dispatcherType() default "";
|
||||
String skipPattern() default "";
|
||||
String idMapper() default "";
|
||||
}
|
||||
|
|
|
@ -124,21 +124,9 @@ public class DeploymentArchiveProcessorUtils {
|
|||
|
||||
// Limitation that all deployments of annotated class use same skipPattern. Refactor if
|
||||
// something more flexible is needed (would require more tricky web.xml parsing though...)
|
||||
String skipPattern = testClass.getAnnotation(UseServletFilter.class).skipPattern();
|
||||
if (skipPattern != null && !skipPattern.isEmpty()) {
|
||||
Element initParam = webXmlDoc.createElement("init-param");
|
||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.SKIP_PATTERN_PARAM, testClass.getAnnotation(UseServletFilter.class).skipPattern());
|
||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.ID_MAPPER_PARAM, testClass.getAnnotation(UseServletFilter.class).idMapper());
|
||||
|
||||
Element paramName = webXmlDoc.createElement("param-name");
|
||||
paramName.setTextContent(KeycloakOIDCFilter.SKIP_PATTERN_PARAM);
|
||||
|
||||
Element paramValue = webXmlDoc.createElement("param-value");
|
||||
paramValue.setTextContent(skipPattern);
|
||||
|
||||
initParam.appendChild(paramName);
|
||||
initParam.appendChild(paramValue);
|
||||
|
||||
filter.appendChild(initParam);
|
||||
}
|
||||
|
||||
IOUtil.appendChildInDocument(webXmlDoc, "web-app", filter);
|
||||
|
||||
|
@ -169,6 +157,24 @@ public class DeploymentArchiveProcessorUtils {
|
|||
archive.add(new StringAsset((IOUtil.documentToString(webXmlDoc))), WEBXML_PATH);
|
||||
}
|
||||
|
||||
private static void addInitParam(Document webXmlDoc, Element filter, String initParamName, String initParamValue) {
|
||||
// Limitation that all deployments of annotated class use same skipPattern. Refactor if something more flexible is needed (would require more tricky web.xml parsing though...)
|
||||
if (initParamValue != null && !initParamValue.isEmpty()) {
|
||||
Element initParam = webXmlDoc.createElement("init-param");
|
||||
|
||||
Element paramName = webXmlDoc.createElement("param-name");
|
||||
paramName.setTextContent(initParamName);
|
||||
|
||||
Element paramValue = webXmlDoc.createElement("param-value");
|
||||
paramValue.setTextContent(initParamValue);
|
||||
|
||||
initParam.appendChild(paramName);
|
||||
initParam.appendChild(paramValue);
|
||||
|
||||
filter.appendChild(initParam);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getKeycloakResolverClass(Document doc) {
|
||||
try {
|
||||
XPathFactory factory = XPathFactory.newInstance();
|
||||
|
|
Loading…
Reference in a new issue