/* * 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. */ package org.keycloak.testsuite.rule; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.ErrorPage; import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.WebResourceCollection; import org.jboss.resteasy.spi.ResteasyDeployment; import org.junit.rules.ExternalResource; import org.junit.rules.TemporaryFolder; import org.keycloak.Config; import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.servlet.KeycloakOIDCFilter; import org.keycloak.common.util.Time; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.KeycloakServer; import org.keycloak.testsuite.Retry; import org.keycloak.util.JsonSerialization; import javax.servlet.DispatcherType; import javax.servlet.Servlet; import javax.ws.rs.core.Application; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.Map; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public abstract class AbstractKeycloakRule extends ExternalResource { protected TemporaryFolder temporaryFolder; protected KeycloakServer server; protected void before() throws Throwable { temporaryFolder = new TemporaryFolder(); temporaryFolder.create(); System.setProperty("keycloak.tmp.dir", temporaryFolder.newFolder().getAbsolutePath()); server = new KeycloakServer(); configureServer(server); server.start(); removeTestRealms(); setupKeycloak(); } protected void configureServer(KeycloakServer server) { } public UserRepresentation getUser(String realm, String name) { KeycloakSession session = server.getSessionFactory().create(); session.getTransactionManager().begin(); try { RealmModel realmByName = session.realms().getRealmByName(realm); UserModel user = session.users().getUserByUsername(name, realmByName); UserRepresentation userRep = user != null ? ModelToRepresentation.toRepresentation(session, realmByName, user) : null; session.getTransactionManager().commit(); return userRep; } finally { session.close(); } } public UserRepresentation getUserById(String realm, String id) { KeycloakSession session = server.getSessionFactory().create(); session.getTransactionManager().begin(); try { RealmModel realmByName = session.realms().getRealmByName(realm); UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realmByName, session.users().getUserById(id, realmByName)); session.getTransactionManager().commit(); return userRep; } finally { session.close(); } } protected void setupKeycloak() { KeycloakSession session = server.getSessionFactory().create(); session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); RealmModel adminstrationRealm = manager.getRealm(Config.getAdminRealm()); configure(session, manager, adminstrationRealm); session.getTransactionManager().commit(); } finally { session.close(); } } public void update(KeycloakRule.KeycloakSetup configurer, String realmId) { KeycloakSession session = server.getSessionFactory().create(); session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); manager.setContextPath("/auth"); RealmModel adminstrationRealm = manager.getRealm(Config.getAdminRealm()); RealmModel appRealm = manager.getRealm(realmId); configurer.session = session; configurer.config(manager, adminstrationRealm, appRealm); session.getTransactionManager().commit(); } finally { session.close(); } } protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { } public void deployServlet(String name, String contextPath, Class<? extends Servlet> servletClass) { DeploymentInfo deploymentInfo = createDeploymentInfo(name, contextPath, servletClass); server.getServer().deploy(deploymentInfo); } public DeploymentInfo createDeploymentInfo(String name, String contextPath, Class<? extends Servlet> servletClass) { DeploymentInfo deploymentInfo = new DeploymentInfo(); deploymentInfo.setClassLoader(getClass().getClassLoader()); deploymentInfo.setDeploymentName(name); deploymentInfo.setContextPath(contextPath); ServletInfo servlet = new ServletInfo(servletClass.getSimpleName(), servletClass); servlet.addMapping("/*"); deploymentInfo.addServlet(servlet); return deploymentInfo; } public DeploymentBuilder createApplicationDeployment() { return new DeploymentBuilder(); } public void addErrorPage(String errorPage, DeploymentInfo di) { ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class); servlet.addMapping("/error.html"); SecurityConstraint constraint = new SecurityConstraint(); WebResourceCollection collection = new WebResourceCollection(); collection.addUrlPattern("/error.html"); constraint.addWebResourceCollection(collection); constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT); di.addSecurityConstraint(constraint); di.addServlet(servlet); di .addErrorPage(new ErrorPage(errorPage, 400)) .addErrorPage(new ErrorPage(errorPage, 401)) .addErrorPage(new ErrorPage(errorPage, 403)) .addErrorPage(new ErrorPage(errorPage, 500)); } public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) { ResteasyDeployment deployment = new ResteasyDeployment(); deployment.setApplicationClass(applicationClass.getName()); DeploymentInfo di = server.getServer().undertowDeployment(deployment, ""); di.setClassLoader(getClass().getClassLoader()); di.setContextPath(contextPath); di.setDeploymentName(name); for (Map.Entry<String,String> param : initParams.entrySet()) { di.addInitParameter(param.getKey(), param.getValue()); } server.getServer().deploy(di); } @Override protected void after() { removeTestRealms(); stopServer(); Time.setOffset(0); temporaryFolder.delete(); System.getProperties().remove("keycloak.tmp.dir"); } protected void removeTestRealms() { KeycloakSession session = server.getSessionFactory().create(); try { session.getTransactionManager().begin(); RealmManager realmManager = new RealmManager(session); for (String realmName : getTestRealms()) { RealmModel realm = realmManager.getRealmByName(realmName); if (realm != null) { realmManager.removeRealm(realm); } } session.getTransactionManager().commit(); } finally { session.close(); } } public RealmRepresentation loadJson(String path) throws IOException { InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); ByteArrayOutputStream os = new ByteArrayOutputStream(); int c; while ((c = is.read()) != -1) { os.write(c); } byte[] bytes = os.toByteArray(); return JsonSerialization.readValue(bytes, RealmRepresentation.class); } public KeycloakSession startSession() { KeycloakSession session = server.getSessionFactory().create(); session.getTransactionManager().begin(); return session; } public void stopSession(KeycloakSession session, boolean commit) { KeycloakTransaction transaction = session.getTransactionManager(); if (commit && !transaction.getRollbackOnly()) { transaction.commit(); } else { transaction.rollback(); } session.close(); } public void restartServer() { try { stopServer(); server.start(); } catch (Throwable t) { throw new RuntimeException(t); } } private void stopServer() { server.stop(); // Add some variable delay (Some windows envs have issues as server is not stopped immediately after server.stop) try { Retry.execute(new Runnable() { @Override public void run() { try { Socket s = new Socket(server.getConfig().getHost(), server.getConfig().getPort()); s.close(); throw new IllegalStateException("Server still running"); } catch (IOException expected) { } } }, 10, 500); Thread.sleep(100); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } protected String[] getTestRealms() { return new String[]{"test", "demo"}; } public class DeploymentBuilder { private String name; private String contextPath; private Class<? extends Servlet> servletClass; private String adapterConfigPath; private String role; private boolean isConstrained = true; private Class<? extends KeycloakConfigResolver> keycloakConfigResolver; private String constraintUrl = "/*"; private String errorPage = "/error.html"; public DeploymentBuilder name(String name) { this.name = name; return this; } public DeploymentBuilder contextPath(String contextPath) { this.contextPath = contextPath; return this; } public DeploymentBuilder servletClass(Class<? extends Servlet> servletClass) { this.servletClass = servletClass; return this; } public DeploymentBuilder adapterConfigPath(String adapterConfigPath) { this.adapterConfigPath = adapterConfigPath; return this; } public DeploymentBuilder role(String role) { this.role = role; return this; } public DeploymentBuilder isConstrained(boolean isConstrained) { this.isConstrained = isConstrained; return this; } public DeploymentBuilder keycloakConfigResolver(Class<? extends KeycloakConfigResolver> keycloakConfigResolver) { this.keycloakConfigResolver = keycloakConfigResolver; return this; } public DeploymentBuilder constraintUrl(String constraintUrl) { this.constraintUrl = constraintUrl; return this; } public DeploymentBuilder errorPage(String errorPage) { this.errorPage = errorPage; return this; } public void deployApplication() { DeploymentInfo di = createDeploymentInfo(name, contextPath, servletClass); if (null == keycloakConfigResolver) { di.addInitParameter("keycloak.config.file", adapterConfigPath); } else { di.addInitParameter("keycloak.config.resolver", keycloakConfigResolver.getCanonicalName()); } if (isConstrained) { SecurityConstraint constraint = new SecurityConstraint(); WebResourceCollection collection = new WebResourceCollection(); collection.addUrlPattern(constraintUrl); constraint.addWebResourceCollection(collection); constraint.addRoleAllowed(role); di.addSecurityConstraint(constraint); } LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, null); di.setLoginConfig(loginConfig); addErrorPage(errorPage, di); server.getServer().deploy(di); } public void deployApplicationWithFilter() { DeploymentInfo di = createDeploymentInfo(name, contextPath, servletClass); FilterInfo filter = new FilterInfo("keycloak-filter", KeycloakOIDCFilter.class); if (null == keycloakConfigResolver) { filter.addInitParam("keycloak.config.file", adapterConfigPath); } else { filter.addInitParam("keycloak.config.resolver", keycloakConfigResolver.getCanonicalName()); } di.addFilter(filter); di.addFilterUrlMapping("keycloak-filter", constraintUrl, DispatcherType.REQUEST); server.getServer().deploy(di); } } }