parent
3119566407
commit
33cb1ad7cd
21 changed files with 810 additions and 350 deletions
|
@ -3,7 +3,7 @@ package org.keycloak.quarkus.deployment;
|
||||||
import io.quarkus.deployment.IsTest;
|
import io.quarkus.deployment.IsTest;
|
||||||
import io.quarkus.runtime.LaunchMode;
|
import io.quarkus.runtime.LaunchMode;
|
||||||
|
|
||||||
import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
|
|
||||||
public class IsIntegrationTest extends IsTest {
|
public class IsIntegrationTest extends IsTest {
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ public class IsIntegrationTest extends IsTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getAsBoolean() {
|
public boolean getAsBoolean() {
|
||||||
return super.getAsBoolean() && (System.getProperty(LAUNCH_MODE) != null && System.getProperty(LAUNCH_MODE).equals("test"));
|
return super.getAsBoolean() && Environment.isTestLaunchMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.keycloak.quarkus.runtime;
|
package org.keycloak.quarkus.runtime;
|
||||||
|
|
||||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
|
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
|
||||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfig;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
@ -177,10 +176,6 @@ public final class Environment {
|
||||||
})).collect(Collectors.toMap(File::getName, Function.identity()));
|
})).collect(Collectors.toMap(File::getName, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isQuarkusDevMode() {
|
|
||||||
return ProfileManager.getLaunchMode().equals(LaunchMode.DEVELOPMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTestLaunchMode() {
|
public static boolean isTestLaunchMode() {
|
||||||
return "test".equals(System.getProperty(LAUNCH_MODE));
|
return "test".equals(System.getProperty(LAUNCH_MODE));
|
||||||
}
|
}
|
||||||
|
@ -220,7 +215,7 @@ public final class Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDistribution() {
|
public static boolean isDistribution() {
|
||||||
if (isQuarkusDevMode()) {
|
if (LaunchMode.current().isDevOrTest()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return getHomeDir() != null;
|
return getHomeDir() != null;
|
||||||
|
@ -233,4 +228,8 @@ public final class Environment {
|
||||||
public static boolean isRebuilt() {
|
public static boolean isRebuilt() {
|
||||||
return Boolean.getBoolean("kc.config.built");
|
return Boolean.getBoolean("kc.config.built");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setHomeDir(Path path) {
|
||||||
|
System.setProperty("kc.home.dir", path.toFile().getAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,7 @@ import io.quarkus.runtime.Quarkus;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakTransactionManager;
|
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
|
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
|
||||||
import org.keycloak.quarkus.runtime.cli.Picocli;
|
import org.keycloak.quarkus.runtime.cli.Picocli;
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.quarkus.runtime.configuration;
|
package org.keycloak.quarkus.runtime.configuration;
|
||||||
|
|
||||||
import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores;
|
|
||||||
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
|
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
|
||||||
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
|
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
|
||||||
|
|
||||||
|
@ -156,6 +155,22 @@ public final class Configuration {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String replaceNonAlphanumericByUnderscores(String name) {
|
||||||
|
int length = name.length();
|
||||||
|
StringBuilder sb = new StringBuilder(length);
|
||||||
|
|
||||||
|
for(int i = 0; i < length; ++i) {
|
||||||
|
char c = name.charAt(i);
|
||||||
|
if (('a' > c || c > 'z') && ('A' > c || c > 'Z') && ('0' > c || c > '9')) {
|
||||||
|
sb.append('_');
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private static String getValue(ConfigSource configSource, String name) {
|
private static String getValue(ConfigSource configSource, String name) {
|
||||||
String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name)));
|
String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name)));
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,12 @@
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-junit5-internal</artifactId>
|
<artifactId>quarkus-junit5-internal</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-quarkus-dist</artifactId>
|
<artifactId>keycloak-quarkus-dist</artifactId>
|
||||||
|
@ -100,6 +106,10 @@
|
||||||
<groupId>org.testcontainers</groupId>
|
<groupId>org.testcontainers</groupId>
|
||||||
<artifactId>mysql</artifactId>
|
<artifactId>mysql</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.wagon</groupId>
|
||||||
|
<artifactId>wagon-http-shared</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package org.junit.rules;
|
|
||||||
|
|
||||||
// WORKAROUND: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public interface TestRule {
|
|
||||||
}
|
|
|
@ -0,0 +1,237 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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;
|
||||||
|
|
||||||
|
import static java.util.concurrent.CompletableFuture.runAsync;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||||
|
import org.keycloak.common.Version;
|
||||||
|
import org.keycloak.platform.Platform;
|
||||||
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
|
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
||||||
|
|
||||||
|
import io.quarkus.bootstrap.app.AugmentAction;
|
||||||
|
import io.quarkus.bootstrap.app.CuratedApplication;
|
||||||
|
import io.quarkus.bootstrap.app.QuarkusBootstrap;
|
||||||
|
import io.quarkus.bootstrap.app.RunningQuarkusApplication;
|
||||||
|
import io.quarkus.bootstrap.app.StartupAction;
|
||||||
|
import io.quarkus.bootstrap.model.ApplicationModel;
|
||||||
|
import io.quarkus.bootstrap.resolver.AppModelResolverException;
|
||||||
|
import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
|
||||||
|
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
|
||||||
|
import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
|
||||||
|
import io.quarkus.bootstrap.workspace.WorkspaceModule;
|
||||||
|
import io.quarkus.bootstrap.workspace.WorkspaceModuleId;
|
||||||
|
import io.quarkus.maven.dependency.Dependency;
|
||||||
|
import io.quarkus.maven.dependency.DependencyBuilder;
|
||||||
|
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
|
||||||
|
|
||||||
|
public class Keycloak {
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private String version;
|
||||||
|
private Path homeDir;
|
||||||
|
private List<Dependency> dependencies = new ArrayList<>();
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setVersion(String version) {
|
||||||
|
this.version = version;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setHomeDir(Path path) {
|
||||||
|
this.homeDir = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addDependency(String groupId, String artifactId, String version) {
|
||||||
|
addDependency(groupId, artifactId, version, null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addDependency(String groupId, String artifactId, String version, String classifier) {
|
||||||
|
this.dependencies.add(DependencyBuilder.newInstance()
|
||||||
|
.setGroupId(groupId)
|
||||||
|
.setArtifactId(artifactId)
|
||||||
|
.setVersion(version)
|
||||||
|
.setClassifier(classifier)
|
||||||
|
.build());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Keycloak start(String... args) {
|
||||||
|
return start(List.of(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Keycloak start(List<String> args) {
|
||||||
|
if (homeDir == null) {
|
||||||
|
homeDir = Platform.getPlatform().getTmpDirectory().toPath();
|
||||||
|
}
|
||||||
|
return new Keycloak(homeDir, version, dependencies).start(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RunningQuarkusApplication application;
|
||||||
|
private ApplicationModel applicationModel;
|
||||||
|
private Path homeDir;
|
||||||
|
private List<Dependency> dependencies;
|
||||||
|
|
||||||
|
public Keycloak() {
|
||||||
|
this(null, Version.VERSION, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Keycloak(Path homeDir, String version, List<Dependency> dependencies) {
|
||||||
|
this.homeDir = homeDir;
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
try {
|
||||||
|
applicationModel = createApplicationModel(version);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Keycloak start(List<String> args) {
|
||||||
|
QuarkusBootstrap.Builder builder = QuarkusBootstrap.builder()
|
||||||
|
.setExistingModel(applicationModel)
|
||||||
|
.setApplicationRoot(applicationModel.getApplicationModule().getModuleDir().toPath())
|
||||||
|
.setTargetDirectory(applicationModel.getApplicationModule().getModuleDir().toPath())
|
||||||
|
.setIsolateDeployment(true)
|
||||||
|
.setMode(QuarkusBootstrap.Mode.TEST);
|
||||||
|
|
||||||
|
try (CuratedApplication curated = builder.build().bootstrap()) {
|
||||||
|
AugmentAction action = curated.createAugmentor();
|
||||||
|
Environment.setHomeDir(homeDir);
|
||||||
|
ConfigArgsConfigSource.setCliArgs(args.toArray(new String[0]));
|
||||||
|
|
||||||
|
StartupAction startupAction = action.createInitialRuntimeApplication();
|
||||||
|
|
||||||
|
application = startupAction.runMainClass(args.toArray(new String[0]));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
} catch (Exception cause) {
|
||||||
|
throw new RuntimeException("Fail to start the server", cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() throws TimeoutException {
|
||||||
|
if (isRunning()) {
|
||||||
|
closeApplication();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationModel createApplicationModel(String keycloakVersion)
|
||||||
|
throws AppModelResolverException {
|
||||||
|
// initialize Quarkus application model resolver
|
||||||
|
BootstrapAppModelResolver appModelResolver = new BootstrapAppModelResolver(getMavenArtifactResolver());
|
||||||
|
|
||||||
|
// configure server dependencies
|
||||||
|
WorkspaceModule module = createWorkspaceModule(keycloakVersion);
|
||||||
|
|
||||||
|
// resolve Keycloak server Quarkus application model
|
||||||
|
return appModelResolver.resolveModel(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorkspaceModule createWorkspaceModule(String keycloakVersion) {
|
||||||
|
Path moduleDir = createModuleDir();
|
||||||
|
|
||||||
|
WorkspaceModule.Mutable builder = WorkspaceModule.builder()
|
||||||
|
.setModuleId(WorkspaceModuleId.of("io.playground", "keycloak-app", "1"))
|
||||||
|
.setModuleDir(moduleDir)
|
||||||
|
.setBuildDir(moduleDir)
|
||||||
|
.addDependencyConstraint(
|
||||||
|
Dependency.pomImport("org.keycloak", "keycloak-quarkus-parent", keycloakVersion))
|
||||||
|
.addDependency(DependencyBuilder.newInstance()
|
||||||
|
.setGroupId("org.keycloak")
|
||||||
|
.setArtifactId("keycloak-quarkus-server-app")
|
||||||
|
.setVersion(keycloakVersion)
|
||||||
|
.addExclusion("org.jboss.logmanager", "log4j-jboss-logmanager")
|
||||||
|
.addExclusion("org.keycloak", "keycloak-crypto-fips1402") //TODO: enable fips
|
||||||
|
.build());
|
||||||
|
|
||||||
|
for (Dependency dependency : dependencies) {
|
||||||
|
builder.addDependency(dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path createModuleDir() {
|
||||||
|
Path moduleDir;
|
||||||
|
|
||||||
|
try {
|
||||||
|
moduleDir = Files.createTempDirectory("kc-embedded");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return moduleDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
MavenArtifactResolver getMavenArtifactResolver() throws BootstrapMavenException {
|
||||||
|
return MavenArtifactResolver.builder()
|
||||||
|
.setWorkspaceDiscovery(true)
|
||||||
|
.setOffline(false)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRunning() {
|
||||||
|
return application != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeApplication() {
|
||||||
|
if (application != null) {
|
||||||
|
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||||
|
Thread.currentThread().setContextClassLoader(application.getClassLoader());
|
||||||
|
try {
|
||||||
|
application.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
Thread.currentThread().setContextClassLoader(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QuarkusConfigFactory.setConfig(null);
|
||||||
|
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||||
|
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ConfigProviderResolver cpr = ConfigProviderResolver.instance();
|
||||||
|
cpr.releaseConfig(cpr.getConfig());
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// just means no config was installed, which is fine
|
||||||
|
} finally {
|
||||||
|
Thread.currentThread().setContextClassLoader(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
application = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,23 @@ This can be achieved by add the `auth-server-quarkus` profile when running the t
|
||||||
Unlike the "development" setup described above, this requires re-build the whole distribution
|
Unlike the "development" setup described above, this requires re-build the whole distribution
|
||||||
after doing any change in the code.
|
after doing any change in the code.
|
||||||
|
|
||||||
|
### Running tests using an embedded server
|
||||||
|
|
||||||
|
For test driven development, it is possible to run the Keycloak server deployed on real Quarkus server.
|
||||||
|
This can be achieved by add the `auth-server-quarkus-embedded` profile when running the testsuite.
|
||||||
|
|
||||||
|
mvn -f testsuite/integration-arquillian/pom.xml -Pauth-server-quarkus-embedded clean install -Dtest=LoginTest
|
||||||
|
|
||||||
|
After running this command, you should also be able to run tests from your IDE. For that, make sure you have the `auth-server-quarkus-embedded` profile enabled.
|
||||||
|
|
||||||
|
When running in embedded mode, the `build` phase happens every time the server is started, and it is based on the same configuration used during a full-distribution test run(e.g.: `auth-server-quarkus` profile is active).
|
||||||
|
|
||||||
|
There are a few limitations when running tests. The well-known limitations are:
|
||||||
|
|
||||||
|
* FIPS tests not working
|
||||||
|
* Deploying script providers not working. Probably any test deploying JAR files.
|
||||||
|
* Re-starting the server during a test execution is taking too much metaspace. Need more investigation.
|
||||||
|
|
||||||
## Debugging - tips & tricks
|
## Debugging - tips & tricks
|
||||||
|
|
||||||
### Arquillian debugging
|
### Arquillian debugging
|
||||||
|
@ -465,7 +482,6 @@ This is temporary and database configuration should be more integrated with the
|
||||||
|
|
||||||
Activate the following profiles:
|
Activate the following profiles:
|
||||||
|
|
||||||
* `quarkus`
|
|
||||||
* `auth-server-cluster-quarkus`
|
* `auth-server-cluster-quarkus`
|
||||||
|
|
||||||
Then run any cluster test as usual.
|
Then run any cluster test as usual.
|
||||||
|
|
|
@ -176,6 +176,19 @@
|
||||||
<artifactId>infinispan-server-hotrod</artifactId>
|
<artifactId>infinispan-server-hotrod</artifactId>
|
||||||
<version>${infinispan.version}</version>
|
<version>${infinispan.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Embedded Distribution -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-quarkus-integration-tests</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>*</groupId>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -1056,6 +1069,51 @@
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>auth-server-quarkus-embedded</id>
|
||||||
|
<properties>
|
||||||
|
<surefire.memory.Xms>1024m</surefire.memory.Xms>
|
||||||
|
<surefire.memory.Xmx>1024m</surefire.memory.Xmx>
|
||||||
|
<surefire.memory.metaspace>512m</surefire.memory.metaspace>
|
||||||
|
<surefire.memory.metaspace.max>512m</surefire.memory.metaspace.max>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-quarkus-integration-tests</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
|
||||||
|
<!-- TODO: FIPS not yet supported -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-crypto-fips1402</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
|
||||||
|
<!-- Remove Junit5 deps-->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-junit5-internal</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logmanager</groupId>
|
||||||
|
<artifactId>log4j-jboss-logmanager</artifactId>
|
||||||
|
<version>1.3.0.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.bytebuddy</groupId>
|
||||||
|
<artifactId>byte-buddy</artifactId>
|
||||||
|
<version>1.12.18</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.arquillian.containers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
|
||||||
|
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
|
||||||
|
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
|
||||||
|
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
|
||||||
|
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
|
||||||
|
import org.jboss.arquillian.core.api.Instance;
|
||||||
|
import org.jboss.arquillian.core.api.annotation.Inject;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.shrinkwrap.api.Archive;
|
||||||
|
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
|
||||||
|
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
|
||||||
|
import org.keycloak.common.crypto.FipsMode;
|
||||||
|
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||||
|
import org.keycloak.testsuite.model.StoreProvider;
|
||||||
|
|
||||||
|
public abstract class AbstractQuarkusDeployableContainer implements DeployableContainer<KeycloakQuarkusConfiguration> {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(AbstractQuarkusDeployableContainer.class);
|
||||||
|
|
||||||
|
protected static AtomicBoolean restart = new AtomicBoolean();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected Instance<SuiteContext> suiteContext;
|
||||||
|
|
||||||
|
protected KeycloakQuarkusConfiguration configuration;
|
||||||
|
protected List<String> additionalBuildArgs = Collections.emptyList();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<KeycloakQuarkusConfiguration> getConfigurationClass() {
|
||||||
|
return KeycloakQuarkusConfiguration.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(KeycloakQuarkusConfiguration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
|
||||||
|
try {
|
||||||
|
deployArchiveToServer(archive);
|
||||||
|
restartServer();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DeploymentException(e.getMessage(),e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProtocolMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undeploy(Archive<?> archive) throws DeploymentException {
|
||||||
|
File wrkDir = configuration.getProvidersPath().resolve("providers").toFile();
|
||||||
|
try {
|
||||||
|
if (isWindows()) {
|
||||||
|
// stop before updating providers to avoid file locking issues on Windows
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(wrkDir.toPath().resolve(archive.getName()));
|
||||||
|
restartServer();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DeploymentException(e.getMessage(),e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolDescription getDefaultProtocol() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deploy(Descriptor descriptor) throws DeploymentException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undeploy(Descriptor descriptor) throws DeploymentException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartServer() throws Exception {
|
||||||
|
stop();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> getArgs() {
|
||||||
|
return getArgs(new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> getArgs(Map<String, String> env) {
|
||||||
|
List<String> commands = new ArrayList<>();
|
||||||
|
|
||||||
|
commands.add("-v");
|
||||||
|
commands.add("start");
|
||||||
|
commands.add("--http-enabled=true");
|
||||||
|
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("auth.server.debug", "false"))) {
|
||||||
|
commands.add("--debug");
|
||||||
|
|
||||||
|
String debugPort = configuration.getDebugPort() > 0 ? Integer.toString(configuration.getDebugPort()) : System.getProperty("auth.server.debug.port", "5005");
|
||||||
|
env.put("DEBUG_PORT", debugPort);
|
||||||
|
|
||||||
|
String debugSuspend = System.getProperty("auth.server.debug.suspend");
|
||||||
|
if (debugSuspend != null) {
|
||||||
|
env.put("DEBUG_SUSPEND", debugSuspend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.add("--http-port=" + configuration.getBindHttpPort());
|
||||||
|
commands.add("--https-port=" + configuration.getBindHttpsPort());
|
||||||
|
|
||||||
|
if (configuration.getRoute() != null) {
|
||||||
|
commands.add("-Djboss.node.name=" + configuration.getRoute());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.getProperty("auth.server.quarkus.log-level") != null) {
|
||||||
|
commands.add("--log-level=" + System.getProperty("auth.server.quarkus.log-level"));
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.addAll(getAdditionalBuildArgs());
|
||||||
|
|
||||||
|
commands = configureArgs(commands);
|
||||||
|
|
||||||
|
final StoreProvider storeProvider = StoreProvider.getCurrentProvider();
|
||||||
|
final Supplier<Boolean> shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT);
|
||||||
|
final Supplier<String> getClusterConfig = () -> System.getProperty("auth.server.quarkus.cluster.config", "local");
|
||||||
|
|
||||||
|
log.debugf("FIPS Mode: %s", configuration.getFipsMode());
|
||||||
|
|
||||||
|
// only run build during first execution of the server (if the DB is specified), restarts or when running cluster tests
|
||||||
|
if (restart.get() || shouldSetUpDb.get() || "ha".equals(getClusterConfig.get()) || configuration.getFipsMode() != FipsMode.disabled) {
|
||||||
|
commands.removeIf("--optimized"::equals);
|
||||||
|
commands.add("--http-relative-path=/auth");
|
||||||
|
|
||||||
|
if (!storeProvider.isMapStore()) {
|
||||||
|
String cacheMode = getClusterConfig.get();
|
||||||
|
|
||||||
|
if ("local".equals(cacheMode)) {
|
||||||
|
commands.add("--cache=local");
|
||||||
|
} else {
|
||||||
|
commands.add("--cache-config-file=cluster-" + cacheMode + ".xml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.getFipsMode() != FipsMode.disabled) {
|
||||||
|
addFipsOptions(commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addStorageOptions(storeProvider, commands);
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> configureArgs(List<String> commands) {
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deployArchiveToServer(Archive<?> archive) throws IOException, LifecycleException {
|
||||||
|
if (isWindows()) {
|
||||||
|
// stop before updating providers to avoid file locking issues on Windows
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
File providersDir = configuration.getProvidersPath().resolve("providers").toFile();
|
||||||
|
InputStream zipStream = archive.as(ZipExporter.class).exportAsInputStream();
|
||||||
|
Files.copy(zipStream, providersDir.toPath().resolve(archive.getName()), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean isWindows() {
|
||||||
|
return SystemUtils.IS_OS_WINDOWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAdditionalBuildArgs() {
|
||||||
|
return additionalBuildArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdditionalBuildArgs(List<String> newArgs) {
|
||||||
|
additionalBuildArgs = newArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetConfiguration() {
|
||||||
|
additionalBuildArgs = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitForReadiness() throws MalformedURLException, LifecycleException {
|
||||||
|
SuiteContext suiteContext = this.suiteContext.get();
|
||||||
|
//TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have
|
||||||
|
// support for MP Health this should change
|
||||||
|
URL contextRoot = new URL(getBaseUrl(suiteContext) + "/auth/realms/master/");
|
||||||
|
HttpURLConnection connection;
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (System.currentTimeMillis() - startTime > getStartTimeout()) {
|
||||||
|
stop();
|
||||||
|
throw new IllegalStateException("Timeout [" + getStartTimeout() + "] while waiting for Quarkus server");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// wait before checking for opening a new connection
|
||||||
|
Thread.sleep(1000);
|
||||||
|
if ("https".equals(contextRoot.getProtocol())) {
|
||||||
|
HttpsURLConnection httpsConnection = (HttpsURLConnection) (connection = (HttpURLConnection) contextRoot.openConnection());
|
||||||
|
httpsConnection.setSSLSocketFactory(createInsecureSslSocketFactory());
|
||||||
|
httpsConnection.setHostnameVerifier(createInsecureHostnameVerifier());
|
||||||
|
} else {
|
||||||
|
connection = (HttpURLConnection) contextRoot.openConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.setReadTimeout((int) getStartTimeout());
|
||||||
|
connection.setConnectTimeout((int) getStartTimeout());
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
if (connection.getResponseCode() == 200) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.disconnect();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.infof("Keycloak is ready at %s", contextRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL getBaseUrl(SuiteContext suiteContext) throws MalformedURLException {
|
||||||
|
URL baseUrl = suiteContext.getAuthServerInfo().getContextRoot();
|
||||||
|
|
||||||
|
// might be running behind a load balancer
|
||||||
|
if ("https".equals(baseUrl.getProtocol())) {
|
||||||
|
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpsPort())));
|
||||||
|
} else {
|
||||||
|
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpPort())));
|
||||||
|
}
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HostnameVerifier createInsecureHostnameVerifier() {
|
||||||
|
return new HostnameVerifier() {
|
||||||
|
@Override
|
||||||
|
public boolean verify(String s, SSLSession sslSession) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLSocketFactory createInsecureSslSocketFactory() throws IOException {
|
||||||
|
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
|
||||||
|
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
SSLContext sslContext;
|
||||||
|
SSLSocketFactory socketFactory;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||||
|
socketFactory = sslContext.getSocketFactory();
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
|
throw new IOException("Can't create unsecure trust manager");
|
||||||
|
}
|
||||||
|
return socketFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getStartTimeout() {
|
||||||
|
return TimeUnit.SECONDS.toMillis(configuration.getStartupTimeoutInSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStorageOptions(StoreProvider storeProvider, List<String> commands) {
|
||||||
|
log.debugf("Store '%s' is used.", storeProvider.name());
|
||||||
|
storeProvider.addStoreOptions(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFipsOptions(List<String> commands) {
|
||||||
|
commands.add("--fips-mode=" + configuration.getFipsMode().toString());
|
||||||
|
|
||||||
|
log.debugf("Keystore file: %s, keystore type: %s, truststore file: %s, truststore type: %s",
|
||||||
|
configuration.getKeystoreFile(), configuration.getKeystoreType(),
|
||||||
|
configuration.getTruststoreFile(), configuration.getTruststoreType());
|
||||||
|
commands.add("--https-key-store-file=" + configuration.getKeystoreFile());
|
||||||
|
commands.add("--https-key-store-type=" + configuration.getKeystoreType());
|
||||||
|
commands.add("--https-key-store-password=" + configuration.getKeystorePassword());
|
||||||
|
commands.add("--https-trust-store-file=" + configuration.getTruststoreFile());
|
||||||
|
commands.add("--https-trust-store-type=" + configuration.getTruststoreType());
|
||||||
|
commands.add("--https-trust-store-password=" + configuration.getTruststorePassword());
|
||||||
|
commands.add("--spi-truststore-file-file=" + configuration.getTruststoreFile());
|
||||||
|
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
|
||||||
|
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
|
||||||
|
|
||||||
|
// BCFIPS approved mode requires passwords of at least 112 bits (14 characters) to be used. To bypass this, we use this by default
|
||||||
|
// as testsuite uses shorter passwords everywhere
|
||||||
|
if (FipsMode.strict == configuration.getFipsMode()) {
|
||||||
|
commands.add("--spi-password-hashing-pbkdf2-max-padding-length=14");
|
||||||
|
commands.add("--spi-password-hashing-pbkdf2-sha256-max-padding-length=14");
|
||||||
|
commands.add("--spi-password-hashing-pbkdf2-sha512-max-padding-length=14");
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.add("--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE,org.keycloak.truststore:TRACE");
|
||||||
|
|
||||||
|
configuration.appendJavaOpts("-Djava.security.properties=" + System.getProperty("auth.server.java.security.file"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.keycloak.testsuite.arquillian.containers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
|
||||||
|
import org.keycloak.Keycloak;
|
||||||
|
import org.keycloak.common.Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
public class KeycloakQuarkusEmbeddedDeployableContainer extends AbstractQuarkusDeployableContainer {
|
||||||
|
|
||||||
|
private static final String KEYCLOAK_VERSION = Version.VERSION;
|
||||||
|
|
||||||
|
private Keycloak keycloak;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws LifecycleException {
|
||||||
|
try {
|
||||||
|
keycloak = configure().start(getArgs());
|
||||||
|
waitForReadiness();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() throws LifecycleException {
|
||||||
|
if (keycloak != null) {
|
||||||
|
try {
|
||||||
|
keycloak.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to stop the server", e);
|
||||||
|
} finally {
|
||||||
|
keycloak = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Keycloak.Builder configure() {
|
||||||
|
return Keycloak.builder()
|
||||||
|
.setHomeDir(configuration.getProvidersPath())
|
||||||
|
.setVersion(KEYCLOAK_VERSION)
|
||||||
|
.addDependency("org.keycloak.testsuite", "integration-arquillian-testsuite-providers", KEYCLOAK_VERSION)
|
||||||
|
.addDependency("org.keycloak.testsuite", "integration-arquillian-testsuite-providers", KEYCLOAK_VERSION)
|
||||||
|
.addDependency("org.keycloak.testsuite", "integration-arquillian-tests-base", KEYCLOAK_VERSION)
|
||||||
|
.addDependency("org.keycloak.testsuite", "integration-arquillian-tests-base", KEYCLOAK_VERSION, "tests");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> configureArgs(List<String> args) {
|
||||||
|
System.setProperty("quarkus.http.test-port", String.valueOf(configuration.getBindHttpPort()));
|
||||||
|
System.setProperty("quarkus.http.test-ssl-port", String.valueOf(configuration.getBindHttpsPort()));
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,7 @@
|
||||||
package org.keycloak.testsuite.arquillian.containers;
|
package org.keycloak.testsuite.arquillian.containers;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
|
@ -20,68 +10,32 @@ import java.nio.file.LinkOption;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.SimpleFileVisitor;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.exec.StreamPumper;
|
import org.apache.commons.exec.StreamPumper;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
|
|
||||||
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
|
|
||||||
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
|
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
|
||||||
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
|
|
||||||
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
|
|
||||||
import org.jboss.arquillian.core.api.Instance;
|
|
||||||
import org.jboss.arquillian.core.api.annotation.Inject;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.shrinkwrap.api.Archive;
|
|
||||||
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
|
|
||||||
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
|
|
||||||
import org.keycloak.common.crypto.FipsMode;
|
|
||||||
import org.keycloak.testsuite.arquillian.SuiteContext;
|
|
||||||
import org.keycloak.testsuite.model.StoreProvider;
|
import org.keycloak.testsuite.model.StoreProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
public class KeycloakQuarkusServerDeployableContainer implements DeployableContainer<KeycloakQuarkusConfiguration> {
|
public class KeycloakQuarkusServerDeployableContainer extends AbstractQuarkusDeployableContainer {
|
||||||
|
|
||||||
private static final int DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 10;
|
private static final int DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 10;
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class);
|
private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class);
|
||||||
|
|
||||||
private KeycloakQuarkusConfiguration configuration;
|
|
||||||
private Process container;
|
private Process container;
|
||||||
private static AtomicBoolean restart = new AtomicBoolean();
|
|
||||||
private Thread stdoutForwarderThread;
|
private Thread stdoutForwarderThread;
|
||||||
|
|
||||||
@Inject
|
|
||||||
private Instance<SuiteContext> suiteContext;
|
|
||||||
|
|
||||||
private List<String> additionalBuildArgs = Collections.emptyList();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<KeycloakQuarkusConfiguration> getConfigurationClass() {
|
|
||||||
return KeycloakQuarkusConfiguration.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup(KeycloakQuarkusConfiguration configuration) {
|
|
||||||
this.configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() throws LifecycleException {
|
public void start() throws LifecycleException {
|
||||||
try {
|
try {
|
||||||
|
@ -109,50 +63,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProtocolDescription getDefaultProtocol() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
|
|
||||||
log.infof("Trying to deploy: " + archive.getName());
|
|
||||||
|
|
||||||
try {
|
|
||||||
deployArchiveToServer(archive);
|
|
||||||
restartServer();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new DeploymentException(e.getMessage(),e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ProtocolMetaData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void undeploy(Archive<?> archive) throws DeploymentException {
|
|
||||||
File wrkDir = configuration.getProvidersPath().resolve("providers").toFile();
|
|
||||||
try {
|
|
||||||
if (isWindows()) {
|
|
||||||
// stop before updating providers to avoid file locking issues on Windows
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
Files.deleteIfExists(wrkDir.toPath().resolve(archive.getName()));
|
|
||||||
restartServer();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new DeploymentException(e.getMessage(),e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deploy(Descriptor descriptor) throws DeploymentException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void undeploy(Descriptor descriptor) throws DeploymentException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void importRealm() throws IOException, URISyntaxException {
|
private void importRealm() throws IOException, URISyntaxException {
|
||||||
if (suiteContext.get().isAuthServerMigrationEnabled() && configuration.getImportFile() != null) {
|
if (suiteContext.get().isAuthServerMigrationEnabled() && configuration.getImportFile() != null) {
|
||||||
final String importFileName = configuration.getImportFile();
|
final String importFileName = configuration.getImportFile();
|
||||||
|
@ -201,224 +111,28 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
return builder.start();
|
return builder.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProcessBuilder getProcessBuilder() {
|
@Override
|
||||||
List<String> commands = new ArrayList<>();
|
protected List<String> configureArgs(List<String> args) {
|
||||||
Map<String, String> env = new HashMap<>();
|
List<String> commands = new ArrayList<>(args);
|
||||||
|
|
||||||
commands.add(getCommand());
|
commands.add(0, getCommand());
|
||||||
commands.add("-v");
|
|
||||||
commands.add("start");
|
|
||||||
commands.add("--optimized");
|
commands.add("--optimized");
|
||||||
commands.add("--http-enabled=true");
|
|
||||||
|
|
||||||
if (Boolean.parseBoolean(System.getProperty("auth.server.debug", "false"))) {
|
|
||||||
commands.add("--debug");
|
|
||||||
|
|
||||||
String debugPort = configuration.getDebugPort() > 0 ? Integer.toString(configuration.getDebugPort()) : System.getProperty("auth.server.debug.port", "5005");
|
|
||||||
env.put("DEBUG_PORT", debugPort);
|
|
||||||
|
|
||||||
String debugSuspend = System.getProperty("auth.server.debug.suspend");
|
|
||||||
if (debugSuspend != null) {
|
|
||||||
env.put("DEBUG_SUSPEND", debugSuspend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.add("--http-port=" + configuration.getBindHttpPort());
|
|
||||||
commands.add("--https-port=" + configuration.getBindHttpsPort());
|
|
||||||
|
|
||||||
if (configuration.getRoute() != null) {
|
|
||||||
commands.add("-Djboss.node.name=" + configuration.getRoute());
|
|
||||||
}
|
|
||||||
|
|
||||||
final StoreProvider storeProvider = StoreProvider.getCurrentProvider();
|
|
||||||
|
|
||||||
final Supplier<Boolean> shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT);
|
|
||||||
final Supplier<String> getClusterConfig = () -> System.getProperty("auth.server.quarkus.cluster.config", "local");
|
|
||||||
|
|
||||||
log.debugf("FIPS Mode: %s", configuration.getFipsMode());
|
|
||||||
|
|
||||||
// only run build during first execution of the server (if the DB is specified), restarts or when running cluster tests
|
|
||||||
if (restart.get() || shouldSetUpDb.get() || "ha".equals(getClusterConfig.get()) || configuration.getFipsMode() != FipsMode.disabled) {
|
|
||||||
commands.removeIf("--optimized"::equals);
|
|
||||||
commands.add("--http-relative-path=/auth");
|
|
||||||
|
|
||||||
if (!storeProvider.isMapStore()) {
|
|
||||||
String cacheMode = getClusterConfig.get();
|
|
||||||
|
|
||||||
if ("local".equals(cacheMode)) {
|
|
||||||
commands.add("--cache=local");
|
|
||||||
} else {
|
|
||||||
commands.add("--cache-config-file=cluster-" + cacheMode + ".xml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.getFipsMode() != FipsMode.disabled) {
|
|
||||||
addFipsOptions(commands);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addStorageOptions(storeProvider, commands);
|
|
||||||
if (System.getProperty("auth.server.quarkus.log-level") != null) {
|
|
||||||
commands.add("--log-level=" + System.getProperty("auth.server.quarkus.log-level"));
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.addAll(getAdditionalBuildArgs());
|
|
||||||
|
|
||||||
log.debugf("Quarkus parameters: %s", commands);
|
log.debugf("Quarkus parameters: %s", commands);
|
||||||
|
|
||||||
String[] processCommands = commands.toArray(new String[0]);
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessBuilder getProcessBuilder() {
|
||||||
|
Map<String, String> env = new HashMap<>();
|
||||||
|
String[] processCommands = getArgs(env).toArray(new String[0]);
|
||||||
ProcessBuilder pb = new ProcessBuilder(processCommands);
|
ProcessBuilder pb = new ProcessBuilder(processCommands);
|
||||||
|
|
||||||
pb.environment().putAll(env);
|
pb.environment().putAll(env);
|
||||||
|
|
||||||
return pb;
|
return pb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStorageOptions(StoreProvider storeProvider, List<String> commands) {
|
|
||||||
log.debugf("Store '%s' is used.", storeProvider.name());
|
|
||||||
storeProvider.addStoreOptions(commands);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFipsOptions(List<String> commands) {
|
|
||||||
commands.add("--fips-mode=" + configuration.getFipsMode().toString());
|
|
||||||
|
|
||||||
log.debugf("Keystore file: %s, keystore type: %s, truststore file: %s, truststore type: %s",
|
|
||||||
configuration.getKeystoreFile(), configuration.getKeystoreType(),
|
|
||||||
configuration.getTruststoreFile(), configuration.getTruststoreType());
|
|
||||||
commands.add("--https-key-store-file=" + configuration.getKeystoreFile());
|
|
||||||
commands.add("--https-key-store-type=" + configuration.getKeystoreType());
|
|
||||||
commands.add("--https-key-store-password=" + configuration.getKeystorePassword());
|
|
||||||
commands.add("--https-trust-store-file=" + configuration.getTruststoreFile());
|
|
||||||
commands.add("--https-trust-store-type=" + configuration.getTruststoreType());
|
|
||||||
commands.add("--https-trust-store-password=" + configuration.getTruststorePassword());
|
|
||||||
commands.add("--spi-truststore-file-file=" + configuration.getTruststoreFile());
|
|
||||||
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
|
|
||||||
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
|
|
||||||
|
|
||||||
// BCFIPS approved mode requires passwords of at least 112 bits (14 characters) to be used. To bypass this, we use this by default
|
|
||||||
// as testsuite uses shorter passwords everywhere
|
|
||||||
if (FipsMode.strict == configuration.getFipsMode()) {
|
|
||||||
commands.add("--spi-password-hashing-pbkdf2-max-padding-length=14");
|
|
||||||
commands.add("--spi-password-hashing-pbkdf2-sha256-max-padding-length=14");
|
|
||||||
commands.add("--spi-password-hashing-pbkdf2-sha512-max-padding-length=14");
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.add("--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE,org.keycloak.truststore:TRACE");
|
|
||||||
|
|
||||||
configuration.appendJavaOpts("-Djava.security.properties=" + System.getProperty("auth.server.java.security.file"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waitForReadiness() throws MalformedURLException, LifecycleException {
|
|
||||||
SuiteContext suiteContext = this.suiteContext.get();
|
|
||||||
//TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have
|
|
||||||
// support for MP Health this should change
|
|
||||||
URL contextRoot = new URL(getBaseUrl(suiteContext) + "/auth/realms/master/");
|
|
||||||
HttpURLConnection connection;
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (System.currentTimeMillis() - startTime > getStartTimeout()) {
|
|
||||||
stop();
|
|
||||||
throw new IllegalStateException("Timeout [" + getStartTimeout() + "] while waiting for Quarkus server");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// wait before checking for opening a new connection
|
|
||||||
Thread.sleep(1000);
|
|
||||||
if ("https".equals(contextRoot.getProtocol())) {
|
|
||||||
HttpsURLConnection httpsConnection = (HttpsURLConnection) (connection = (HttpURLConnection) contextRoot.openConnection());
|
|
||||||
httpsConnection.setSSLSocketFactory(createInsecureSslSocketFactory());
|
|
||||||
httpsConnection.setHostnameVerifier(createInsecureHostnameVerifier());
|
|
||||||
} else {
|
|
||||||
connection = (HttpURLConnection) contextRoot.openConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.setReadTimeout((int) getStartTimeout());
|
|
||||||
connection.setConnectTimeout((int) getStartTimeout());
|
|
||||||
connection.connect();
|
|
||||||
|
|
||||||
if (connection.getResponseCode() == 200) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.disconnect();
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.infof("Keycloak is ready at %s", contextRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL getBaseUrl(SuiteContext suiteContext) throws MalformedURLException {
|
|
||||||
URL baseUrl = suiteContext.getAuthServerInfo().getContextRoot();
|
|
||||||
|
|
||||||
// might be running behind a load balancer
|
|
||||||
if ("https".equals(baseUrl.getProtocol())) {
|
|
||||||
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpsPort())));
|
|
||||||
} else {
|
|
||||||
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpPort())));
|
|
||||||
}
|
|
||||||
return baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HostnameVerifier createInsecureHostnameVerifier() {
|
|
||||||
return new HostnameVerifier() {
|
|
||||||
@Override
|
|
||||||
public boolean verify(String s, SSLSession sslSession) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private SSLSocketFactory createInsecureSslSocketFactory() throws IOException {
|
|
||||||
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
|
|
||||||
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
SSLContext sslContext;
|
|
||||||
SSLSocketFactory socketFactory;
|
|
||||||
|
|
||||||
try {
|
|
||||||
sslContext = SSLContext.getInstance("TLS");
|
|
||||||
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
|
||||||
socketFactory = sslContext.getSocketFactory();
|
|
||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
|
||||||
throw new IOException("Can't create unsecure trust manager");
|
|
||||||
}
|
|
||||||
return socketFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getStartTimeout() {
|
|
||||||
return TimeUnit.SECONDS.toMillis(configuration.getStartupTimeoutInSeconds());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetConfiguration() {
|
|
||||||
additionalBuildArgs = Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deployArchiveToServer(Archive<?> archive) throws IOException, LifecycleException {
|
|
||||||
if (isWindows()) {
|
|
||||||
// stop before updating providers to avoid file locking issues on Windows
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
File providersDir = configuration.getProvidersPath().resolve("providers").toFile();
|
|
||||||
InputStream zipStream = archive.as(ZipExporter.class).exportAsInputStream();
|
|
||||||
Files.copy(zipStream, providersDir.toPath().resolve(archive.getName()), StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restartServer() throws Exception {
|
|
||||||
stop();
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCommand() {
|
private String getCommand() {
|
||||||
if (isWindows()) {
|
if (isWindows()) {
|
||||||
return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString();
|
return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString();
|
||||||
|
@ -426,14 +140,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
return "./kc.sh";
|
return "./kc.sh";
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getAdditionalBuildArgs() {
|
|
||||||
return additionalBuildArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdditionalBuildArgs(List<String> newArgs) {
|
|
||||||
additionalBuildArgs = newArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void destroyDescendantsOnWindows(Process parent, boolean force) {
|
private void destroyDescendantsOnWindows(Process parent, boolean force) {
|
||||||
if (!isWindows()) {
|
if (!isWindows()) {
|
||||||
return;
|
return;
|
||||||
|
@ -467,10 +173,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isWindows() {
|
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deleteDirectory(final Path directory) throws IOException {
|
public static void deleteDirectory(final Path directory) throws IOException {
|
||||||
if (Files.isDirectory(directory, new LinkOption[0])) {
|
if (Files.isDirectory(directory, new LinkOption[0])) {
|
||||||
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
||||||
|
|
|
@ -44,6 +44,7 @@ public class MultipleContainersExtension implements LoadableExtension {
|
||||||
logger.info("Multiple containers extension registering.");
|
logger.info("Multiple containers extension registering.");
|
||||||
|
|
||||||
builder.service(DeployableContainer.class, KeycloakQuarkusServerDeployableContainer.class)
|
builder.service(DeployableContainer.class, KeycloakQuarkusServerDeployableContainer.class)
|
||||||
|
.service(DeployableContainer.class, KeycloakQuarkusEmbeddedDeployableContainer.class)
|
||||||
.service(DeployableContainer.class, InfinispanServerDeployableContainer.class);
|
.service(DeployableContainer.class, InfinispanServerDeployableContainer.class);
|
||||||
|
|
||||||
builder.context(ContainerContextImpl.class).context(DeploymentContextImpl.class);
|
builder.context(ContainerContextImpl.class).context(DeploymentContextImpl.class);
|
||||||
|
|
|
@ -38,7 +38,7 @@ public enum StoreProvider {
|
||||||
public void addStoreOptions(List<String> commands) {
|
public void addStoreOptions(List<String> commands) {
|
||||||
commands.add("--storage=" + getAlias());
|
commands.add("--storage=" + getAlias());
|
||||||
getDbVendor().ifPresent(vendor -> commands.add("--db=" + vendor));
|
getDbVendor().ifPresent(vendor -> commands.add("--db=" + vendor));
|
||||||
commands.add("--db-url='" + System.getProperty("keycloak.map.storage.connectionsJpa.url") + "'");
|
commands.add("--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"));
|
||||||
commands.add("--db-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user"));
|
commands.add("--db-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user"));
|
||||||
commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password"));
|
commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,11 +57,11 @@ public class ContainerAssume {
|
||||||
|
|
||||||
public static void assumeNotAuthServerQuarkus() {
|
public static void assumeNotAuthServerQuarkus() {
|
||||||
Assume.assumeFalse("Doesn't work on auth-server-quarkus",
|
Assume.assumeFalse("Doesn't work on auth-server-quarkus",
|
||||||
AuthServerTestEnricher.AUTH_SERVER_CONTAINER.equals("auth-server-quarkus"));
|
AuthServerTestEnricher.AUTH_SERVER_CONTAINER.startsWith("auth-server-quarkus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assumeAuthServerQuarkus() {
|
public static void assumeAuthServerQuarkus() {
|
||||||
Assume.assumeTrue("Only works on auth-server-quarkus",
|
Assume.assumeTrue("Only works on auth-server-quarkus",
|
||||||
AuthServerTestEnricher.AUTH_SERVER_CONTAINER.equals("auth-server-quarkus"));
|
AuthServerTestEnricher.AUTH_SERVER_CONTAINER.startsWith("auth-server-quarkus"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,12 @@ package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
import org.jboss.arquillian.container.spi.Container;
|
import org.jboss.arquillian.container.spi.Container;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
|
||||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
import org.keycloak.testsuite.arquillian.SuiteContext;
|
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
|
||||||
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
|
import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
|
||||||
import org.keycloak.utils.StringUtil;
|
import org.keycloak.utils.StringUtil;
|
||||||
import org.wildfly.extras.creaper.core.online.CliException;
|
|
||||||
import org.wildfly.extras.creaper.core.online.ModelNodeResult;
|
|
||||||
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -58,8 +53,8 @@ public class SpiProvidersSwitchingUtils {
|
||||||
getQuarkusContainer(container).setAdditionalBuildArgs(Collections.emptyList());
|
getQuarkusContainer(container).setAdditionalBuildArgs(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeycloakQuarkusServerDeployableContainer getQuarkusContainer(Container container) {
|
private AbstractQuarkusDeployableContainer getQuarkusContainer(Container container) {
|
||||||
return (KeycloakQuarkusServerDeployableContainer) container.getDeployableContainer();
|
return (AbstractQuarkusDeployableContainer) container.getDeployableContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.models.ClientProvider;
|
import org.keycloak.models.ClientProvider;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
|
import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -147,7 +147,7 @@ public class ClientSearchTest extends AbstractClientTest {
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
String s = String.join(",",searchableAttributes);
|
String s = String.join(",",searchableAttributes);
|
||||||
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||||
container.setAdditionalBuildArgs(Collections.singletonList("--spi-client-jpa-searchable-attributes=\""+ s + "\""));
|
container.setAdditionalBuildArgs(Collections.singletonList("--spi-client-jpa-searchable-attributes=\""+ s + "\""));
|
||||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
} else {
|
} else {
|
||||||
|
@ -165,7 +165,7 @@ public class ClientSearchTest extends AbstractClientTest {
|
||||||
System.clearProperty(SEARCHABLE_ATTRS_PROP);
|
System.clearProperty(SEARCHABLE_ATTRS_PROP);
|
||||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||||
container.setAdditionalBuildArgs(Collections.emptyList());
|
container.setAdditionalBuildArgs(Collections.emptyList());
|
||||||
container.restartServer();
|
container.restartServer();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,11 +23,8 @@ import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.models.GroupProvider;
|
import org.keycloak.models.GroupProvider;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
|
||||||
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
|
|
||||||
import org.keycloak.testsuite.updaters.Creator;
|
import org.keycloak.testsuite.updaters.Creator;
|
||||||
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
|
|
||||||
import org.wildfly.extras.creaper.core.online.operations.admin.Administration;
|
|
||||||
|
|
||||||
public class GroupSearchTest extends AbstractGroupTest {
|
public class GroupSearchTest extends AbstractGroupTest {
|
||||||
@ArquillianResource
|
@ArquillianResource
|
||||||
|
@ -166,7 +163,7 @@ public class GroupSearchTest extends AbstractGroupTest {
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
String s = String.join(",", searchableAttributes);
|
String s = String.join(",", searchableAttributes);
|
||||||
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||||
container.setAdditionalBuildArgs(
|
container.setAdditionalBuildArgs(
|
||||||
Collections.singletonList("--spi-group-jpa-searchable-attributes=\"" + s + "\""));
|
Collections.singletonList("--spi-group-jpa-searchable-attributes=\"" + s + "\""));
|
||||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
|
@ -184,7 +181,7 @@ public class GroupSearchTest extends AbstractGroupTest {
|
||||||
System.clearProperty(SEARCHABLE_ATTRS_PROP);
|
System.clearProperty(SEARCHABLE_ATTRS_PROP);
|
||||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||||
container.setAdditionalBuildArgs(Collections.emptyList());
|
container.setAdditionalBuildArgs(Collections.emptyList());
|
||||||
container.restartServer();
|
container.restartServer();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import org.jboss.arquillian.container.test.api.ContainerController;
|
||||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
|
import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -33,7 +33,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
|
||||||
"keycloak.hostname.fixed.alwaysHttps");
|
"keycloak.hostname.fixed.alwaysHttps");
|
||||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||||
container.resetConfiguration();
|
container.resetConfiguration();
|
||||||
configureDefault(OAuthClient.AUTH_SERVER_ROOT, false, null);
|
configureDefault(OAuthClient.AUTH_SERVER_ROOT, false, null);
|
||||||
container.restartServer();
|
container.restartServer();
|
||||||
|
@ -58,7 +58,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
|
||||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||||
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
||||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||||
List<String> additionalArgs = new ArrayList<>();
|
List<String> additionalArgs = new ArrayList<>();
|
||||||
URI frontendUri = URI.create(frontendUrl);
|
URI frontendUri = URI.create(frontendUrl);
|
||||||
// enable proxy so that we can check headers are taken into account when building urls
|
// enable proxy so that we can check headers are taken into account when building urls
|
||||||
|
|
|
@ -654,6 +654,16 @@
|
||||||
</configuration>
|
</configuration>
|
||||||
</container>
|
</container>
|
||||||
|
|
||||||
|
<container qualifier="auth-server-quarkus-embedded" mode="manual" >
|
||||||
|
<configuration>
|
||||||
|
<property name="enabled">${auth.server.quarkus.embedded}</property>
|
||||||
|
<property name="adapterImplClass">
|
||||||
|
org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusEmbeddedDeployableContainer
|
||||||
|
</property>
|
||||||
|
<property name="bindHttpPortOffset">${auth.server.port.offset}</property>
|
||||||
|
</configuration>
|
||||||
|
</container>
|
||||||
|
|
||||||
<!-- Clustering with Quarkus -->
|
<!-- Clustering with Quarkus -->
|
||||||
<group qualifier="auth-server-quarkus-cluster">
|
<group qualifier="auth-server-quarkus-cluster">
|
||||||
<container qualifier="auth-server-quarkus-backend1" mode="manual" >
|
<container qualifier="auth-server-quarkus-backend1" mode="manual" >
|
||||||
|
|
|
@ -92,6 +92,7 @@
|
||||||
|
|
||||||
<auth.server.remote>false</auth.server.remote>
|
<auth.server.remote>false</auth.server.remote>
|
||||||
<auth.server.quarkus>false</auth.server.quarkus>
|
<auth.server.quarkus>false</auth.server.quarkus>
|
||||||
|
<auth.server.quarkus.embedded>false</auth.server.quarkus.embedded>
|
||||||
|
|
||||||
<auth.server.profile/>
|
<auth.server.profile/>
|
||||||
<auth.server.feature/>
|
<auth.server.feature/>
|
||||||
|
@ -569,6 +570,8 @@
|
||||||
|
|
||||||
<auth.server.remote>${auth.server.remote}</auth.server.remote>
|
<auth.server.remote>${auth.server.remote}</auth.server.remote>
|
||||||
<auth.server.quarkus>${auth.server.quarkus}</auth.server.quarkus>
|
<auth.server.quarkus>${auth.server.quarkus}</auth.server.quarkus>
|
||||||
|
<auth.server.quarkus.embedded>${auth.server.quarkus.embedded}</auth.server.quarkus.embedded>
|
||||||
|
<jboss.server.config.dir>${auth.server.config.dir}</jboss.server.config.dir>
|
||||||
|
|
||||||
<adapter.test.props>${adapter.test.props}</adapter.test.props>
|
<adapter.test.props>${adapter.test.props}</adapter.test.props>
|
||||||
<examples.home>${examples.home}</examples.home>
|
<examples.home>${examples.home}</examples.home>
|
||||||
|
@ -766,6 +769,20 @@
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>auth-server-quarkus-embedded</id>
|
||||||
|
<properties>
|
||||||
|
<auth.server>quarkus</auth.server>
|
||||||
|
<auth.server.quarkus.embedded>true</auth.server.quarkus.embedded>
|
||||||
|
<auth.server.jboss>false</auth.server.jboss>
|
||||||
|
<auth.server.undertow>false</auth.server.undertow>
|
||||||
|
<auth.server.config.dir>${auth.server.home}/conf</auth.server.config.dir>
|
||||||
|
<auth.server.quarkus.skip.unpack>false</auth.server.quarkus.skip.unpack>
|
||||||
|
<auth.server.undertow.skip.unpack>true</auth.server.undertow.skip.unpack>
|
||||||
|
<auth.server.jboss.skip.unpack>true</auth.server.jboss.skip.unpack>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
|
||||||
<profile>
|
<profile>
|
||||||
<id>auth-server-cluster-quarkus</id>
|
<id>auth-server-cluster-quarkus</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
|
Loading…
Reference in a new issue