Move AdminClientTest to the new testsuite (#44705)

* Moving files to the new test suite

Signed-off-by: Simon Vacek <simonvacky@email.cz>

* Move AdminClientTest to the new testsuite

Part of: #35040

Signed-off-by: Simon Vacek <simonvacky@email.cz>
Co-authored: Lukas Hanusovsky <lhanusov@redhat.com>

* Refactoring of ManagedCertificates

* Fix compatiblity issue with ManagedCertificates dependency

Signed-off-by: stianst <stianst@gmail.com>

* Fixing trustStrategy for SSLContext truststore.

Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>

* Fix FIPS

Signed-off-by: stianst <stianst@gmail.com>

---------

Signed-off-by: Simon Vacek <simonvacky@email.cz>
Signed-off-by: stianst <stianst@gmail.com>
Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>
Co-authored-by: Simon Vacek <simonvacky@email.cz>
Co-authored-by: stianst <stianst@gmail.com>
This commit is contained in:
Lukas Hanusovsky 2025-12-17 15:31:22 +01:00 committed by GitHub
parent ca617d9711
commit 92849ef5d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 605 additions and 582 deletions

View File

@ -54,7 +54,7 @@ public class ClusteredKeycloakServer implements KeycloakServer {
}
@Override
public void start(KeycloakServerConfigBuilder configBuilder) {
public void start(KeycloakServerConfigBuilder configBuilder, boolean tlsEnabled) {
int numServers = containers.length;
CountdownLatchLoggingConsumer clusterLatch = new CountdownLatchLoggingConsumer(numServers, String.format(CLUSTER_VIEW_REGEX, numServers));
String[] imagePeServer = null;
@ -155,11 +155,6 @@ public class ClusteredKeycloakServer implements KeycloakServer {
return getManagementBaseUrl(0);
}
@Override
public boolean isTlsEnabled() {
return false;
}
public int getBasePort(int index) {
return containers[index].getMappedPort(REQUEST_PORT);
}

View File

@ -14,11 +14,11 @@ public class AdminClientFactorySupplier implements Supplier<AdminClientFactory,
@Override
public AdminClientFactory getValue(InstanceContext<AdminClientFactory, InjectAdminClientFactory> instanceContext) {
KeycloakServer server = instanceContext.getDependency(KeycloakServer.class);
ManagedCertificates managedCert = instanceContext.getDependency(ManagedCertificates.class);
if (!server.isTlsEnabled()) {
if (!managedCert.isTlsEnabled()) {
return new AdminClientFactory(server.getBaseUrl());
} else {
ManagedCertificates managedCert = instanceContext.getDependency(ManagedCertificates.class);
SSLContext sslContext = managedCert.getClientSSLContext();
return new AdminClientFactory(server.getBaseUrl(), sslContext);
}

View File

@ -9,7 +9,6 @@ import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.server.KeycloakServer;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
@ -22,10 +21,9 @@ public class HttpClientSupplier implements Supplier<HttpClient, InjectHttpClient
public HttpClient getValue(InstanceContext<HttpClient, InjectHttpClient> instanceContext) {
HttpClientBuilder builder = HttpClientBuilder.create();
KeycloakServer server = instanceContext.getDependency(KeycloakServer.class);
if (server.isTlsEnabled()) {
ManagedCertificates managedCerts = instanceContext.getDependency(ManagedCertificates.class);
ManagedCertificates managedCerts = instanceContext.getDependency(ManagedCertificates.class);
if (managedCerts.isTlsEnabled()) {
SSLContext sslContext = managedCerts.getClientSSLContext();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext,

View File

@ -5,6 +5,8 @@ import org.keycloak.common.util.KeystoreUtil;
public class CertificatesConfigBuilder {
private KeystoreUtil.KeystoreFormat keystoreFormat = KeystoreUtil.KeystoreFormat.JKS;
private boolean tlsEnabled = false;
private boolean mTlsEnabled = false;
public CertificatesConfigBuilder() {
}
@ -17,4 +19,22 @@ public class CertificatesConfigBuilder {
public KeystoreUtil.KeystoreFormat getKeystoreFormat() {
return this.keystoreFormat;
}
public CertificatesConfigBuilder tlsEnabled(boolean tlsEnabled) {
this.tlsEnabled = tlsEnabled;
return this;
}
public boolean isTlsEnabled() {
return tlsEnabled || mTlsEnabled;
}
public CertificatesConfigBuilder mTlsEnabled(boolean mTlsEnabled) {
this.mTlsEnabled = mTlsEnabled;
return this;
}
public boolean isMTlsEnabled() {
return mTlsEnabled;
}
}

View File

@ -31,7 +31,7 @@ public class CertificatesSupplier implements Supplier<ManagedCertificates, Injec
@Override
public boolean compatible(InstanceContext<ManagedCertificates, InjectCertificates> a, RequestedInstance<ManagedCertificates, InjectCertificates> b) {
return true;
return a.getAnnotation().config().equals(b.getAnnotation().config());
}
@Override

View File

@ -5,7 +5,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
@ -20,25 +19,32 @@ import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.crypto.def.DefaultCryptoProvider;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.ssl.SSLContextBuilder;
import org.jboss.logging.Logger;
public class ManagedCertificates {
private static final Logger LOGGER = Logger.getLogger(ManagedCertificates.class);
private final static Path KEYSTORES_DIR = Path.of(System.getProperty("java.io.tmpdir"));
private static final String STORE_PASSWORD = "mysuperstrongstorepassword";
private static final char[] STORE_PASSWORD_CHARS = STORE_PASSWORD.toCharArray();
private final boolean tlsEnabled;
private final boolean mTlsEnabled;
private final KeystoreUtil.KeystoreFormat keystoreFormat;
private final CryptoProvider cryptoProvider;
private KeyStore serverKeyStore;
private KeyStore clientsTrustStore;
private final Path serverKeystorePath;
private final Path clientsTruststorePath;
private final char[] password;
private final Path serverTruststorePath;
private final static Path KEYSTORES_DIR = Path.of(System.getProperty("java.io.tmpdir"));
private final static String PRV_KEY_ENTRY = "prvKey";
public final static String CERT_ENTRY = "cert";
private final Path clientKeystorePath;
private KeyStore clientKeyStore;
private final Path clientTruststorePath;
private KeyStore clientTrustStore;
private SSLContext clientSslContext;
public ManagedCertificates(CertificatesConfigBuilder configBuilder) throws ManagedCertificatesException {
if (!CryptoIntegration.isInitialised()) {
@ -46,87 +52,120 @@ public class ManagedCertificates {
}
cryptoProvider = CryptoIntegration.getProvider();
serverKeystorePath = KEYSTORES_DIR.resolve("kc-testing-server-keystore" + "." + configBuilder.getKeystoreFormat().getPrimaryExtension());
clientsTruststorePath = KEYSTORES_DIR.resolve("kc-testing-clients-truststore" + "." + configBuilder.getKeystoreFormat().getPrimaryExtension());
keystoreFormat = configBuilder.getKeystoreFormat();
tlsEnabled = configBuilder.isTlsEnabled();
mTlsEnabled = configBuilder.isMTlsEnabled();
password = configBuilder.getKeystoreFormat() == KeystoreUtil.KeystoreFormat.JKS ? "password".toCharArray() : "passwordpassword".toCharArray();
serverKeystorePath = resolvePath("kc-testing-server-keystore");
serverTruststorePath = resolvePath("kc-testing-server-truststore");
initServerCerts(configBuilder.getKeystoreFormat());
}
clientKeystorePath = resolvePath("kc-testing-client-keystore");
clientTruststorePath = resolvePath("kc-testing-client-truststore");
public String getKeycloakServerKeyStorePath() {
return serverKeystorePath.toString();
}
public String getKeycloakServerKeyStorePassword() {
return String.valueOf(password);
}
public KeyStore getClientTrustStore() {
return clientsTrustStore;
}
public X509Certificate getKeycloakServerCertificate() {
try {
return (X509Certificate) serverKeyStore.getCertificate(CERT_ENTRY);
} catch (KeyStoreException e) {
throw new ManagedCertificatesException(e);
if (!Files.exists(serverKeystorePath) || !Files.exists(serverTruststorePath) || !Files.exists(clientKeystorePath) || !Files.exists(clientTruststorePath)) {
createStores();
} else {
clientKeyStore = load(clientKeystorePath);
clientTrustStore = load(clientTruststorePath);
}
clientSslContext = tlsEnabled ? createClientSSLContext() : null;
}
public String getServerKeyStorePath() {
return tlsEnabled ? serverKeystorePath.toString() : null;
}
public String getServerKeyStorePassword() {
return tlsEnabled ? STORE_PASSWORD : null;
}
public String getServerTrustStorePath() {
return mTlsEnabled ? serverTruststorePath.toString() : null;
}
public String getServerTrustStorePassword() {
return mTlsEnabled ? STORE_PASSWORD : null;
}
public SSLContext getClientSSLContext() {
return clientSslContext;
}
public boolean isTlsEnabled() {
return tlsEnabled;
}
public boolean isMTlsEnabled() {
return mTlsEnabled;
}
private SSLContext createClientSSLContext() {
try {
return SSLContextBuilder.create()
.loadTrustMaterial(clientsTrustStore, null)
.build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new ManagedCertificatesException(e);
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create()
.loadTrustMaterial(clientTrustStore, TrustAllStrategy.INSTANCE);
if (mTlsEnabled) {
sslContextBuilder.loadKeyMaterial(clientKeyStore, STORE_PASSWORD_CHARS);
}
return sslContextBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void initServerCerts(KeystoreUtil.KeystoreFormat keystoreFormat) throws ManagedCertificatesException {
private void createStores() {
try {
serverKeyStore = cryptoProvider.getKeyStore(keystoreFormat);
clientsTrustStore = cryptoProvider.getKeyStore(keystoreFormat);
KeyPair serverKeyPair = generateKeyPair();
X509Certificate serverCertificate = generateX509CertificateCertificate(serverKeyPair, "localhost");
if (Files.exists(serverKeystorePath) && Files.exists(clientsTruststorePath)) {
LOGGER.debugv("Existing Server KeyStore files found in {0}", KEYSTORES_DIR);
KeyPair clientKeyPair = generateKeyPair();
X509Certificate clientCertificate = generateX509CertificateCertificate(clientKeyPair, "myclient");
loadKeyStore(serverKeyStore, serverKeystorePath, password);
loadKeyStore(clientsTrustStore, clientsTruststorePath, password);
} else {
LOGGER.debugv("Generating Server KeyStore files in {0}", KEYSTORES_DIR);
KeyStore serverKeyStore = cryptoProvider.getKeyStore(keystoreFormat);
serverKeyStore.load(null, STORE_PASSWORD_CHARS);
serverKeyStore.setKeyEntry("server-key", serverKeyPair.getPrivate(), STORE_PASSWORD_CHARS, new X509Certificate[] { serverCertificate });
save(serverKeyStore, serverKeystorePath);
generateKeystoreAndTruststore(serverKeyStore, clientsTrustStore, "localhost");
// store the generated keystore and truststore in a temp folder
try (FileOutputStream fos = new FileOutputStream(serverKeystorePath.toFile())) {
serverKeyStore.store(fos, password);
}
try (FileOutputStream fos = new FileOutputStream(clientsTruststorePath.toFile())) {
clientsTrustStore.store(fos, password);
}
}
KeyStore serverTrustStore = cryptoProvider.getKeyStore(keystoreFormat);
serverTrustStore.load(null, STORE_PASSWORD_CHARS);
serverTrustStore.setCertificateEntry("myclient-certificate", clientCertificate);
save(serverTrustStore, serverTruststorePath);
clientKeyStore = cryptoProvider.getKeyStore(keystoreFormat);
clientKeyStore.load(null, STORE_PASSWORD_CHARS);
clientKeyStore.setKeyEntry("client-key", clientKeyPair.getPrivate(), STORE_PASSWORD_CHARS, new X509Certificate[] { clientCertificate });
save(clientKeyStore, clientKeystorePath);
clientTrustStore = cryptoProvider.getKeyStore(keystoreFormat);
clientTrustStore.load(null, STORE_PASSWORD_CHARS);
clientTrustStore.setCertificateEntry("server-certificate", serverCertificate);
save(clientTrustStore, clientTruststorePath);
} catch (Exception e) {
throw new ManagedCertificatesException(e);
}
}
private void loadKeyStore(KeyStore keyStore, Path keyStorePath, char[] keyStorePasswd) throws NoSuchAlgorithmException, IOException, CertificateException {
try (FileInputStream fis = new FileInputStream(keyStorePath.toFile())) {
keyStore.load(fis, keyStorePasswd);
private Path resolvePath(String fileName) {
return KEYSTORES_DIR.resolve(fileName + "." + keystoreFormat.getPrimaryExtension());
}
private void save(KeyStore store, Path storePath) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
try (FileOutputStream fos = new FileOutputStream(storePath.toFile())) {
store.store(fos, STORE_PASSWORD_CHARS);
}
}
private void generateKeystoreAndTruststore(KeyStore keyStore, KeyStore trustStore, String subject) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException, IOException, KeyStoreException, Exception {
keyStore.load(null);
trustStore.load(null);
KeyPair keyPair = generateKeyPair();
X509Certificate cert = generateX509CertificateCertificate(keyPair, subject);
keyStore.setCertificateEntry(CERT_ENTRY, cert);
trustStore.setCertificateEntry(CERT_ENTRY, cert);
keyStore.setKeyEntry(PRV_KEY_ENTRY, keyPair.getPrivate(), password, new X509Certificate[]{cert});
private KeyStore load(Path keyStorePath) {
try (FileInputStream fis = new FileInputStream(keyStorePath.toFile())) {
KeyStore keyStore = cryptoProvider.getKeyStore(keystoreFormat);
keyStore.load(fis, STORE_PASSWORD_CHARS);
return keyStore;
} catch (Exception e) {
throw new ManagedCertificatesException(e);
}
}
private KeyPair generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {

View File

@ -11,6 +11,7 @@ import java.util.Objects;
import java.util.Set;
import org.keycloak.testframework.TestFrameworkExecutor;
import org.keycloak.testframework.server.KeycloakServer;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
@ -165,7 +166,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
}
Class testClass = testInstance.getClass();
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), KeycloakServer.class);
if (requestedServerInstance != null) {
requestedInstances.add(requestedServerInstance);
}
@ -177,6 +178,17 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
}
}
List<RequiredDependencies.RequiredDependency> dependencies = requestedInstances.stream().flatMap(r -> r.getSupplier().getDependencies().getList().stream()).toList();
for (RequiredDependencies.RequiredDependency dependency : dependencies) {
boolean dependencyRequested = requestedInstances.stream().anyMatch(r -> r.getValueType().equals(dependency.valueType()) && Objects.equals(r.getRef(), dependency.ref()));
if (!dependencyRequested) {
Supplier<?, ?> supplier = extensions.findSupplierByType(dependency.valueType());
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), dependency.ref());
RequestedInstance<?, ?> requestDependency = createRequestedInstance(new Annotation[]{ defaultAnnotation }, dependency.valueType());
requestedInstances.add(requestDependency);
}
}
logger.logRequestedInstances(requestedInstances);
}

View File

@ -0,0 +1,45 @@
package org.keycloak.testframework.injection;
import java.util.LinkedList;
import java.util.List;
public class RequiredDependencies {
private static final RequiredDependencies NONE = new RequiredDependencies();
private List<RequiredDependency> dependencies;
public static RequiredDependencies none() {
return NONE;
}
public static RequiredDependencies create(Class<?> valueType) {
return new RequiredDependencies().add(valueType);
}
public static RequiredDependencies create(Class<?> valueType, String ref) {
return new RequiredDependencies().add(valueType, ref);
}
public RequiredDependencies add(Class<?> valueType) {
dependencies.add(new RequiredDependency(valueType, null));
return this;
}
public RequiredDependencies add(Class<?> valueType, String ref) {
dependencies.add(new RequiredDependency(valueType, ref));
return this;
}
public RequiredDependencies() {
this.dependencies = new LinkedList<>();
}
List<RequiredDependency> getList() {
return dependencies;
}
public record RequiredDependency(Class<?> valueType, String ref) {
}
}

View File

@ -44,4 +44,8 @@ public interface Supplier<T, S extends Annotation> {
return SupplierOrder.DEFAULT;
}
default RequiredDependencies getDependencies() {
return RequiredDependencies.none();
}
}

View File

@ -122,6 +122,11 @@ public class UserConfigBuilder {
return this;
}
public UserConfigBuilder serviceAccountId(String serviceAccountClientId) {
rep.setServiceAccountClientId(serviceAccountClientId);
return this;
}
/**
* Best practice is to use other convenience methods when configuring a user, but while the framework is under
* active development there may not be a way to perform all updates required. In these cases this method allows

View File

@ -10,6 +10,7 @@ import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.Registry;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.RequiredDependencies;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.injection.SupplierHelpers;
import org.keycloak.testframework.injection.SupplierOrder;
@ -50,10 +51,17 @@ public abstract class AbstractKeycloakServerSupplier implements Supplier<Keycloa
ServerConfigInterceptorHelper interceptor = new ServerConfigInterceptorHelper(instanceContext.getRegistry());
command = interceptor.intercept(command, instanceContext);
if (command.tlsEnabled()) {
ManagedCertificates managedCert = instanceContext.getDependency(ManagedCertificates.class);
command.option("https-key-store-file", managedCert.getKeycloakServerKeyStorePath());
command.option("https-key-store-password", managedCert.getKeycloakServerKeyStorePassword());
ManagedCertificates managedCert = instanceContext.getDependency(ManagedCertificates.class);
if (managedCert.isTlsEnabled()) {
command.option("https-key-store-file", managedCert.getServerKeyStorePath());
command.option("https-key-store-password", managedCert.getServerKeyStorePassword());
}
if (managedCert.isMTlsEnabled()) {
command.option("https-client-auth", "request");
command.option("https-trust-store-file", managedCert.getServerTrustStorePath());
command.option("https-trust-store-password", managedCert.getServerTrustStorePassword());
}
command.log().fromConfig(Config.getConfig());
@ -66,7 +74,7 @@ public abstract class AbstractKeycloakServerSupplier implements Supplier<Keycloa
long start = System.currentTimeMillis();
KeycloakServer server = getServer();
server.start(command);
server.start(command, managedCert.isTlsEnabled());
getLogger().infov("Keycloak test server started in {0} ms", System.currentTimeMillis() - start);
@ -88,6 +96,11 @@ public abstract class AbstractKeycloakServerSupplier implements Supplier<Keycloa
instanceContext.getValue().stop();
}
@Override
public RequiredDependencies getDependencies() {
return RequiredDependencies.create(ManagedCertificates.class);
}
public abstract KeycloakServer getServer();
public abstract boolean requiresDatabase();

View File

@ -22,15 +22,15 @@ public class DistributionKeycloakServer implements KeycloakServer {
private RawKeycloakDistribution keycloak;
private final boolean debug;
private boolean enableTls = false;
private boolean tlsEnabled = false;
public DistributionKeycloakServer(boolean debug) {
this.debug = debug;
}
@Override
public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder) {
enableTls = keycloakServerConfigBuilder.tlsEnabled();
public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder, boolean tlsEnabled) {
this.tlsEnabled = tlsEnabled;
keycloak = new RawKeycloakDistribution(false, MANUAL_STOP, false, RE_CREATE, REMOVE_BUILD_OPTIONS_AFTER_BUILD, REQUEST_PORT, new LoggingOutputConsumer());
// RawKeycloakDistribution sets "DEBUG_SUSPEND", not "DEBUG" when debug is passed to constructor
@ -56,7 +56,7 @@ public class DistributionKeycloakServer implements KeycloakServer {
@Override
public String getBaseUrl() {
if (isTlsEnabled()) {
if (tlsEnabled) {
return "https://localhost:8443";
} else {
return "http://localhost:8080";
@ -65,18 +65,13 @@ public class DistributionKeycloakServer implements KeycloakServer {
@Override
public String getManagementBaseUrl() {
if (isTlsEnabled()) {
if (tlsEnabled) {
return "https://localhost:9000";
} else {
return "http://localhost:9000";
}
}
@Override
public boolean isTlsEnabled() {
return enableTls;
}
private static final class LoggingOutputConsumer implements OutputConsumer {
private static final Pattern LOG_PATTERN = Pattern.compile("([^ ]*) ([^ ]*) ([A-Z]*)([ ]*)(.*)");

View File

@ -16,12 +16,12 @@ public class EmbeddedKeycloakServer implements KeycloakServer {
private Keycloak keycloak;
private Path homeDir;
private boolean enableTls = false;
private boolean tlsEnabled = false;
@Override
public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder) {
public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder, boolean tlsEnabled) {
Keycloak.Builder builder = Keycloak.builder().setVersion(Version.VERSION);
enableTls = keycloakServerConfigBuilder.tlsEnabled();
this.tlsEnabled = tlsEnabled;
for(Dependency dependency : keycloakServerConfigBuilder.toDependencies()) {
builder.addDependency(dependency.getGroupId(), dependency.getArtifactId(), "");
@ -64,7 +64,7 @@ public class EmbeddedKeycloakServer implements KeycloakServer {
@Override
public String getBaseUrl() {
if (isTlsEnabled()) {
if (tlsEnabled) {
return "https://localhost:8443";
} else {
return "http://localhost:8080";
@ -73,15 +73,10 @@ public class EmbeddedKeycloakServer implements KeycloakServer {
@Override
public String getManagementBaseUrl() {
if (isTlsEnabled()) {
if (tlsEnabled) {
return "https://localhost:9001";
} else {
return "http://localhost:9001";
}
}
@Override
public boolean isTlsEnabled() {
return enableTls;
}
}

View File

@ -2,7 +2,7 @@ package org.keycloak.testframework.server;
public interface KeycloakServer {
void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder);
void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder, boolean tlsEnabled);
void stop();
@ -10,5 +10,4 @@ public interface KeycloakServer {
String getManagementBaseUrl();
boolean isTlsEnabled();
}

View File

@ -34,7 +34,6 @@ public class KeycloakServerConfigBuilder {
private final Set<Path> configFiles = new HashSet<>();
private CacheType cacheType = CacheType.LOCAL;
private boolean externalInfinispan = false;
private boolean tlsEnabled = false;
private KeycloakServerConfigBuilder(String command) {
this.command = command;
@ -107,15 +106,6 @@ public class KeycloakServerConfigBuilder {
dependencies.add(new DependencyBuilder().setGroupId(groupId).setArtifactId(artifactId).build());
return this;
}
public KeycloakServerConfigBuilder tlsEnabled(boolean enabled) {
tlsEnabled = enabled;
return this;
}
public boolean tlsEnabled() {
return tlsEnabled ;
}
public KeycloakServerConfigBuilder cacheConfigFile(String resourcePath) {
try {

View File

@ -17,13 +17,13 @@ import static java.lang.System.out;
public class RemoteKeycloakServer implements KeycloakServer {
private boolean enableTls = false;
private boolean tlsEnabled = false;
private String kcwCommand;
@Override
public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder) {
enableTls = keycloakServerConfigBuilder.tlsEnabled();
public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder, boolean tlsEnabled) {
this.tlsEnabled = tlsEnabled;
kcwCommand = Config.getValueTypeConfig(KeycloakServer.class, "kcw", null, String.class);
if (!verifyRunningKeycloak()) {
if (kcwCommand != null) {
@ -41,7 +41,7 @@ public class RemoteKeycloakServer implements KeycloakServer {
@Override
public String getBaseUrl() {
if (isTlsEnabled()) {
if (tlsEnabled) {
return "https://localhost:8443";
} else {
return "http://localhost:8080";
@ -50,18 +50,13 @@ public class RemoteKeycloakServer implements KeycloakServer {
@Override
public String getManagementBaseUrl() {
if (isTlsEnabled()) {
if (tlsEnabled) {
return "https://localhost:9000";
} else {
return "http://localhost:9000";
}
}
@Override
public boolean isTlsEnabled() {
return enableTls;
}
private void printStartupInstructionsManual(KeycloakServerConfigBuilder config) {
out.println("Remote Keycloak server is not running on " + getBaseUrl() + ", please start Keycloak with:");
out.println();

View File

@ -2,23 +2,18 @@ package org.keycloak.test.examples;
import java.io.IOException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.annotations.InjectHttpClient;
import org.keycloak.testframework.annotations.InjectKeycloakUrls;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.https.CertificatesConfig;
import org.keycloak.testframework.https.CertificatesConfigBuilder;
import org.keycloak.testframework.https.InjectCertificates;
import org.keycloak.testframework.https.ManagedCertificates;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.testframework.server.KeycloakUrls;
import org.apache.http.HttpResponse;
@ -27,7 +22,7 @@ import org.apache.http.client.methods.HttpGet;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@KeycloakIntegrationTest(config = TlsEnabledTest.TlsEnabledServerConfig.class)
@KeycloakIntegrationTest
public class TlsEnabledTest {
@InjectHttpClient
@ -39,32 +34,20 @@ public class TlsEnabledTest {
@InjectAdminClient
Keycloak adminClient;
@InjectCertificates
@InjectCertificates(config = TlsEnabledConfig.class)
ManagedCertificates managedCertificates;
@InjectKeycloakUrls
KeycloakUrls keycloakUrls;
@Test
public void testCertSupplier() throws KeyStoreException {
public void testCertSupplier() {
Assertions.assertNotNull(managedCertificates);
KeyStore trustStore = managedCertificates.getClientTrustStore();
Assertions.assertNotNull(trustStore);
Assertions.assertNotNull(managedCertificates.getServerKeyStorePath());
Assertions.assertNull(managedCertificates.getServerTrustStorePath());
X509Certificate cert = managedCertificates.getKeycloakServerCertificate();
Assertions.assertNotNull(cert);
Assertions.assertEquals(cert.getSerialNumber(), ((X509Certificate) trustStore.getCertificate(ManagedCertificates.CERT_ENTRY)).getSerialNumber());
}
@Test
public void testCertDetails() throws CertificateNotYetValidException, CertificateExpiredException {
X509Certificate cert = managedCertificates.getKeycloakServerCertificate();
cert.checkValidity();
Assertions.assertEquals("CN=localhost", cert.getSubjectX500Principal().getName());
Assertions.assertEquals("CN=localhost", cert.getIssuerX500Principal().getName());
Assertions.assertNotNull(managedCertificates.getClientSSLContext());
}
@Test
@ -87,11 +70,10 @@ public class TlsEnabledTest {
Assertions.assertTrue(oAuthClient.doWellKnownRequest().getTokenEndpoint().startsWith("https://"));
}
public static class TlsEnabledServerConfig implements KeycloakServerConfig {
private static class TlsEnabledConfig implements CertificatesConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config.tlsEnabled(true);
}
}

View File

@ -0,0 +1,356 @@
/*
* Copyright 2020 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.tests.admin;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.core.Response;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.admin.AdminClientFactory;
import org.keycloak.testframework.annotations.InjectAdminClientFactory;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.https.CertificatesConfig;
import org.keycloak.testframework.https.CertificatesConfigBuilder;
import org.keycloak.testframework.https.InjectCertificates;
import org.keycloak.testframework.https.ManagedCertificates;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.RealmConfig;
import org.keycloak.testframework.realm.RealmConfigBuilder;
import org.keycloak.testframework.remote.timeoffset.InjectTimeOffSet;
import org.keycloak.testframework.remote.timeoffset.TimeOffSet;
import org.keycloak.testframework.util.ApiUtil;
import org.keycloak.tests.utils.admin.AdminApiUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Test for the various "Advanced" scenarios of java admin-client
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@KeycloakIntegrationTest
public class AdminClientTest {
@InjectRealm(config = TestRealmConfig.class)
ManagedRealm testRealm;
@InjectAdminClientFactory
AdminClientFactory adminClientFactory;
@InjectCertificates(config = MTlsCertificatesEnabled.class)
ManagedCertificates managedCertificates;
@InjectTimeOffSet
TimeOffSet timeOffSet;
private static final String TEST_USER_USERNAME = "test-user@localhost";
private static final String TEST_USER_PASSWORD = "password";
private static final String CLIENT_ID = "service-account-cl";
private static final String CLIENT_SECRET = "secret1";
private static final String X509_CLIENT_ID = "x509-client-sa";
@Test
public void clientCredentialsAuthSuccess() {
Keycloak adminClient = adminClientFactory.create()
.realm(testRealm.getName())
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.autoClose()
.build();
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
timeOffSet.set(1000);
// Check still possible to load the realm after original token expired (admin client should automatically re-authenticate)
realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
}
@Test
public void clientCredentialsClientDisabled() {
Keycloak adminClient = adminClientFactory.create()
.realm(testRealm.getName())
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.autoClose()
.build();
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
// Disable client and check it should not be possible to load the realms anymore
setClientEnabled(CLIENT_ID, false);
// Check not possible to invoke anymore
try {
realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
} finally {
setClientEnabled(CLIENT_ID, true);
}
}
@Test
public void adminAuthCloseUserSession() {
UserResource user = AdminApiUtil.findUserByUsernameId(testRealm.admin(), TEST_USER_USERNAME);
try(Keycloak keycloak = adminClientFactory.create()
.realm(testRealm.getName())
.username(TEST_USER_USERNAME)
.password(TEST_USER_PASSWORD)
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
.build()
) {
// Check possible to load the realm
RealmRepresentation realm = keycloak.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
Assertions.assertEquals(1, user.getUserSessions().size());
}
Assertions.assertEquals(0, user.getUserSessions().size());
}
@Test
public void adminAuthClientDisabled() {
Keycloak adminClient = adminClientFactory.create()
.realm(testRealm.getName())
.username(TEST_USER_USERNAME)
.password(TEST_USER_PASSWORD)
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
.build();
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
// Disable client and check it should not be possible to load the realms anymore
setClientEnabled(Constants.ADMIN_CLI_CLIENT_ID, false);
// Check not possible to invoke anymore
try {
realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
} finally {
setClientEnabled(Constants.ADMIN_CLI_CLIENT_ID, true);
adminClient.close();
}
}
@Test
public void adminAuthUserDisabled() {
Keycloak adminClient = adminClientFactory.create()
.realm(testRealm.getName())
.username(TEST_USER_USERNAME)
.password(TEST_USER_PASSWORD)
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
.build();
Keycloak adminClientOffline = adminClientFactory.create()
.realm(testRealm.getName())
.username(TEST_USER_USERNAME)
.password(TEST_USER_PASSWORD)
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
.scope(OAuth2Constants.OFFLINE_ACCESS)
.build();
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
realm = adminClientOffline.realm(testRealm.getName()).toRepresentation();
Assertions.assertEquals(testRealm.getName(), realm.getRealm());
// Disable user and check it should not be possible to load the realms anymore
setUserEnabled(TEST_USER_USERNAME, false);
// Check not possible to invoke anymore
try {
realm = adminClient.realm(testRealm.getName()).toRepresentation();
Assertions.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
}
try {
realm = adminClientOffline.realm(testRealm.getName()).toRepresentation();
Assertions.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
} finally {
setUserEnabled(TEST_USER_USERNAME, true);
adminClient.close();
adminClientOffline.close();
}
}
@Test
public void scopedClientCredentialsAuthSuccess() {
// we need to create custom scope after import, otherwise the default scopes are missing.
final String scopeName = "myScope";
String scopeId = createScope(scopeName, KeycloakModelUtils.generateId());
AdminApiUtil.findClientByClientId(testRealm.admin(), CLIENT_ID).addOptionalClientScope(scopeId);
// with scope
try (Keycloak adminClient = adminClientFactory.create()
.realm(testRealm.getName())
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.scope(scopeName)
.build()) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assertions.assertTrue(accessToken.getScope().contains(scopeName));
Assertions.assertNotNull(adminClient.realm(testRealm.getName()).clientScopes().get(scopeId).toRepresentation());
}
// without scope
try (Keycloak adminClient = adminClientFactory.create()
.realm(testRealm.getName())
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.build()) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assertions.assertFalse(accessToken.getScope().contains(scopeName));
Assertions.assertNotNull(adminClient.realm(testRealm.getName()).clientScopes().get(scopeId).toRepresentation());
}
}
// A client secret is not necessary when authentication is
// performed via X.509 authorizer.
@Test
public void noClientSecretWithClientCredentialsAuthSuccess() {
final String scopeName = "dummyScope";
String scopeId = createScope(scopeName, KeycloakModelUtils.generateId());
testRealm.admin().clients().get(testRealm.admin().clients().findByClientId(X509_CLIENT_ID).get(0).getId()).addOptionalClientScope(scopeId);
// with scope and no client secret
try (Keycloak adminClient = adminClientFactory.create().realm(testRealm.getName()).grantType(OAuth2Constants.CLIENT_CREDENTIALS).clientId(X509_CLIENT_ID).scope(scopeName).build()) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assertions.assertTrue(accessToken.getScope().contains(scopeName));
Assertions.assertNotNull(adminClient.realm(testRealm.getName()).clientScopes().get(scopeId).toRepresentation());
}
// without scope and no client secret
try (Keycloak adminClient = adminClientFactory.create().realm(testRealm.getName()).grantType(OAuth2Constants.CLIENT_CREDENTIALS).clientId(X509_CLIENT_ID).scope(null).build()) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assertions.assertFalse(accessToken.getScope().contains(scopeName));
Assertions.assertNotNull(adminClient.realm(testRealm.getName()).clientScopes().get(scopeId).toRepresentation());
}
}
private void setUserEnabled(String username, boolean enabled) {
UserResource user = AdminApiUtil.findUserByUsernameId(testRealm.admin(), username);
UserRepresentation userRep = user.toRepresentation();
userRep.setEnabled(enabled);
user.update(userRep);
}
private void setClientEnabled(String clientId, boolean enabled) {
ClientResource client = AdminApiUtil.findClientByClientId(testRealm.admin(), clientId);
ClientRepresentation clientRep = client.toRepresentation();
clientRep.setEnabled(enabled);
client.update(clientRep);
}
private String createScope(String scopeName, String scopeId) {
final ClientScopeRepresentation testScope = new ClientScopeRepresentation();
testScope.setId(scopeId);
testScope.setName(scopeName);
testScope.setProtocol("openid-connect");
Response response = testRealm.admin().clientScopes().create(testScope);
Assertions.assertEquals(201, response.getStatus());
return ApiUtil.getCreatedId(response);
}
private static class TestRealmConfig implements RealmConfig {
@Override
public RealmConfigBuilder configure(RealmConfigBuilder realm) {
realm.addUser(TEST_USER_USERNAME)
.name("test", "user")
.email("testuser@localhost.com")
.emailVerified(true)
.password(TEST_USER_PASSWORD)
.clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)
.roles(OAuth2Constants.OFFLINE_ACCESS);
realm.addClient(CLIENT_ID)
.secret(CLIENT_SECRET)
.serviceAccountsEnabled(true);
realm.addUser(ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + CLIENT_ID)
.name("serviceAccount", "user")
.email("serviceAccountUser@localhost.com")
.serviceAccountId(CLIENT_ID)
.clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN);
realm.addClient(X509_CLIENT_ID)
.serviceAccountsEnabled(true)
.authenticatorType(X509ClientAuthenticator.PROVIDER_ID)
.attribute(X509ClientAuthenticator.ATTR_SUBJECT_DN, "(.*?)(?:$)")
.attribute(X509ClientAuthenticator.ATTR_ALLOW_REGEX_PATTERN_COMPARISON, "true");
// This user is associated with the x509-client-sa service account above and
// give the service account a service account role "realm-management:realm-admin".
// Without the "realm-management:realm-admin" role we won't be able to test any actual
// admin call.
realm.addUser(ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + X509_CLIENT_ID)
.name("x509ServiceAccount", "user")
.email("x509ServiceAccountUser@localhost.com")
.emailVerified(true)
.serviceAccountId(X509_CLIENT_ID)
.clientRoles(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN);
return realm;
}
}
private static class MTlsCertificatesEnabled implements CertificatesConfig {
@Override
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config.tlsEnabled(true).mTlsEnabled(true);
}
}
}

View File

@ -42,7 +42,7 @@ public class FipsNonStrictTestSuite {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.features(Profile.Feature.FIPS).tlsEnabled(true)
return config.features(Profile.Feature.FIPS)
.option("fips-mode", "non-strict")
.dependency("org.bouncycastle", "bc-fips")
.dependency("org.bouncycastle", "bctls-fips")
@ -55,7 +55,7 @@ public class FipsNonStrictTestSuite {
@Override
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config.keystoreFormat(KeystoreUtil.KeystoreFormat.PKCS12);
return config.tlsEnabled(true).keystoreFormat(KeystoreUtil.KeystoreFormat.PKCS12);
}
}
}

View File

@ -42,7 +42,7 @@ public class FipsStrictTestSuite {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.features(Profile.Feature.FIPS).tlsEnabled(true)
return config.features(Profile.Feature.FIPS)
.option("fips-mode", "strict")
.option("spi-password-hashing-pbkdf2-max-padding-length", "14")
.option("spi-password-hashing-pbkdf2-sha256-max-padding-length", "14")
@ -58,7 +58,7 @@ public class FipsStrictTestSuite {
@Override
public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) {
return config.keystoreFormat(KeystoreUtil.KeystoreFormat.BCFKS);
return config.tlsEnabled(true).keystoreFormat(KeystoreUtil.KeystoreFormat.BCFKS);
}
}
}

View File

@ -54,7 +54,7 @@ get_default_commit_message() {
read_commit_message() {
local default_commit_message
default_commit_message=$(get_default_commit_message)
default_commit_message+=$'\n\n'"Part of: #34494"
default_commit_message+=$'\n\n'"Part of: #35040"
echo ""
echo "Provide a different commit message (optional)"

View File

@ -18,18 +18,14 @@
package org.keycloak.testsuite.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.models.Constants;
@ -88,46 +84,6 @@ public class AdminClientUtil {
.build();
}
public static Keycloak createAdminClientWithClientCredentials(String realmName, String clientId, String clientSecret, String scope)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
boolean ignoreUnknownProperties = false;
ResteasyClient resteasyClient = createResteasyClient(ignoreUnknownProperties, null);
return KeycloakBuilder.builder()
.serverUrl(getAuthServerContextRoot() + "/auth")
.realm(realmName)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientId(clientId)
.clientSecret(clientSecret)
.resteasyClient(resteasyClient)
.scope(scope).build();
}
public static Keycloak createMTlsAdminClientWithClientCredentialsWithoutSecret(
final String realmName, String clientId, String scope)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
boolean ignoreUnknownProperties = false;
ResteasyClient resteasyClient = createResteasyClientWithKeystoreAndTruststore();
return KeycloakBuilder.builder()
.serverUrl(getAuthServerContextRoot() + "/auth")
.realm(realmName)
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientId(clientId)
.resteasyClient(resteasyClient)
.scope(scope).build();
}
public static Keycloak createAdminClient() throws Exception {
return createAdminClient(false, getAuthServerContextRoot());
}
public static Keycloak createAdminClient(boolean ignoreUnknownProperties) throws Exception {
return createAdminClient(ignoreUnknownProperties, getAuthServerContextRoot());
}
public static ResteasyClient createResteasyClient() {
try {
return createResteasyClient(false, null);
@ -161,30 +117,6 @@ public class AdminClientUtil {
return resteasyClientBuilder.build();
}
public static ResteasyClient createResteasyClientWithKeystoreAndTruststore() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
ResteasyClientBuilder resteasyClientBuilder = (ResteasyClientBuilder) ResteasyClientBuilder.newBuilder();
if ("true".equals(System.getProperty("auth.server.ssl.required"))) {
File truststore = new File(PROJECT_BUILD_DIRECTORY, "dependency/keystore/keycloak.truststore");
try {
resteasyClientBuilder.sslContext(getSSLContextWithTruststoreAndKeystore(
truststore, "secret",
new File(PROJECT_BUILD_DIRECTORY, "dependency/keystore/keycloak.jks"), "secret"));
} catch (UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
System.setProperty("javax.net.ssl.trustStore", truststore.getAbsolutePath());
}
resteasyClientBuilder
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD)
.connectionPoolSize(NUMBER_OF_CONNECTIONS)
.httpEngine(getCustomClientHttpEngine(resteasyClientBuilder, 1, null));
return resteasyClientBuilder.build();
}
private static SSLContext getSSLContextWithTruststore(File file, String password) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
if (!file.isFile()) {
throw new RuntimeException("Truststore file not found: " + file.getAbsolutePath());
@ -209,27 +141,6 @@ public class AdminClientUtil {
return null;
}
private static SSLContext getSSLContextWithTruststoreAndKeystore(
File trustStore, String truststorePassword, File keystore, String keystorePassword)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException, UnrecoverableKeyException {
if (!trustStore.isFile()) {
throw new RuntimeException("Truststore file not found: " + trustStore.getAbsolutePath());
}
if (!keystore.isFile()) {
throw new RuntimeException("Keystore file not found: " + keystore.getAbsolutePath());
}
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keystore), keystorePassword.toCharArray());
SSLContext theContext = SSLContexts.custom()
.setProtocol("TLS")
.loadTrustMaterial(trustStore, truststorePassword == null ? null : truststorePassword.toCharArray())
.loadKeyMaterial(ks, keystorePassword.toCharArray())
.build();
return theContext;
}
public static ClientHttpEngine getCustomClientHttpEngine(ResteasyClientBuilder resteasyClientBuilder, int validateAfterInactivity, Boolean followRedirects) {
return new CustomClientHttpEngineBuilder43(validateAfterInactivity, followRedirects).resteasyClientBuilder(resteasyClientBuilder).build();
}

View File

@ -1,329 +0,0 @@
/*
* Copyright 2020 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.admin;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.core.Response;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.ServerURLs;
import org.keycloak.testsuite.util.UserBuilder;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
/**
* Test for the various "Advanced" scenarios of java admin-client
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AdminClientTest extends AbstractKeycloakTest {
private static String realmName;
private static String userId;
private static String userName;
private static String clientUUID;
private static String clientId;
private static String clientSecret;
private static String x509ClientUUID;
private static String x509ClientId;
private static String x509UserName;
@Rule
public AssertEvents events = new AssertEvents(this);
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
realmName = "test";
RealmBuilder realm = RealmBuilder.create().name(realmName)
.testEventListener();
clientId = "service-account-cl";
clientSecret = "secret1";
ClientRepresentation enabledAppWithSkipRefreshToken = ClientBuilder.create()
.clientId(clientId)
.secret(clientSecret)
.serviceAccountsEnabled(true)
.build();
realm.client(enabledAppWithSkipRefreshToken);
x509ClientId = "x509-client-sa";
ClientRepresentation x509ServiceAccountClient = ClientBuilder.create()
.clientId(x509ClientId)
.serviceAccountsEnabled(true)
.build();
x509ServiceAccountClient.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
x509ServiceAccountClient.setAttributes(Map.of(
X509ClientAuthenticator.ATTR_SUBJECT_DN, "(.*?)(?:$)",
X509ClientAuthenticator.ATTR_ALLOW_REGEX_PATTERN_COMPARISON, "true"));
realm.client(x509ServiceAccountClient);
userId = KeycloakModelUtils.generateId();
userName = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + enabledAppWithSkipRefreshToken.getClientId();
UserBuilder serviceAccountUser = UserBuilder.create()
.username(userName)
.serviceAccountId(enabledAppWithSkipRefreshToken.getClientId())
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN);
realm.user(serviceAccountUser);
// This user is associated with the x509-client-sa service account above and
// give the service account a service account role "realm-management:realm-admin".
// Without the "realm-management:realm-admin" role we won't be able to test any actual
// admin call.
x509UserName = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + x509ServiceAccountClient.getClientId();
UserBuilder x509ServiceAccountUser = UserBuilder.create()
.username(x509UserName)
.serviceAccountId(x509ServiceAccountClient.getClientId())
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN);
realm.user(x509ServiceAccountUser);
UserBuilder defaultUser = UserBuilder.create()
.id(KeycloakModelUtils.generateId())
.username("test-user@localhost")
.password("password")
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)
.addRoles(OAuth2Constants.OFFLINE_ACCESS);
realm.user(defaultUser);
testRealms.add(realm.build());
}
@Override
public void importRealm(RealmRepresentation realm) {
super.importRealm(realm);
if (Objects.equals(realm.getRealm(), realmName)) {
x509ClientUUID = adminClient.realm(realmName).clients().findByClientId(x509ClientId).get(0).getId();
clientUUID = adminClient.realm(realmName).clients().findByClientId(clientId).get(0).getId();
userId = adminClient.realm(realmName).users().searchByUsername(userName, true).get(0).getId();
}
}
@Test
public void clientCredentialsAuthSuccess() throws Exception {
try (Keycloak adminClient = AdminClientUtil.createAdminClientWithClientCredentials(realmName, clientId, clientSecret, null)) {
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
setTimeOffset(1000);
// Check still possible to load the realm after original token expired (admin client should automatically re-authenticate)
realm = adminClient.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
}
}
@Test
public void clientCredentialsClientDisabled() throws Exception {
try (Keycloak adminClient = AdminClientUtil.createAdminClientWithClientCredentials(realmName, clientId, clientSecret, null)) {
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
// Disable client and check it should not be possible to load the realms anymore
setClientEnabled(clientId, false);
// Check not possible to invoke anymore
try {
realm = adminClient.realm(realmName).toRepresentation();
Assert.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
}
} finally {
setClientEnabled(clientId, true);
}
}
@Test
public void adminAuthCloseUserSession() throws Exception {
UserResource user = ApiUtil.findUserByUsernameId(adminClient.realm(realmName), "test-user@localhost");
try (Keycloak keycloak = AdminClientUtil.createAdminClient(false, realmName, "test-user@localhost", "password", Constants.ADMIN_CLI_CLIENT_ID, null)) {
// Check possible to load the realm
RealmRepresentation realm = keycloak.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
Assert.assertEquals(1, user.getUserSessions().size());
}
Assert.assertEquals(0, user.getUserSessions().size());
}
@Test
public void adminAuthClientDisabled() throws Exception {
try (Keycloak adminClient = AdminClientUtil.createAdminClient(false, realmName, "test-user@localhost", "password", Constants.ADMIN_CLI_CLIENT_ID, null)) {
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
// Disable client and check it should not be possible to load the realms anymore
setClientEnabled(Constants.ADMIN_CLI_CLIENT_ID, false);
// Check not possible to invoke anymore
try {
realm = adminClient.realm(realmName).toRepresentation();
Assert.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
}
} finally {
setClientEnabled(Constants.ADMIN_CLI_CLIENT_ID, true);
}
}
@Test
public void adminAuthUserDisabled() throws Exception {
try (Keycloak adminClient = AdminClientUtil.createAdminClient(false, realmName, "test-user@localhost", "password", Constants.ADMIN_CLI_CLIENT_ID, null);
Keycloak adminClientOffline = AdminClientUtil.createAdminClient(false, ServerURLs.getAuthServerContextRoot(), realmName, "test-user@localhost", "password", Constants.ADMIN_CLI_CLIENT_ID, null, OAuth2Constants.OFFLINE_ACCESS, false);
) {
// Check possible to load the realm
RealmRepresentation realm = adminClient.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
realm = adminClientOffline.realm(realmName).toRepresentation();
Assert.assertEquals(realmName, realm.getRealm());
// Disable client and check it should not be possible to load the realms anymore
setUserEnabled("test-user@localhost", false);
// Check not possible to invoke anymore
try {
realm = adminClient.realm(realmName).toRepresentation();
Assert.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
}
try {
realm = adminClientOffline.realm(realmName).toRepresentation();
Assert.fail("Not expected to successfully get realm");
} catch (NotAuthorizedException nae) {
// Expected
}
} finally {
setUserEnabled("test-user@localhost", true);
}
}
@Test
public void scopedClientCredentialsAuthSuccess() throws Exception {
final RealmResource testRealm = adminClient.realm(realmName);
// we need to create custom scope after import, otherwise the default scopes are missing.
final String scopeName = "myScope";
String scopeId = createScope(testRealm, scopeName, KeycloakModelUtils.generateId());
testRealm.clients().get(clientUUID).addOptionalClientScope(scopeId);
// with scope
try (Keycloak adminClient = AdminClientUtil.createAdminClientWithClientCredentials(realmName,
clientId, clientSecret, scopeName)) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assert.assertTrue(accessToken.getScope().contains(scopeName));
Assert.assertNotNull(adminClient.realm(realmName).clientScopes().get(scopeId).toRepresentation());
}
// without scope
try (Keycloak adminClient = AdminClientUtil.createAdminClientWithClientCredentials(realmName,
clientId, clientSecret, null)) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assert.assertFalse(accessToken.getScope().contains(scopeName));
Assert.assertNotNull(adminClient.realm(realmName).clientScopes().get(scopeId).toRepresentation());
}
}
// A client secret in not necessary when authentication is
// performed via X.509 authorizer.
@Test
public void noClientSecretWithClientCredentialsAuthSuccess() throws Exception {
final RealmResource testRealm = adminClient.realm(realmName);
final String scopeName = "dummyScope";
String scopeId = createScope(testRealm, scopeName, KeycloakModelUtils.generateId());
testRealm.clients().get(x509ClientUUID).addOptionalClientScope(scopeId);
// with scope and no client secret
try (Keycloak adminClient = AdminClientUtil.
createMTlsAdminClientWithClientCredentialsWithoutSecret(realmName, x509ClientId, scopeName)) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assert.assertTrue(accessToken.getScope().contains(scopeName));
Assert.assertNotNull(adminClient.realm(realmName).clientScopes().get(scopeId).toRepresentation());
}
// without scope and no client secret
try (Keycloak adminClient = AdminClientUtil.
createMTlsAdminClientWithClientCredentialsWithoutSecret(realmName, x509ClientId, null)) {
final AccessTokenResponse accessToken = adminClient.tokenManager().getAccessToken();
Assert.assertFalse(accessToken.getScope().contains(scopeName));
Assert.assertNotNull(adminClient.realm(realmName).clientScopes().get(scopeId).toRepresentation());
}
}
private void setClientEnabled(String clientId, boolean enabled) {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realms().realm(realmName), clientId);
ClientRepresentation clientRep = client.toRepresentation();
clientRep.setEnabled(enabled);
client.update(clientRep);
}
private void setUserEnabled(String username, boolean enabled) {
UserResource user = ApiUtil.findUserByUsernameId(adminClient.realms().realm(realmName), username);
UserRepresentation userRep = user.toRepresentation();
userRep.setEnabled(enabled);
user.update(userRep);
}
private String createScope(RealmResource testRealm, String scopeName, String scopeId) {
final ClientScopeRepresentation testScope =
ClientScopeBuilder.create().name(scopeName).protocol("openid-connect").build();
testScope.setId(scopeId);
try (Response response = testRealm.clientScopes().create(testScope)) {
Assert.assertEquals(201, response.getStatus());
return ApiUtil.getCreatedId(response);
}
}
}

View File

@ -1,7 +1,6 @@
account,4
actions,1
adapter,IGNORED
admin,1
authz,3
broker,2
cli,4

View File

@ -15,6 +15,5 @@ SamlClientTest
UserProfileTest
OidcAdvancedClaimToGroupMapperTest
OidcAdvancedClaimToRoleMapperTest
org.keycloak.testsuite.admin.**
org.keycloak.testsuite.authz.**ManagementTest
org.keycloak.testsuite.organization.admin.**