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.runtime.LaunchMode;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
public class IsIntegrationTest extends IsTest {
|
||||
|
||||
|
@ -13,7 +13,7 @@ public class IsIntegrationTest extends IsTest {
|
|||
|
||||
@Override
|
||||
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;
|
||||
|
||||
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.FilenameFilter;
|
||||
|
@ -177,10 +176,6 @@ public final class Environment {
|
|||
})).collect(Collectors.toMap(File::getName, Function.identity()));
|
||||
}
|
||||
|
||||
public static boolean isQuarkusDevMode() {
|
||||
return ProfileManager.getLaunchMode().equals(LaunchMode.DEVELOPMENT);
|
||||
}
|
||||
|
||||
public static boolean isTestLaunchMode() {
|
||||
return "test".equals(System.getProperty(LAUNCH_MODE));
|
||||
}
|
||||
|
@ -220,7 +215,7 @@ public final class Environment {
|
|||
}
|
||||
|
||||
public static boolean isDistribution() {
|
||||
if (isQuarkusDevMode()) {
|
||||
if (LaunchMode.current().isDevOrTest()) {
|
||||
return false;
|
||||
}
|
||||
return getHomeDir() != null;
|
||||
|
@ -233,4 +228,8 @@ public final class Environment {
|
|||
public static boolean isRebuilt() {
|
||||
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.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
|
||||
import org.keycloak.quarkus.runtime.cli.Picocli;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
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.cli.Picocli.ARG_PREFIX;
|
||||
|
||||
|
@ -156,6 +155,22 @@ public final class Configuration {
|
|||
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) {
|
||||
String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name)));
|
||||
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-internal</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-quarkus-dist</artifactId>
|
||||
|
@ -100,6 +106,10 @@
|
|||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mysql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.wagon</groupId>
|
||||
<artifactId>wagon-http-shared</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<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
|
||||
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
|
||||
|
||||
### Arquillian debugging
|
||||
|
@ -465,7 +482,6 @@ This is temporary and database configuration should be more integrated with the
|
|||
|
||||
Activate the following profiles:
|
||||
|
||||
* `quarkus`
|
||||
* `auth-server-cluster-quarkus`
|
||||
|
||||
Then run any cluster test as usual.
|
||||
|
|
|
@ -176,6 +176,19 @@
|
|||
<artifactId>infinispan-server-hotrod</artifactId>
|
||||
<version>${infinispan.version}</version>
|
||||
</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>
|
||||
|
||||
<build>
|
||||
|
@ -1056,6 +1069,51 @@
|
|||
</build>
|
||||
</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>
|
||||
|
||||
</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;
|
||||
|
||||
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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileVisitResult;
|
||||
|
@ -20,68 +10,32 @@ import java.nio.file.LinkOption;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* @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 Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class);
|
||||
|
||||
private KeycloakQuarkusConfiguration configuration;
|
||||
private Process container;
|
||||
private static AtomicBoolean restart = new AtomicBoolean();
|
||||
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
|
||||
public void start() throws LifecycleException {
|
||||
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 {
|
||||
if (suiteContext.get().isAuthServerMigrationEnabled() && configuration.getImportFile() != null) {
|
||||
final String importFileName = configuration.getImportFile();
|
||||
|
@ -201,224 +111,28 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
|||
return builder.start();
|
||||
}
|
||||
|
||||
private ProcessBuilder getProcessBuilder() {
|
||||
List<String> commands = new ArrayList<>();
|
||||
Map<String, String> env = new HashMap<>();
|
||||
@Override
|
||||
protected List<String> configureArgs(List<String> args) {
|
||||
List<String> commands = new ArrayList<>(args);
|
||||
|
||||
commands.add(getCommand());
|
||||
commands.add("-v");
|
||||
commands.add("start");
|
||||
commands.add(0, getCommand());
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
pb.environment().putAll(env);
|
||||
|
||||
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() {
|
||||
if (isWindows()) {
|
||||
return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString();
|
||||
|
@ -426,14 +140,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
|||
return "./kc.sh";
|
||||
}
|
||||
|
||||
public List<String> getAdditionalBuildArgs() {
|
||||
return additionalBuildArgs;
|
||||
}
|
||||
|
||||
public void setAdditionalBuildArgs(List<String> newArgs) {
|
||||
additionalBuildArgs = newArgs;
|
||||
}
|
||||
|
||||
private void destroyDescendantsOnWindows(Process parent, boolean force) {
|
||||
if (!isWindows()) {
|
||||
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 {
|
||||
if (Files.isDirectory(directory, new LinkOption[0])) {
|
||||
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
||||
|
|
|
@ -44,6 +44,7 @@ public class MultipleContainersExtension implements LoadableExtension {
|
|||
logger.info("Multiple containers extension registering.");
|
||||
|
||||
builder.service(DeployableContainer.class, KeycloakQuarkusServerDeployableContainer.class)
|
||||
.service(DeployableContainer.class, KeycloakQuarkusEmbeddedDeployableContainer.class)
|
||||
.service(DeployableContainer.class, InfinispanServerDeployableContainer.class);
|
||||
|
||||
builder.context(ContainerContextImpl.class).context(DeploymentContextImpl.class);
|
||||
|
|
|
@ -38,7 +38,7 @@ public enum StoreProvider {
|
|||
public void addStoreOptions(List<String> commands) {
|
||||
commands.add("--storage=" + getAlias());
|
||||
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-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password"));
|
||||
}
|
||||
|
|
|
@ -57,11 +57,11 @@ public class ContainerAssume {
|
|||
|
||||
public static void assumeNotAuthServerQuarkus() {
|
||||
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() {
|
||||
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.logging.Logger;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||
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.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.Map;
|
||||
import java.util.Optional;
|
||||
|
@ -58,8 +53,8 @@ public class SpiProvidersSwitchingUtils {
|
|||
getQuarkusContainer(container).setAdditionalBuildArgs(Collections.emptyList());
|
||||
}
|
||||
|
||||
private KeycloakQuarkusServerDeployableContainer getQuarkusContainer(Container container) {
|
||||
return (KeycloakQuarkusServerDeployableContainer) container.getDeployableContainer();
|
||||
private AbstractQuarkusDeployableContainer getQuarkusContainer(Container container) {
|
||||
return (AbstractQuarkusDeployableContainer) container.getDeployableContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.models.ClientProvider;
|
||||
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.Collections;
|
||||
|
@ -147,7 +147,7 @@ public class ClientSearchTest extends AbstractClientTest {
|
|||
.toArray(String[]::new);
|
||||
String s = String.join(",",searchableAttributes);
|
||||
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 + "\""));
|
||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||
} else {
|
||||
|
@ -165,7 +165,7 @@ public class ClientSearchTest extends AbstractClientTest {
|
|||
System.clearProperty(SEARCHABLE_ATTRS_PROP);
|
||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
container.setAdditionalBuildArgs(Collections.emptyList());
|
||||
container.restartServer();
|
||||
} else {
|
||||
|
|
|
@ -23,11 +23,8 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
|
||||
import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
|
||||
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 {
|
||||
@ArquillianResource
|
||||
|
@ -166,7 +163,7 @@ public class GroupSearchTest extends AbstractGroupTest {
|
|||
.toArray(String[]::new);
|
||||
String s = String.join(",", searchableAttributes);
|
||||
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
container.setAdditionalBuildArgs(
|
||||
Collections.singletonList("--spi-group-jpa-searchable-attributes=\"" + s + "\""));
|
||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||
|
@ -184,7 +181,7 @@ public class GroupSearchTest extends AbstractGroupTest {
|
|||
System.clearProperty(SEARCHABLE_ATTRS_PROP);
|
||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
container.setAdditionalBuildArgs(Collections.emptyList());
|
||||
container.restartServer();
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.jboss.arquillian.container.test.api.ContainerController;
|
|||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.jboss.logging.Logger;
|
||||
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 java.net.URI;
|
||||
|
@ -33,7 +33,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
|
|||
"keycloak.hostname.fixed.alwaysHttps");
|
||||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
container.resetConfiguration();
|
||||
configureDefault(OAuthClient.AUTH_SERVER_ROOT, false, null);
|
||||
container.restartServer();
|
||||
|
@ -58,7 +58,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
|
|||
controller.start(suiteContext.getAuthServerInfo().getQualifier());
|
||||
} else if (suiteContext.getAuthServerInfo().isQuarkus()) {
|
||||
controller.stop(suiteContext.getAuthServerInfo().getQualifier());
|
||||
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
|
||||
List<String> additionalArgs = new ArrayList<>();
|
||||
URI frontendUri = URI.create(frontendUrl);
|
||||
// enable proxy so that we can check headers are taken into account when building urls
|
||||
|
|
|
@ -654,6 +654,16 @@
|
|||
</configuration>
|
||||
</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 -->
|
||||
<group qualifier="auth-server-quarkus-cluster">
|
||||
<container qualifier="auth-server-quarkus-backend1" mode="manual" >
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
|
||||
<auth.server.remote>false</auth.server.remote>
|
||||
<auth.server.quarkus>false</auth.server.quarkus>
|
||||
<auth.server.quarkus.embedded>false</auth.server.quarkus.embedded>
|
||||
|
||||
<auth.server.profile/>
|
||||
<auth.server.feature/>
|
||||
|
@ -569,6 +570,8 @@
|
|||
|
||||
<auth.server.remote>${auth.server.remote}</auth.server.remote>
|
||||
<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>
|
||||
<examples.home>${examples.home}</examples.home>
|
||||
|
@ -766,6 +769,20 @@
|
|||
</properties>
|
||||
</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>
|
||||
<id>auth-server-cluster-quarkus</id>
|
||||
<properties>
|
||||
|
|
Loading…
Reference in a new issue