From bf08b19e0f0654029c9b5e4fed52256e30a156d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Mon, 24 Aug 2026 23:05:51 +0200 Subject: [PATCH] tests: migrate CLI package to new test framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes: https://github.com/keycloak/keycloak/issues/50756 Signed-off-by: Michal Vavřík --- tests/base/pom.xml | 88 +++ .../keycloak/tests/cli/AbstractCliTest.java | 136 ++++ .../org/keycloak/tests}/cli/KcAdmExec.java | 13 +- .../org/keycloak/tests}/cli/KcRegExec.java | 15 +- .../keycloak/tests/cli/TempFileResource.java | 43 ++ .../tests}/cli/admin/AbstractAdmCliTest.java | 200 +++-- .../tests}/cli/admin/KcAdmCreateTest.java | 20 +- .../tests}/cli/admin/KcAdmSessionTest.java | 34 +- .../keycloak/tests}/cli/admin/KcAdmTest.java | 99 ++- .../tests}/cli/admin/KcAdmTruststoreTest.java | 30 +- .../tests}/cli/admin/KcAdmUpdateTest.java | 45 +- .../tests}/cli/exec/AbstractExec.java | 24 +- .../tests}/cli/exec/AbstractExecBuilder.java | 2 +- .../cli/exec/InteractiveInputStream.java | 4 +- .../tests}/cli/exec/LoggingOutputStream.java | 4 +- .../tests}/cli/exec/StreamReaderThread.java | 6 +- .../cli/registration/AbstractRegCliTest.java | 205 +++-- .../cli/registration/KcRegConfigTest.java | 18 +- .../cli/registration/KcRegCreateTest.java | 51 +- .../tests}/cli/registration/KcRegTest.java | 79 +- .../cli/registration/KcRegTruststoreTest.java | 32 +- .../cli/registration/KcRegUpdateTest.java | 16 +- .../registration/KcRegUpdateTokenTest.java | 16 +- .../keycloak/tests/suites/Base2TestSuite.java | 1 + .../tests/suites/ClusterlessTestSuite.java | 2 + .../tests/suites/FipsNonStrictTestSuite.java | 13 +- .../tests/suites/FipsStrictTestSuite.java | 13 +- .../tests/suites/StatelessTestSuite.java | 4 +- .../suites/VolatileSessionsTestSuite.java | 2 + .../tests}/cli/idp-keycloak-9167.json | 0 .../cli/idp-keycloak-without-sync-mode.json | 0 .../tests}/cli/kcadm/admin-cli-keystore.jks | Bin .../tests}/cli/kcadm/admin-cli-keystore.p12 | Bin .../tests}/cli/kcreg/reg-cli-keystore.jks | Bin .../tests}/cli/kcreg/reg-cli-keystore.p12 | Bin .../tests}/cli/kcreg/saml-sp-metadata.xml | 0 .../keycloak/tests/cli/keycloak.truststore | Bin 0 -> 214735 bytes .../org/keycloak/tests/cli/testrealm.json | 725 ++++++++++++++++++ .../org/keycloak/testsuite/cli/OsArch.java | 53 -- .../org/keycloak/testsuite/cli/OsUtils.java | 60 -- .../cli/exec/ExecutionException.java | 27 - .../testsuite/cli/exec/NullInputStream.java | 12 - .../testsuite/cli/AbstractCliTest.java | 73 -- .../tests/base/testsuites/base-suite | 1 - .../tests/base/testsuites/fips-suite | 4 - .../tests/base/testsuites/stateless-suite | 1 - .../base/testsuites/volatile-sessions-suite | 1 - 47 files changed, 1473 insertions(+), 699 deletions(-) create mode 100644 tests/base/src/test/java/org/keycloak/tests/cli/AbstractCliTest.java rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/KcAdmExec.java (78%) rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/KcRegExec.java (77%) create mode 100644 tests/base/src/test/java/org/keycloak/tests/cli/TempFileResource.java rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/admin/AbstractAdmCliTest.java (61%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/admin/KcAdmCreateTest.java (93%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/admin/KcAdmSessionTest.java (90%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/admin/KcAdmTest.java (91%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/admin/KcAdmTruststoreTest.java (84%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/admin/KcAdmUpdateTest.java (84%) rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/exec/AbstractExec.java (88%) rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/exec/AbstractExecBuilder.java (95%) rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/exec/InteractiveInputStream.java (98%) rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/exec/LoggingOutputStream.java (96%) rename {testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/exec/StreamReaderThread.java (88%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/AbstractRegCliTest.java (71%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/KcRegConfigTest.java (90%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/KcRegCreateTest.java (91%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/KcRegTest.java (91%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/KcRegTruststoreTest.java (83%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/KcRegUpdateTest.java (94%) rename {testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite => tests/base/src/test/java/org/keycloak/tests}/cli/registration/KcRegUpdateTokenTest.java (86%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/idp-keycloak-9167.json (100%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/idp-keycloak-without-sync-mode.json (100%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/kcadm/admin-cli-keystore.jks (100%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/kcadm/admin-cli-keystore.p12 (100%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/kcreg/reg-cli-keystore.jks (100%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/kcreg/reg-cli-keystore.p12 (100%) rename {testsuite/integration-arquillian/tests/base/src/test/resources => tests/base/src/test/resources/org/keycloak/tests}/cli/kcreg/saml-sp-metadata.xml (100%) create mode 100644 tests/base/src/test/resources/org/keycloak/tests/cli/keycloak.truststore create mode 100644 tests/base/src/test/resources/org/keycloak/tests/cli/testrealm.json delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsArch.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsUtils.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java diff --git a/tests/base/pom.xml b/tests/base/pom.xml index 1f044c974858..83d476f82a24 100755 --- a/tests/base/pom.xml +++ b/tests/base/pom.xml @@ -153,8 +153,96 @@ keycloak-model-jpa test + + + org.keycloak + keycloak-admin-cli + test + + + + + + maven-dependency-plugin + + + unpack-client-cli-dist + generate-test-resources + + unpack + + + + + org.keycloak + keycloak-client-cli-dist + ${project.version} + zip + ${project.build.directory}/containers + + + + + + + unpack-client-cli-dist-fips + generate-test-resources + + unpack + + + + ${project.build.directory}/containers-fips/dependency-markers + + + org.keycloak + keycloak-client-cli-dist + ${project.version} + zip + ${project.build.directory}/containers-fips + + + + + + + copy-client-cli-fips-libs + generate-test-resources + + copy + + + + + org.bouncycastle + bc-fips + ${bouncycastle.bcfips.version} + + + org.bouncycastle + bctls-fips + ${bouncycastle.bctls-fips.version} + + + org.bouncycastle + bcutil-fips + ${bouncycastle.bcutilfips.version} + + + ${project.build.directory}/containers-fips/keycloak-client-tools/bin/client/lib + false + + + + + + + execute-sql diff --git a/tests/base/src/test/java/org/keycloak/tests/cli/AbstractCliTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/AbstractCliTest.java new file mode 100644 index 000000000000..65efbba37f90 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/cli/AbstractCliTest.java @@ -0,0 +1,136 @@ +package org.keycloak.tests.cli; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +import jakarta.ws.rs.NotFoundException; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.common.util.KeystoreUtil; +import org.keycloak.testframework.annotations.InjectAdminClient; +import org.keycloak.testframework.annotations.InjectCryptoHelper; +import org.keycloak.testframework.annotations.InjectKeycloakUrls; +import org.keycloak.testframework.crypto.CryptoHelper; +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.server.KeycloakUrls; +import org.keycloak.tests.cli.exec.AbstractExec; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; + +/** + * @author Marko Strukelj + */ +public abstract class AbstractCliTest { + + // Enables TLS on the Keycloak server (https://localhost:8443) while HTTP stays available on localhost + @InjectCertificates(config = TlsEnabledConfig.class) + ManagedCertificates managedCertificates; + + @InjectKeycloakUrls + protected KeycloakUrls keycloakUrls; + + @InjectAdminClient + protected Keycloak adminClient; + + @InjectCryptoHelper + protected CryptoHelper cryptoHelper; + + protected String serverUrl; + + @BeforeEach + public void initServerUrl() { + serverUrl = getHttpBaseUrl(); + } + + // TODO replace hardcoded port with server API once https://github.com/keycloak/keycloak/issues/48089 is resolved + private String getHttpBaseUrl() { + try { + return new URL("http", new URL(keycloakUrls.getBase()).getHost(), 8080, "").toExternalForm(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + protected static void assertExitCodeAndStdErrSize(AbstractExec exe, int exitCode, int stdErrLineCount) { + assertExitCodeAndStreamSizes(exe, exitCode, -1, stdErrLineCount); + } + + protected static void assertExitCodeAndStreamSizes(AbstractExec exe, int exitCode, int stdOutLineCount, int stdErrLineCount) { + Assertions.assertEquals(exitCode, exe.exitCode(), "exitCode == " + exitCode); + if (stdOutLineCount != -1) { + assertLineCount("STDOUT: " + exe.stdoutString(), exe.stdoutLines(), stdOutLineCount); + } + // There is additional logging in case that BC FIPS libraries are used, so the count of logged lines don't match with the case with plain BC used + // Hence we test count of lines just with FIPS disabled + if (stdErrLineCount != -1 && isFipsDisabled()) { + assertLineCount("STDERR: " + exe.stderrString(), exe.stderrLines(), stdErrLineCount); + } + } + + private static void assertLineCount(String label, List lines, int count) { + if (lines.size() == count) { + return; + } + // there is some kind of race condition in 'kcreg' that results in intermittent extra empty line + if (lines.size() == count + 1) { + if ("".equals(lines.get(lines.size()-1))) { + return; + } + } + Assertions.assertEquals(lines.size(), count, label + " has " + lines.size() + " lines (expected: " + count + ")"); + } + + private static boolean isFipsDisabled() { + return !AbstractExec.isFips(); + } + + /** + * Removes a realm created on the fly through the CLI, so it does not leak into other test methods or classes. + */ + protected void removeRealmIfExists(String realm) { + try { + adminClient.realm(realm).remove(); + } catch (NotFoundException e) { + // already removed + } + } + + protected void assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat keystoreType) { + List supportedKeystoreTypes = Arrays.asList(cryptoHelper.getExpectedSupportedKeyStoreTypes()); + Assumptions.assumeTrue(supportedKeystoreTypes.contains(keystoreType.name()), + "Keystore type '" + keystoreType + "' not supported. Supported keystore types: " + supportedKeystoreTypes); + } + + protected static File resourceFile(String path) { + URL resource = AbstractCliTest.class.getClassLoader().getResource(path); + Assertions.assertNotNull(resource, "Cannot load resource from path: " + path); + return new File(resource.getFile()); + } + + /** + * Points the client tools at the FIPS-enabled copy built into {@code target/containers-fips} (which bundles the + * BouncyCastle FIPS provider) so the external kcadm/kcreg processes run under FIPS. Invoked from the FIPS test + * suites before any test runs; non-FIPS runs use the default client tools, which keep the non-FIPS provider. + */ + public static void useFipsClientTools() { + System.setProperty(AbstractExec.CLI_TOOLS_DIR_PROPERTY, + System.getProperty("user.dir") + "/target/containers-fips/keycloak-client-tools"); + } + + static class TlsEnabledConfig implements CertificatesConfig { + + @Override + public CertificatesConfigBuilder configure(CertificatesConfigBuilder config) { + return config.tlsEnabled(true); + } + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcAdmExec.java b/tests/base/src/test/java/org/keycloak/tests/cli/KcAdmExec.java similarity index 78% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcAdmExec.java rename to tests/base/src/test/java/org/keycloak/tests/cli/KcAdmExec.java index 715630c24547..b1c6de93717b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcAdmExec.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/KcAdmExec.java @@ -1,19 +1,18 @@ -package org.keycloak.testsuite.cli; +package org.keycloak.tests.cli; import java.io.InputStream; import java.util.List; -import org.keycloak.common.crypto.FipsMode; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; -import org.keycloak.testsuite.cli.exec.AbstractExec; -import org.keycloak.testsuite.cli.exec.AbstractExecBuilder; +import org.keycloak.tests.cli.exec.AbstractExec; +import org.keycloak.tests.cli.exec.AbstractExecBuilder; /** * @author Marko Strukelj */ public class KcAdmExec extends AbstractExec { - public static final String WORK_DIR = System.getProperty("user.dir") + "/target/containers/keycloak-client-tools"; + public static final String WORK_DIR = System.getProperty(CLI_TOOLS_DIR_PROPERTY, + System.getProperty("user.dir") + "/target/containers/keycloak-client-tools"); public static final String CMD = OS_ARCH.isWindows() ? "kcadm.bat" : "kcadm.sh"; @@ -44,7 +43,7 @@ public static KcAdmExec execute(String args) { public List stderrLines() { List lines = super.stderrLines(); // remove the two lines with the BC provider info if FIPS - return AuthServerTestEnricher.AUTH_SERVER_FIPS_MODE == FipsMode.DISABLED || lines.size() < 2 + return !isFips() || lines.size() < 2 ? lines : lines.subList(2, lines.size()); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java b/tests/base/src/test/java/org/keycloak/tests/cli/KcRegExec.java similarity index 77% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java rename to tests/base/src/test/java/org/keycloak/tests/cli/KcRegExec.java index a0ec0283fffc..b7a35b76e2b6 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/KcRegExec.java @@ -1,19 +1,18 @@ -package org.keycloak.testsuite.cli; +package org.keycloak.tests.cli; import java.io.InputStream; import java.util.List; -import org.keycloak.common.crypto.FipsMode; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; -import org.keycloak.testsuite.cli.exec.AbstractExec; -import org.keycloak.testsuite.cli.exec.AbstractExecBuilder; +import org.keycloak.tests.cli.exec.AbstractExec; +import org.keycloak.tests.cli.exec.AbstractExecBuilder; /** * @author Marko Strukelj */ public class KcRegExec extends AbstractExec { - public static final String WORK_DIR = System.getProperty("user.dir") + "/target/containers/keycloak-client-tools"; + public static final String WORK_DIR = System.getProperty(CLI_TOOLS_DIR_PROPERTY, + System.getProperty("user.dir") + "/target/containers/keycloak-client-tools"); public static final String CMD = OS_ARCH.isWindows() ? "kcreg.bat" : "kcreg.sh"; @@ -44,7 +43,7 @@ public static KcRegExec execute(String args) { public List stderrLines() { List lines = super.stderrLines(); // remove the two lines with the BC provider info if FIPS - return AuthServerTestEnricher.AUTH_SERVER_FIPS_MODE == FipsMode.DISABLED || lines.size() < 2 + return !isFips() || lines.size() < 2 ? lines : lines.subList(2, lines.size()); } @@ -68,4 +67,4 @@ public KcRegExec executeAsync() { } } -} \ No newline at end of file +} diff --git a/tests/base/src/test/java/org/keycloak/tests/cli/TempFileResource.java b/tests/base/src/test/java/org/keycloak/tests/cli/TempFileResource.java new file mode 100644 index 000000000000..04e0ce827d72 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/cli/TempFileResource.java @@ -0,0 +1,43 @@ +package org.keycloak.tests.cli; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +/** + * @author Marko Strukelj + */ +public class TempFileResource implements Closeable { + + private File file; + + public TempFileResource(String filepath) { + file = new File(filepath); + } + + public TempFileResource(File file) { + this.file = file; + } + + public File getFile() { + return file; + } + + public String getName() { + return file.getName(); + } + + public String getAbsolutePath() { + return file.getAbsolutePath(); + } + + public boolean isFile() { + return file.isFile(); + } + + @Override + public void close() throws IOException { + // delete file if it exists + file.delete(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/admin/AbstractAdmCliTest.java similarity index 61% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/admin/AbstractAdmCliTest.java index 99ad995af651..ec91ec531d19 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/admin/AbstractAdmCliTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.admin; +package org.keycloak.tests.cli.admin; import java.io.File; import java.io.FileOutputStream; @@ -6,10 +6,8 @@ import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.UUID; @@ -19,25 +17,30 @@ import org.keycloak.client.cli.config.FileConfigHandler; import org.keycloak.client.cli.config.RealmConfigData; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.RequiredActionProviderRepresentation; +import org.keycloak.testframework.annotations.InjectRealm; import org.keycloak.testframework.realm.ClientBuilder; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmBuilder; +import org.keycloak.testframework.realm.RealmConfig; import org.keycloak.testframework.realm.UserBuilder; -import org.keycloak.testsuite.cli.AbstractCliTest; -import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.tests.cli.AbstractCliTest; +import org.keycloak.tests.cli.KcAdmExec; import org.keycloak.util.JsonSerialization; import org.junit.jupiter.api.Assertions; -import static org.keycloak.testsuite.AbstractAdminTest.loadJson; -import static org.keycloak.testsuite.cli.KcAdmExec.WORK_DIR; -import static org.keycloak.testsuite.cli.KcAdmExec.execute; +import static org.keycloak.tests.cli.KcAdmExec.WORK_DIR; +import static org.keycloak.tests.cli.KcAdmExec.execute; /** * @author Marko Strukelj */ public abstract class AbstractAdmCliTest extends AbstractCliTest { + @InjectRealm(fromJson = "/org/keycloak/tests/cli/testrealm.json", config = AdmCliRealmConfig.class) + ManagedRealm realm; + static boolean runIntermittentlyFailingTests() { return "true".equals(System.getProperty("test.intermittent")); } @@ -46,82 +49,7 @@ static File getDefaultConfigFilePath() { return new File(System.getProperty("user.home") + "/.keycloak/kcadm.config"); } - @Override - public void addTestRealms(List testRealms) { - - RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); - testRealms.add(realmRepresentation); - - // create admin user account with permissions to manage clients - UserRepresentation admin = UserBuilder.create() - .username("user1") - .password("userpass") - .enabled(true) - .build(); - HashMap> clientRoles = new HashMap<>(); - clientRoles.put("realm-management", Arrays.asList("realm-admin")); - admin.setClientRoles(clientRoles); - realmRepresentation.getUsers().add(admin); - - - - // create client with service account to use Signed JWT credentials with - ClientRepresentation regClient = ClientBuilder.create() - .clientId("admin-cli-jwt") - .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g==") - .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) - .serviceAccountsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - // create service account for client reg-cli with permissions to manage clients - addServiceAccount(realmRepresentation, "admin-cli-jwt"); - - - - // create client to use with user account - enable direct grants - regClient = ClientBuilder.create() - .clientId("admin-cli-jwt-direct") - .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g==") - .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) - .directAccessGrantsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - - - - // create client with service account to use client secret with - regClient = ClientBuilder.create() - .clientId("admin-cli-secret") - .secret("password") - .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) - .serviceAccountsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - // create service account for client reg-cli with permissions to manage clients - addServiceAccount(realmRepresentation, "admin-cli-secret"); - - - - - // create client to use with user account - enable direct grants - regClient = ClientBuilder.create() - .clientId("admin-cli-secret-direct") - .secret("password") - .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) - .directAccessGrantsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - } - - FileConfigHandler initCustomConfigFile() { + static FileConfigHandler initCustomConfigFile() { String filename = UUID.randomUUID().toString() + ".config"; File cfgFile = new File(WORK_DIR + "/" + filename); FileConfigHandler handler = new FileConfigHandler(); @@ -129,7 +57,7 @@ FileConfigHandler initCustomConfigFile() { return handler; } - void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, String ... excluded) { + static void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, String ... excluded) { HashSet exclusions = new HashSet<>(Arrays.asList(excluded)); @@ -184,7 +112,7 @@ void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, Str } } - void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigData data1, RealmConfigData data2, String ... excluded) { + static void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigData data1, RealmConfigData data2, String ... excluded) { HashSet exclusions = new HashSet<>(Arrays.asList(excluded)); @@ -244,7 +172,7 @@ void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigDat } } - void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extraOptions, String loginMessage) throws IOException { + static void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extraOptions, String loginMessage) throws IOException { File configFile = getDefaultConfigFilePath(); long lastModified = configFile.exists() ? configFile.lastModified() : 0; @@ -315,11 +243,11 @@ void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extra Assertions.assertEquals(lastModified, lastModified2, "config file not modified"); } - File initTempFile(String extension) throws IOException { + static File initTempFile(String extension) throws IOException { return initTempFile(extension, null); } - File initTempFile(String extension, String content) throws IOException { + static File initTempFile(String extension, String content) throws IOException { String filename = UUID.randomUUID().toString() + extension; File file = new File(KcAdmExec.WORK_DIR + "/" + filename); if (content != null) { @@ -330,23 +258,7 @@ File initTempFile(String extension, String content) throws IOException { return file; } - void addServiceAccount(RealmRepresentation realm, String clientId) { - - UserRepresentation account = UserBuilder.create() - .username("service-account-" + clientId) - .enabled(true) - .serviceAccountId(clientId) - .build(); - - HashMap> clientRoles = new HashMap<>(); - clientRoles.put("realm-management", Arrays.asList("realm-admin")); - - account.setClientRoles(clientRoles); - - realm.getUsers().add(account); - } - - void loginAsUser(File configFile, String server, String realm, String user, String password, boolean envPassword) { + static void loginAsUser(File configFile, String server, String realm, String user, String password, boolean envPassword) { KcAdmExec exe = KcAdmExec.newBuilder() .argsLine("config credentials --server " + server + " --realm " + realm + " --user " + user + (envPassword ? "" : (" --password " + password)) + " --config " + configFile.getAbsolutePath()) @@ -356,8 +268,78 @@ void loginAsUser(File configFile, String server, String realm, String user, Stri assertExitCodeAndStreamSizes(exe, 0, 0, 1); } - void loginAsUser(File configFile, String server, String realm, String user, String password) { + static void loginAsUser(File configFile, String server, String realm, String user, String password) { loginAsUser(configFile, server, realm, user, password, false); } + // Disable the VERIFY_PROFILE required action so users with a minimal or non-standard profile can complete + // direct-grant login: user1 has no e-mail, and KcAdmTest#testUserLoginWithAngleBrackets logs in as the + // 'special>>character' user whose username the default user profile validator would otherwise reject. + static RequiredActionProviderRepresentation disabledVerifyProfile() { + RequiredActionProviderRepresentation verifyProfile = new RequiredActionProviderRepresentation(); + verifyProfile.setAlias("VERIFY_PROFILE"); + verifyProfile.setName("Verify Profile"); + verifyProfile.setProviderId("VERIFY_PROFILE"); + verifyProfile.setEnabled(false); + verifyProfile.setDefaultAction(false); + verifyProfile.setPriority(100); + return verifyProfile; + } + + static class AdmCliRealmConfig implements RealmConfig { + + // certificate used by the signed JWT client credentials (reg-cli) + private static final String CERTIFICATE = "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g=="; + + @Override + public RealmBuilder configure(RealmBuilder realm) { + return realm + .requiredActions(disabledVerifyProfile()) + // create admin user account with permissions to manage clients + .users( + UserBuilder.create() + .username("user1") + .password("userpass") + .enabled(true) + .clientRoles("realm-management", "realm-admin"), + // create service account for client admin-cli-jwt with permissions to manage clients + serviceAccount("admin-cli-jwt"), + // create service account for client admin-cli-secret with permissions to manage clients + serviceAccount("admin-cli-secret")) + .clients( + // create client with service account to use Signed JWT credentials with + ClientBuilder.create() + .clientId("admin-cli-jwt") + .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, CERTIFICATE) + .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) + .serviceAccountsEnabled(), + // create client to use with user account - enable direct grants + ClientBuilder.create() + .clientId("admin-cli-jwt-direct") + .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, CERTIFICATE) + .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) + .directAccessGrantsEnabled(), + // create client with service account to use client secret with + ClientBuilder.create() + .clientId("admin-cli-secret") + .secret("password") + .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) + .serviceAccountsEnabled(), + // create client to use with user account - enable direct grants + ClientBuilder.create() + .clientId("admin-cli-secret-direct") + .secret("password") + .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) + .directAccessGrantsEnabled()); + } + + private static UserBuilder serviceAccount(String clientId) { + return UserBuilder.create() + .username("service-account-" + clientId) + .enabled(true) + .serviceAccountId(clientId) + .clientRoles("realm-management", "realm-admin"); + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmCreateTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmCreateTest.java similarity index 93% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmCreateTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmCreateTest.java index ead22a6a2d1d..573f73492862 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmCreateTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmCreateTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.admin; +package org.keycloak.tests.cli.admin; import java.io.File; import java.io.IOException; @@ -8,22 +8,24 @@ import org.keycloak.client.cli.config.FileConfigHandler; import org.keycloak.models.IdentityProviderModel; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.testsuite.cli.KcAdmExec; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcAdmExec; +import org.keycloak.tests.cli.TempFileResource; import org.keycloak.util.JsonSerialization; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.keycloak.testsuite.cli.KcAdmExec.execute; +import static org.keycloak.tests.cli.KcAdmExec.execute; /** * @author Marko Strukelj */ +@KeycloakIntegrationTest public class KcAdmCreateTest extends AbstractAdmCliTest { @Test - public void testCreateWithRealmOverride() throws IOException { + void testCreateWithRealmOverride() throws IOException { FileConfigHandler handler = initCustomConfigFile(); @@ -42,7 +44,7 @@ public void testCreateWithRealmOverride() throws IOException { } @Test - public void testCreateIDPWithoutSyncMode() throws IOException { + void testCreateIDPWithoutSyncMode() throws IOException { final String realm = "test"; final RealmResource realmResource = adminClient.realm(realm); @@ -50,7 +52,7 @@ public void testCreateIDPWithoutSyncMode() throws IOException { try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass"); - final File idpJson = new File("target/test-classes/cli/idp-keycloak-without-sync-mode.json"); + final File idpJson = resourceFile("org/keycloak/tests/cli/idp-keycloak-without-sync-mode.json"); KcAdmExec exe = execute("create identity-provider/instances/ -r " + realm + " -f " + idpJson.getAbsolutePath() + " --config " + configFile.getFile()); assertExitCodeAndStdErrSize(exe, 0, 1); } @@ -60,7 +62,7 @@ public void testCreateIDPWithoutSyncMode() throws IOException { } @Test - public void testCreateThoroughly() throws IOException { + void testCreateThoroughly() throws IOException { FileConfigHandler handler = initCustomConfigFile(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmSessionTest.java similarity index 90% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmSessionTest.java index 69aeed489322..d01728ad7967 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmSessionTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmSessionTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.admin; +package org.keycloak.tests.cli.admin; import java.io.IOException; import java.util.Arrays; @@ -7,16 +7,17 @@ import java.util.stream.StreamSupport; import org.keycloak.client.cli.config.FileConfigHandler; -import org.keycloak.testsuite.cli.KcAdmExec; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcAdmExec; +import org.keycloak.tests.cli.TempFileResource; +import org.keycloak.util.JsonSerialization; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.keycloak.testsuite.AbstractAdminTest.loadJson; -import static org.keycloak.testsuite.cli.KcAdmExec.execute; +import static org.keycloak.tests.cli.KcAdmExec.execute; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -26,12 +27,15 @@ /** * @author Marko Strukelj */ +@KeycloakIntegrationTest public class KcAdmSessionTest extends AbstractAdmCliTest { static TypeReference> LIST_OF_JSON = new TypeReference>() {}; @Test - public void test() throws IOException { + void test() throws IOException { + + realm.cleanup().add(r -> removeRealmIfExists("demorealm")); initCustomConfigFile(); @@ -82,14 +86,14 @@ public void test() throws IOException { exe = execute("get-roles --config '" + configFile.getName() + "'"); assertExitCodeAndStdErrSize(exe, 0, 0); - List roles = loadJson(exe.stdout(), LIST_OF_JSON); + List roles = JsonSerialization.readValue(exe.stdout(), LIST_OF_JSON); assertThat("expected three realm roles available", roles.size(), equalTo(3)); // create realm role exe = execute("create roles --config '" + configFile.getName() + "' -s name=testrole -s 'description=Test role' -o"); assertExitCodeAndStdErrSize(exe, 0, 0); - ObjectNode role = loadJson(exe.stdout(), ObjectNode.class); + ObjectNode role = JsonSerialization.readValue(exe.stdout(), ObjectNode.class); Assertions.assertEquals("testrole", role.get("name").asText()); String roleId = role.get("id").asText(); @@ -97,7 +101,7 @@ public void test() throws IOException { exe = execute("get-roles --config '" + configFile.getName() + "'"); assertExitCodeAndStdErrSize(exe, 0, 0); - roles = loadJson(exe.stdout(), LIST_OF_JSON); + roles = JsonSerialization.readValue(exe.stdout(), LIST_OF_JSON); assertThat("expected four realm roles available", roles.size(), equalTo(4)); // create client @@ -117,7 +121,7 @@ public void test() throws IOException { exe = execute("get-roles --config '" + configFile.getName() + "' --cclientid testclient"); assertExitCodeAndStdErrSize(exe, 0, 0); - roles = loadJson(exe.stdout(), LIST_OF_JSON); + roles = JsonSerialization.readValue(exe.stdout(), LIST_OF_JSON); assertThat("expected one role", roles.size(), equalTo(1)); Assertions.assertEquals("clientrole", roles.get(0).get("name").asText()); @@ -131,7 +135,7 @@ public void test() throws IOException { exe = execute("get-roles --config '" + configFile.getName() + "' --uusername testuser --all"); assertExitCodeAndStdErrSize(exe, 0, 0); - ObjectNode node = loadJson(exe.stdout(), ObjectNode.class); + ObjectNode node = JsonSerialization.readValue(exe.stdout(), ObjectNode.class); Assertions.assertNotNull(node.get("realmMappings")); List realmMappings = StreamSupport.stream(node.get("realmMappings").spliterator(), false) @@ -164,7 +168,7 @@ public void test() throws IOException { exe = execute("get-roles --config '" + configFile.getName() + "' --uusername testuser --all"); assertExitCodeAndStdErrSize(exe, 0, 0); - node = loadJson(exe.stdout(), ObjectNode.class); + node = JsonSerialization.readValue(exe.stdout(), ObjectNode.class); Assertions.assertNotNull(node.get("realmMappings")); realmMappings = StreamSupport.stream(node.get("realmMappings").spliterator(), false) @@ -209,7 +213,9 @@ public void test() throws IOException { } @Test - public void testCompositeRoleCreationWithHigherVolumeOfRoles() throws Exception { + void testCompositeRoleCreationWithHigherVolumeOfRoles() throws Exception { + + realm.cleanup().add(r -> removeRealmIfExists("HigherVolumeRolesRealm")); initCustomConfigFile(); try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmTest.java similarity index 91% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmTest.java index a3a707601449..72e067b88f18 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.admin; +package org.keycloak.tests.cli.admin; import java.io.File; import java.io.FileOutputStream; @@ -11,25 +11,34 @@ import org.keycloak.client.cli.config.RealmConfigData; import org.keycloak.common.util.KeystoreUtil; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.testsuite.cli.KcAdmExec; -import org.keycloak.testsuite.util.KeystoreUtils; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.oauth.OAuthClient; +import org.keycloak.testframework.oauth.annotations.InjectOAuthClient; +import org.keycloak.testframework.realm.ClientBuilder; +import org.keycloak.testframework.realm.ClientConfig; +import org.keycloak.tests.cli.KcAdmExec; +import org.keycloak.tests.cli.TempFileResource; import org.keycloak.util.JsonSerialization; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static org.keycloak.client.admin.cli.KcAdmMain.CMD; import static org.keycloak.client.cli.util.OsUtil.EOL; -import static org.keycloak.testsuite.cli.KcAdmExec.execute; +import static org.keycloak.tests.cli.KcAdmExec.execute; /** * @author Marko Strukelj */ +@KeycloakIntegrationTest public class KcAdmTest extends AbstractAdmCliTest { + // uses a dedicated clientId so it does not clash with clients already present in testrealm.json (e.g. test-app) + @InjectOAuthClient(config = CliOAuthClientConfig.class) + OAuthClient oauth; + @Test - public void testBadCommand() { + void testBadCommand() { /* * Test most basic execution with non-existent command */ @@ -41,7 +50,7 @@ public void testBadCommand() { @Test - public void testNoArgs() { + void testNoArgs() { /* * Test (sub)commands without any arguments */ @@ -139,7 +148,7 @@ public void testNoArgs() { } @Test - public void testHelpGlobalOption() { + void testHelpGlobalOption() { /* * Test --help for all commands */ @@ -197,7 +206,7 @@ public void testHelpGlobalOption() { } @Test - public void testBadOptionInPlaceOfCommand() { + void testBadOptionInPlaceOfCommand() { /* * Test most basic execution with non-existent option */ @@ -208,7 +217,7 @@ public void testBadOptionInPlaceOfCommand() { } @Test - public void testBadOption() { + void testBadOption() { /* * Test sub-command execution with non-existent option */ @@ -227,7 +236,7 @@ public void testBadOption() { } @Test - public void testBadOverlappingOption() { + void testBadOverlappingOption() { KcAdmExec exe = KcAdmExec.execute("config credentials --server http://localhost:8080 --realm master --username admin --password admin"); assertExitCodeAndStreamSizes(exe, 2, 0, 3); @@ -235,7 +244,7 @@ public void testBadOverlappingOption() { } @Test - public void testCredentialsServerAndRealmWithDefaultConfig() { + void testCredentialsServerAndRealmWithDefaultConfig() { /* * Test without --server specified */ @@ -245,7 +254,7 @@ public void testCredentialsServerAndRealmWithDefaultConfig() { } @Test - public void testCredentialsNoServerWithDefaultConfig() { + void testCredentialsNoServerWithDefaultConfig() { /* * Test without --server specified */ @@ -257,7 +266,7 @@ public void testCredentialsNoServerWithDefaultConfig() { } @Test - public void testCredentialsNoRealmWithDefaultConfig() { + void testCredentialsNoRealmWithDefaultConfig() { /* * Test without --server specified */ @@ -269,7 +278,7 @@ public void testCredentialsNoRealmWithDefaultConfig() { } @Test - public void testCredentialsWithNoConfig() { + void testCredentialsWithNoConfig() { /* * Test with --no-config specified which is not supported */ @@ -281,7 +290,7 @@ public void testCredentialsWithNoConfig() { } @Test - public void testUserLoginWithDefaultConfig() { + void testUserLoginWithDefaultConfig() { /* * Test most basic user login, using the default admin-cli as a client */ @@ -292,7 +301,7 @@ public void testUserLoginWithDefaultConfig() { } @Test - public void testUserLoginWithAngleBrackets() { + void testUserLoginWithAngleBrackets() { KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm test --user 'special>>character' --password ''"); assertExitCodeAndStreamSizes(exe, 0, 0, 1); @@ -300,7 +309,7 @@ public void testUserLoginWithAngleBrackets() { } @Test - public void testUserLoginWithDefaultConfigInteractive() throws IOException { + void testUserLoginWithDefaultConfigInteractive() throws IOException { /* * Test user login with interaction - provide user password after prompted for it */ @@ -344,7 +353,7 @@ public void testUserLoginWithDefaultConfigInteractive() throws IOException { } @Test - public void testClientLoginWithDefaultConfigInteractive() throws IOException { + void testClientLoginWithDefaultConfigInteractive() throws IOException { /* * Test client login with interaction - login using service account, and provide a client secret after prompted for it */ @@ -389,7 +398,7 @@ public void testClientLoginWithDefaultConfigInteractive() throws IOException { } @Test - public void testUserLoginWithCustomConfig() { + void testUserLoginWithCustomConfig() { /* * Test user login using a custom config file */ @@ -425,7 +434,7 @@ public void testUserLoginWithCustomConfig() { } @Test - public void testCustomConfigLoginCreateDelete() throws IOException { + void testCustomConfigLoginCreateDelete() throws IOException { /* * Test user login, create, delete session using a custom config file */ @@ -471,7 +480,7 @@ public void testCustomConfigLoginCreateDelete() throws IOException { } @Test - public void testCRUDWithOnTheFlyUserAuth() throws IOException { + void testCRUDWithOnTheFlyUserAuth() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, and password. @@ -481,7 +490,7 @@ public void testCRUDWithOnTheFlyUserAuth() throws IOException { } @Test - public void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { + void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client secret. @@ -519,14 +528,14 @@ public void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { } @Test - public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_JKSKeystore() throws IOException { - KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.JKS); + void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_JKSKeystore() throws IOException { + assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.JKS); testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.JKS.getPrimaryExtension()); } @Test - public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_PKCS12Keystore() throws IOException { - KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.PKCS12); + void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_PKCS12Keystore() throws IOException { + assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.PKCS12); testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.PKCS12.getPrimaryExtension()); } @@ -535,7 +544,7 @@ private void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(String keystoreFile * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client JWT signature. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore." + keystoreFileExtension); + File keystore = resourceFile("org/keycloak/tests/cli/kcadm/admin-cli-keystore." + keystoreFileExtension); Assertions.assertTrue(keystore.isFile(), "admin-cli-keystore." + keystoreFileExtension + " must exist, but it does not exists"); // try client without direct grants enabled @@ -577,7 +586,7 @@ private void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(String keystoreFile } @Test - public void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOException { + void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using only client secret - service account is used. @@ -587,12 +596,12 @@ public void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOExcept } @Test - public void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOException { + void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using only client JWT signature - service account is used. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore.p12"); + File keystore = resourceFile("org/keycloak/tests/cli/kcadm/admin-cli-keystore.p12"); Assertions.assertTrue(keystore.isFile(), "admin-cli-keystore.p12 exists"); testCRUDWithOnTheFlyAuth(serverUrl, @@ -601,7 +610,7 @@ public void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOExc } @Test - public void testCRUDWithToken() throws Exception { + void testCRUDWithToken() throws Exception { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client secret. @@ -617,7 +626,8 @@ public void testCRUDWithToken() throws Exception { } @Test - public void testGetUserNameExact() { + void testGetUserNameExact() { + realm.cleanup().add(r -> removeRealmIfExists("demorealm")); KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec.execute("create realms -s realm=demorealm -s enabled=true"); KcAdmExec.execute("create users -r demorealm -s username=testuser"); @@ -632,7 +642,8 @@ public void testGetUserNameExact() { } @Test - public void testGetGroupNameExact() { + void testGetGroupNameExact() { + realm.cleanup().add(r -> removeRealmIfExists("demorealm")); KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec.execute("create realms -s realm=demorealm -s enabled=true"); KcAdmExec.execute("create role -r demorealm -s name=testrole"); @@ -650,7 +661,7 @@ public void testGetGroupNameExact() { } @Test - public void testCsvFormat() { + void testCsvFormat() { execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec exec = execute("get realms/master --format csv"); assertExitCodeAndStreamSizes(exec, 0, 1, 0); @@ -658,7 +669,7 @@ public void testCsvFormat() { } @Test - public void testCsvFormatWithMissingFields() { + void testCsvFormatWithMissingFields() { execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec exec = execute("get realms/master --format csv --fields foo"); // nothing valid was selected, should be blank @@ -667,7 +678,7 @@ public void testCsvFormatWithMissingFields() { } @Test - public void testCompressedCsv() { + void testCompressedCsv() { execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec exec = execute("get realms/master --format csv --compressed"); // should contain an error message @@ -675,7 +686,7 @@ public void testCompressedCsv() { } @Test - public void testEnvPasswordWithRegularCommand() { + void testEnvPasswordWithRegularCommand() { execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec exec = KcAdmExec.newBuilder() .argsLine("get users --format csv") @@ -686,7 +697,7 @@ public void testEnvPasswordWithRegularCommand() { } @Test - public void testStatusOptionForConfigCred() { + void testStatusOptionForConfigCred() { execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); KcAdmExec exec = execute("config credentials --status"); assertExitCodeAndStreamSizes(exec, 0, 1, 0); @@ -698,4 +709,12 @@ public void testStatusOptionForConfigCred() { Assertions.assertTrue(exec.stdoutString().endsWith("from now)\n")); } + static class CliOAuthClientConfig implements ClientConfig { + + @Override + public ClientBuilder configure(ClientBuilder client) { + return client.clientId("kcadm-cli-oauth-client"); + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTruststoreTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmTruststoreTest.java similarity index 84% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTruststoreTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmTruststoreTest.java index c48a7f04f49c..eb4b7769050e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTruststoreTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmTruststoreTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.admin; +package org.keycloak.tests.cli.admin; import java.io.File; import java.io.IOException; @@ -6,27 +6,27 @@ import org.keycloak.client.admin.cli.KcAdmMain; import org.keycloak.client.cli.config.ConfigData; import org.keycloak.client.cli.config.FileConfigHandler; -import org.keycloak.testsuite.cli.KcAdmExec; -import org.keycloak.testsuite.util.TempFileResource; -import org.keycloak.testsuite.util.oauth.OAuthClient; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcAdmExec; +import org.keycloak.tests.cli.TempFileResource; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static org.keycloak.client.admin.cli.KcAdmMain.DEFAULT_CONFIG_FILE_PATH; import static org.keycloak.client.cli.util.OsUtil.EOL; -import static org.keycloak.testsuite.cli.KcAdmExec.execute; -import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED; +import static org.keycloak.tests.cli.KcAdmExec.execute; /** * @author Marko Strukelj */ -public class KcAdmTruststoreTest extends AbstractAdmCliTest { +@KeycloakIntegrationTest +class KcAdmTruststoreTest extends AbstractAdmCliTest { @Test - public void testTruststore() throws IOException { + void testTruststore() throws IOException { - File truststore = new File("src/test/resources/keystore/keycloak.truststore"); + File truststore = resourceFile("org/keycloak/tests/cli/keycloak.truststore"); KcAdmExec exe = execute("config truststore --no-config '" + truststore.getAbsolutePath() + "'"); @@ -34,12 +34,6 @@ public void testTruststore() throws IOException { Assertions.assertEquals("Unsupported option: --no-config", exe.stderrLines().get(0), "stderr first line"); Assertions.assertEquals("Try '" + KcAdmMain.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1), "try help"); - // only run this test if ssl protected keycloak server is available - if (!AUTH_SERVER_SSL_REQUIRED) { - System.out.println("TEST SKIPPED - This test requires HTTPS. Run with '-Pauth-server-quarkus -Dauth.server.ssl.required=true'"); - return; - } - initCustomConfigFile(); try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) { @@ -53,7 +47,7 @@ public void testTruststore() throws IOException { // perform authentication against server - asks for password, then for truststore password exe = KcAdmExec.newBuilder() - .argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" + + .argsLine("config credentials --server " + keycloakUrls.getBase() + " --realm test --user user1" + " --config '" + configFile.getName() + "'") .executeAsync(); @@ -73,7 +67,7 @@ public void testTruststore() throws IOException { // perform authentication against server - asks for password, then for truststore password exe = KcAdmExec.newBuilder() - .argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" + + .argsLine("config credentials --server " + keycloakUrls.getBase() + " --realm test --user user1" + " --config '" + configFile.getName() + "'") .executeAsync(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmUpdateTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmUpdateTest.java similarity index 84% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmUpdateTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmUpdateTest.java index 02f8ff0938af..abc6a615fcff 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmUpdateTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/admin/KcAdmUpdateTest.java @@ -1,7 +1,6 @@ -package org.keycloak.testsuite.cli.admin; +package org.keycloak.tests.cli.admin; import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -12,17 +11,17 @@ import org.keycloak.client.cli.config.FileConfigHandler; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.realm.IdentityProviderBuilder; -import org.keycloak.testsuite.cli.KcAdmExec; -import org.keycloak.testsuite.updaters.IdentityProviderCreator; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.tests.cli.KcAdmExec; +import org.keycloak.tests.cli.TempFileResource; import org.keycloak.util.JsonSerialization; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.keycloak.testsuite.cli.KcAdmExec.CMD; -import static org.keycloak.testsuite.cli.KcAdmExec.execute; +import static org.keycloak.tests.cli.KcAdmExec.CMD; +import static org.keycloak.tests.cli.KcAdmExec.execute; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -31,10 +30,11 @@ /** * @author Marko Strukelj */ -public class KcAdmUpdateTest extends AbstractAdmCliTest { +@KeycloakIntegrationTest +class KcAdmUpdateTest extends AbstractAdmCliTest { @Test - public void testUpdateIDPWithoutInternalId() throws IOException { + void testUpdateIDPWithoutInternalId() throws IOException { final String realm = "test"; final RealmResource realmResource = adminClient.realm(realm); @@ -53,25 +53,26 @@ public void testUpdateIDPWithoutInternalId() throws IOException { .attribute(SAMLIdentityProviderConfig.ARTIFACT_BINDING_RESPONSE, "false") .build(); - try (Closeable ipc = new IdentityProviderCreator(realmResource, identityProvider)) { - initCustomConfigFile(); - try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) { - loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass"); + realmResource.identityProviders().create(identityProvider).close(); + this.realm.cleanup().add(r -> r.identityProviders().get("idpAlias").remove()); - KcAdmExec exe = execute("get identity-provider/instances/idpAlias -r " + realm + " --config " + configFile.getFile()); - assertExitCodeAndStdErrSize(exe, 0, 0); + initCustomConfigFile(); + try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) { + loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass"); - final File idpJson = new File("target/test-classes/cli/idp-keycloak-9167.json"); - exe = execute("update identity-provider/instances/idpAlias -r " + realm + " -f " + idpJson.getAbsolutePath() + " --config " + configFile.getFile()); - assertExitCodeAndStdErrSize(exe, 0, 0); - } + KcAdmExec exe = execute("get identity-provider/instances/idpAlias -r " + realm + " --config " + configFile.getFile()); + assertExitCodeAndStdErrSize(exe, 0, 0); - assertThat(realmResource.identityProviders().get("idpAlias").toRepresentation().getDisplayName(), is(equalTo("SAML_UPDATED"))); + final File idpJson = resourceFile("org/keycloak/tests/cli/idp-keycloak-9167.json"); + exe = execute("update identity-provider/instances/idpAlias -r " + realm + " -f " + idpJson.getAbsolutePath() + " --config " + configFile.getFile()); + assertExitCodeAndStdErrSize(exe, 0, 0); } + + assertThat(realmResource.identityProviders().get("idpAlias").toRepresentation().getDisplayName(), is(equalTo("SAML_UPDATED"))); } @Test - public void testUpdateThoroughly() throws IOException { + void testUpdateThoroughly() throws IOException { initCustomConfigFile(); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java b/tests/base/src/test/java/org/keycloak/tests/cli/exec/AbstractExec.java similarity index 88% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java rename to tests/base/src/test/java/org/keycloak/tests/cli/exec/AbstractExec.java index 5ee682cc7159..a68ddf7fca46 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/exec/AbstractExec.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.exec; +package org.keycloak.tests.cli.exec; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -13,18 +13,26 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import org.keycloak.client.cli.util.OsArch; import org.keycloak.client.cli.util.OsUtil; -import org.keycloak.testsuite.cli.OsArch; -import org.keycloak.testsuite.cli.OsUtils; +import org.keycloak.common.crypto.FipsMode; +import org.keycloak.testframework.config.Config; +import org.keycloak.testframework.crypto.CryptoHelper; /** * @author Marko Strukelj */ public abstract class AbstractExec { + /** + * System property that overrides the location of the client tools. The FIPS test suites point this at a copy of + * the tools that includes the BouncyCastle FIPS provider (see AbstractCliTest#useFipsClientTools). + */ + public static final String CLI_TOOLS_DIR_PROPERTY = "kc.test.cli.tools.dir"; + public static final String WORK_DIR = System.getProperty("user.dir"); - public static final OsArch OS_ARCH = OsUtils.determineOSAndArch(); + public static final OsArch OS_ARCH = OsUtil.determineOSAndArch(); private long waitTimeout = 30000; @@ -254,7 +262,13 @@ public void sendLine(String s) { sendToStdin(s + OsUtil.EOL); } - + /** + * Whether the test framework is configured to run in FIPS mode. When enabled, the client tools log additional + * BouncyCastle FIPS provider information, which needs to be accounted for when asserting stderr output. + */ + public static boolean isFips() { + return Config.getValueTypeConfig(CryptoHelper.class, "fips", FipsMode.DISABLED.name(), FipsMode.class) != FipsMode.DISABLED; + } static void copyStream(InputStream is, OutputStream os) throws IOException { byte [] buf = new byte[8192]; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExecBuilder.java b/tests/base/src/test/java/org/keycloak/tests/cli/exec/AbstractExecBuilder.java similarity index 95% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExecBuilder.java rename to tests/base/src/test/java/org/keycloak/tests/cli/exec/AbstractExecBuilder.java index 97ec7724b4e3..8d8f473d51b9 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExecBuilder.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/exec/AbstractExecBuilder.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.exec; +package org.keycloak.tests.cli.exec; import java.io.InputStream; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/InteractiveInputStream.java b/tests/base/src/test/java/org/keycloak/tests/cli/exec/InteractiveInputStream.java similarity index 98% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/InteractiveInputStream.java rename to tests/base/src/test/java/org/keycloak/tests/cli/exec/InteractiveInputStream.java index cbbea7242be5..1e9bf41e5225 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/InteractiveInputStream.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/exec/InteractiveInputStream.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.exec; +package org.keycloak.tests.cli.exec; import java.io.IOException; import java.io.InputStream; @@ -117,4 +117,4 @@ public synchronized void pushBytes(byte [] buff) { } notify(); } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/LoggingOutputStream.java b/tests/base/src/test/java/org/keycloak/tests/cli/exec/LoggingOutputStream.java similarity index 96% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/LoggingOutputStream.java rename to tests/base/src/test/java/org/keycloak/tests/cli/exec/LoggingOutputStream.java index e13fbe34870a..74485dbcaf92 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/LoggingOutputStream.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/exec/LoggingOutputStream.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.exec; +package org.keycloak.tests.cli.exec; import java.io.ByteArrayOutputStream; import java.io.FilterOutputStream; @@ -50,4 +50,4 @@ private void log() { buffer.reset(); System.out.println("[" + name + "] " + log); } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/StreamReaderThread.java b/tests/base/src/test/java/org/keycloak/tests/cli/exec/StreamReaderThread.java similarity index 88% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/StreamReaderThread.java rename to tests/base/src/test/java/org/keycloak/tests/cli/exec/StreamReaderThread.java index b4bd80b8f19f..6f4ba81dcdf3 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/StreamReaderThread.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/exec/StreamReaderThread.java @@ -1,11 +1,11 @@ -package org.keycloak.testsuite.cli.exec; +package org.keycloak.tests.cli.exec; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; -import static org.keycloak.testsuite.cli.exec.AbstractExec.copyStream; +import static org.keycloak.tests.cli.exec.AbstractExec.copyStream; class StreamReaderThread extends Thread { @@ -33,4 +33,4 @@ public void run() { } } } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractRegCliTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/AbstractRegCliTest.java similarity index 71% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractRegCliTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/AbstractRegCliTest.java index ecdd432d9b98..3485b131252e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractRegCliTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/AbstractRegCliTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.File; import java.io.FileOutputStream; @@ -6,7 +6,6 @@ import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -24,31 +23,36 @@ import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager; import org.keycloak.services.clientregistration.policy.RegistrationAuth; import org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrationPolicyFactory; +import org.keycloak.testframework.annotations.InjectRealm; import org.keycloak.testframework.realm.ClientBuilder; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmBuilder; +import org.keycloak.testframework.realm.RealmConfig; import org.keycloak.testframework.realm.UserBuilder; -import org.keycloak.testsuite.cli.AbstractCliTest; -import org.keycloak.testsuite.cli.KcRegExec; +import org.keycloak.tests.cli.AbstractCliTest; +import org.keycloak.tests.cli.KcRegExec; import org.keycloak.util.JsonSerialization; -import org.junit.Before; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; -import static org.keycloak.testsuite.AbstractAdminTest.loadJson; -import static org.keycloak.testsuite.cli.KcRegExec.WORK_DIR; -import static org.keycloak.testsuite.cli.KcRegExec.execute; +import static org.keycloak.tests.cli.KcRegExec.WORK_DIR; +import static org.keycloak.tests.cli.KcRegExec.execute; /** * @author Marko Strukelj */ public abstract class AbstractRegCliTest extends AbstractCliTest { - @Before + @InjectRealm(fromJson = "/org/keycloak/tests/cli/testrealm.json", config = RegCliRealmConfig.class) + ManagedRealm realm; + + @BeforeEach public void deleteDefaultConfig() { getDefaultConfigFilePath().delete(); } @@ -61,87 +65,14 @@ static File getDefaultConfigFilePath() { return new File(System.getProperty("user.home") + "/.keycloak/kcreg.config"); } - @Override - public void addTestRealms(List testRealms) { - RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); - testRealms.add(realmRepresentation); - - // create admin user account with permissions to manage clients - UserRepresentation admin = UserBuilder.create() - .username("user1") - .password("userpass") - .enabled(true) - .build(); - HashMap> clientRoles = new HashMap<>(); - clientRoles.put("realm-management", Arrays.asList("manage-clients")); - admin.setClientRoles(clientRoles); - realmRepresentation.getUsers().add(admin); - - - - // create client with service account to use Signed JWT credentials with - ClientRepresentation regClient = ClientBuilder.create() - .clientId("reg-cli-jwt") - .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g==") - .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) - .serviceAccountsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - // create service account for client reg-cli with permissions to manage clients - addServiceAccount(realmRepresentation, "reg-cli-jwt"); - - - - // create client to use with user account - enable direct grants - regClient = ClientBuilder.create() - .clientId("reg-cli-jwt-direct") - .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g==") - .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) - .directAccessGrantsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - - - - // create client with service account to use client secret with - regClient = ClientBuilder.create() - .clientId("reg-cli-secret") - .secret("password") - .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) - .serviceAccountsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - - // create service account for client reg-cli with permissions to manage clients - addServiceAccount(realmRepresentation, "reg-cli-secret"); - - - - - // create client to use with user account - enable direct grants - regClient = ClientBuilder.create() - .clientId("reg-cli-secret-direct") - .secret("password") - .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) - .directAccessGrantsEnabled() - .build(); - - realmRepresentation.getClients().add(regClient); - } - - void loginAsUser(File configFile, String server, String realm, String user, String password) { + static void loginAsUser(File configFile, String server, String realm, String user, String password) { KcRegExec exe = execute("config credentials --server " + server + " --realm " + realm + " --user " + user + " --password " + password + " --config " + configFile.getAbsolutePath()); assertExitCodeAndStreamSizes(exe, 0, 0, 1); } - void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, String ... excluded) { + static void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, String ... excluded) { HashSet exclusions = new HashSet<>(Arrays.asList(excluded)); @@ -196,11 +127,11 @@ void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, Str } } - void assertFieldsEqualWithExclusions(RealmConfigData data1, RealmConfigData data2, String ... excluded) { + static void assertFieldsEqualWithExclusions(RealmConfigData data1, RealmConfigData data2, String ... excluded) { assertFieldsEqualWithExclusions(null, null, data1, data2, excluded); } - void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigData data1, RealmConfigData data2, String ... excluded) { + static void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigData data1, RealmConfigData data2, String ... excluded) { HashSet exclusions = new HashSet<>(Arrays.asList(excluded)); @@ -287,24 +218,7 @@ void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigDat } } - void addServiceAccount(RealmRepresentation realm, String clientId) { - - UserRepresentation account = UserBuilder.create() - .username("service-account-" + clientId) - .enabled(true) - .serviceAccountId(clientId) - .build(); - - HashMap> clientRoles = new HashMap<>(); - clientRoles.put("realm-management", Arrays.asList("manage-clients")); - - account.setClientRoles(clientRoles); - - realm.getUsers().add(account); - } - - - void waitFor(long millis) { + static void waitFor(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { @@ -313,7 +227,7 @@ void waitFor(long millis) { } - FileConfigHandler initCustomConfigFile() { + static FileConfigHandler initCustomConfigFile() { String filename = UUID.randomUUID().toString() + ".config"; File cfgFile = new File(WORK_DIR + "/" + filename); FileConfigHandler handler = new FileConfigHandler(); @@ -321,11 +235,11 @@ FileConfigHandler initCustomConfigFile() { return handler; } - File initTempFile(String extension) throws IOException { + static File initTempFile(String extension) throws IOException { return initTempFile(extension, null); } - File initTempFile(String extension, String content) throws IOException { + static File initTempFile(String extension, String content) throws IOException { String filename = UUID.randomUUID().toString() + extension; File file = new File(WORK_DIR + "/" + filename); if (content != null) { @@ -372,7 +286,7 @@ void addLocalhostToAllowedHosts(String realm) { realmResource.components().component(trustedHostRep.getId()).update(trustedHostRep); } - void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extraOptions, String loginMessage) throws IOException { + static void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extraOptions, String loginMessage) throws IOException { File configFile = getDefaultConfigFilePath(); long lastModified = configFile.exists() ? configFile.lastModified() : 0; @@ -469,7 +383,6 @@ void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extra - exe = execute("delete test-client --no-config --server " + serverUrl + " --realm test " + credentials + " " + extraOptions); assertExitCodeAndStreamSizes(exe, 0, 0, 1); @@ -490,4 +403,74 @@ void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extra lastModified2 = configFile.exists() ? configFile.lastModified() : 0; Assertions.assertEquals(lastModified, lastModified2, "config file not modified"); } + + // Disable the VERIFY_PROFILE required action so user1, which has no e-mail, can complete direct-grant login + // (e.g. KcRegTest#testCRUDWithOnTheFlyUserAuth); the default user profile would otherwise require it be completed first. + static RequiredActionProviderRepresentation disabledVerifyProfile() { + RequiredActionProviderRepresentation verifyProfile = new RequiredActionProviderRepresentation(); + verifyProfile.setAlias("VERIFY_PROFILE"); + verifyProfile.setName("Verify Profile"); + verifyProfile.setProviderId("VERIFY_PROFILE"); + verifyProfile.setEnabled(false); + verifyProfile.setDefaultAction(false); + verifyProfile.setPriority(100); + return verifyProfile; + } + + static class RegCliRealmConfig implements RealmConfig { + + // certificate used by the signed JWT client credentials (reg-cli) + private static final String CERTIFICATE = "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g=="; + + @Override + public RealmBuilder configure(RealmBuilder realm) { + return realm + .requiredActions(disabledVerifyProfile()) + // create admin user account with permissions to manage clients + .users( + UserBuilder.create() + .username("user1") + .password("userpass") + .enabled(true) + .clientRoles("realm-management", "manage-clients"), + // create service account for client reg-cli-jwt with permissions to manage clients + serviceAccount("reg-cli-jwt"), + // create service account for client reg-cli-secret with permissions to manage clients + serviceAccount("reg-cli-secret")) + .clients( + // create client with service account to use Signed JWT credentials with + ClientBuilder.create() + .clientId("reg-cli-jwt") + .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, CERTIFICATE) + .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) + .serviceAccountsEnabled(), + // create client to use with user account - enable direct grants + ClientBuilder.create() + .clientId("reg-cli-jwt-direct") + .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, CERTIFICATE) + .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) + .directAccessGrantsEnabled(), + // create client with service account to use client secret with + ClientBuilder.create() + .clientId("reg-cli-secret") + .secret("password") + .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) + .serviceAccountsEnabled(), + // create client to use with user account - enable direct grants + ClientBuilder.create() + .clientId("reg-cli-secret-direct") + .secret("password") + .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) + .directAccessGrantsEnabled()); + } + + private static UserBuilder serviceAccount(String clientId) { + return UserBuilder.create() + .username("service-account-" + clientId) + .enabled(true) + .serviceAccountId(clientId) + .clientRoles("realm-management", "manage-clients"); + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegConfigTest.java similarity index 90% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegConfigTest.java index ee091e3be300..66e000790e60 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegConfigTest.java @@ -1,25 +1,27 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.IOException; import org.keycloak.client.cli.config.FileConfigHandler; -import org.keycloak.testsuite.cli.KcRegExec; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcRegExec; +import org.keycloak.tests.cli.TempFileResource; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static org.keycloak.client.cli.util.OsUtil.EOL; import static org.keycloak.client.registration.cli.KcRegMain.CMD; -import static org.keycloak.testsuite.cli.KcRegExec.execute; +import static org.keycloak.tests.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegConfigTest extends AbstractRegCliTest { +@KeycloakIntegrationTest +class KcRegConfigTest extends AbstractRegCliTest { @Test - public void testRegistrationToken() throws IOException { + void testRegistrationToken() throws IOException { initCustomConfigFile(); @@ -73,7 +75,7 @@ public void testRegistrationToken() throws IOException { } @Test - public void testNoConfigOption() throws IOException { + void testNoConfigOption() throws IOException { KcRegExec exe = execute("config registration-token --no-config --server http://localhost:8080/auth --realm test --client my_client --delete"); assertExitCodeAndStreamSizes(exe, 2, 0, 2); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegCreateTest.java similarity index 91% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegCreateTest.java index a827c9546003..a79f041f9f3e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegCreateTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.File; import java.io.IOException; @@ -11,44 +11,35 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.client.cli.config.ConfigData; import org.keycloak.client.cli.config.FileConfigHandler; -import org.keycloak.common.Profile; import org.keycloak.common.constants.ServiceAccountConstants; -import org.keycloak.common.crypto.FipsMode; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; -import org.keycloak.testsuite.ProfileAssume; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; -import org.keycloak.testsuite.cli.KcRegExec; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcRegExec; +import org.keycloak.tests.cli.TempFileResource; +import org.keycloak.tests.cli.exec.AbstractExec; import org.keycloak.util.JsonSerialization; import org.hamcrest.Matchers; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.keycloak.testsuite.cli.KcRegExec.execute; -import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED; +import static org.keycloak.tests.cli.KcRegExec.execute; import static org.hamcrest.MatcherAssert.assertThat; /** * @author Marko Strukelj */ +@KeycloakIntegrationTest public class KcRegCreateTest extends AbstractRegCliTest { - @Before - public void assumeTLSEnabled() { - Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED); - } - @Test - public void testCreateWithRealmOverride() throws IOException { + void testCreateWithRealmOverride() throws IOException { FileConfigHandler handler = initCustomConfigFile(); @@ -56,13 +47,13 @@ public void testCreateWithRealmOverride() throws IOException { // authenticate as a regular user against one realm KcRegExec exe = execute("config credentials -x --config '" + configFile.getName() + - "' --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm master --user admin --password admin"); + "' --insecure --server " + keycloakUrls.getBase() + " --realm master --user admin --password admin"); assertExitCodeAndStreamSizes(exe, 0, 0, 3); // use initial token of another realm with server, and realm override String token = issueInitialAccessToken("test"); - exe = execute("create --config '" + configFile.getName() + "' --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm test -s clientId=my_first_client -t " + token); + exe = execute("create --config '" + configFile.getName() + "' --insecure --server " + keycloakUrls.getBase() + " --realm test -s clientId=my_first_client -t " + token); assertExitCodeAndStreamSizes(exe, 0, 0, 3); } @@ -70,7 +61,7 @@ public void testCreateWithRealmOverride() throws IOException { @Test - public void testCreateThoroughly() throws IOException { + void testCreateThoroughly() throws IOException { FileConfigHandler handler = initCustomConfigFile(); @@ -81,15 +72,15 @@ public void testCreateThoroughly() throws IOException { final String realm = "test"; KcRegExec exe = execute("config initial-token -x --config '" + configFile.getName() + - "' --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm " + realm + " " + token); + "' --insecure --server " + keycloakUrls.getBase() + " --realm " + realm + " " + token); assertExitCodeAndStreamSizes(exe, 0, 0, 0); // check that current server, realm, and initial token are saved in the file ConfigData config = handler.loadConfig(); - Assertions.assertEquals(oauth.AUTH_SERVER_ROOT, config.getServerUrl(), "Config serverUrl"); + Assertions.assertEquals(keycloakUrls.getBase(), config.getServerUrl(), "Config serverUrl"); Assertions.assertEquals(realm, config.getRealm(), "Config realm"); - Assertions.assertEquals(token, config.ensureRealmConfigData(oauth.AUTH_SERVER_ROOT, realm).getInitialToken(), "Config initial access token"); + Assertions.assertEquals(token, config.ensureRealmConfigData(keycloakUrls.getBase(), realm).getInitialToken(), "Config initial access token"); // create configuration from file using stdin redirect ... output an object String content = "{\n" + @@ -216,10 +207,10 @@ public void testCreateThoroughly() throws IOException { } // TODO: SAML is not tested with FIPS enabled as it does not work. This needs to be revisited when SAML works with FIPS - if (AuthServerTestEnricher.AUTH_SERVER_FIPS_MODE == FipsMode.DISABLED) { + if (!AbstractExec.isFips()) { // test create saml formated xml - format autodetection - File samlSpMetaFile = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/saml-sp-metadata.xml"); + File samlSpMetaFile = resourceFile("org/keycloak/tests/cli/kcreg/saml-sp-metadata.xml"); Assertions.assertTrue(samlSpMetaFile.isFile(), "saml-sp-metadata.xml exists"); exe = execute("create --insecure --config '" + configFile.getName() + "' -o -f - < '" + samlSpMetaFile.getAbsolutePath() + "'"); @@ -245,19 +236,17 @@ public void testCreateThoroughly() throws IOException { } @Test - public void testCreateWithAuthorizationServices() throws IOException { - ProfileAssume.assumeFeatureEnabled(Profile.Feature.AUTHORIZATION); - + void testCreateWithAuthorizationServices() throws IOException { FileConfigHandler handler = initCustomConfigFile(); try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { KcRegExec exe = execute("config credentials -x --config '" + configFile.getName() + - "' --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm master --user admin --password admin"); + "' --insecure --server " + keycloakUrls.getBase() + " --realm master --user admin --password admin"); assertExitCodeAndStreamSizes(exe, 0, 0, 3); String token = issueInitialAccessToken("test"); - exe = execute("create --config '" + configFile.getName() + "' --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm test -s clientId=authz-client -s authorizationServicesEnabled=true -t " + token); + exe = execute("create --config '" + configFile.getName() + "' --insecure --server " + keycloakUrls.getBase() + " --realm test -s clientId=authz-client -s authorizationServicesEnabled=true -t " + token); assertExitCodeAndStreamSizes(exe, 0, 0, 3); RealmResource realm = adminClient.realm("test"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegTest.java similarity index 91% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegTest.java index 29c5b076fb17..c87f1a29832f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.File; import java.io.FileOutputStream; @@ -11,27 +11,26 @@ import org.keycloak.client.cli.config.RealmConfigData; import org.keycloak.common.util.KeystoreUtil; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.testsuite.cli.KcRegExec; -import org.keycloak.testsuite.util.KeystoreUtils; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcRegExec; +import org.keycloak.tests.cli.TempFileResource; import org.keycloak.util.JsonSerialization; -import org.junit.Assume; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static org.keycloak.client.cli.util.OsUtil.EOL; import static org.keycloak.client.registration.cli.KcRegMain.CMD; -import static org.keycloak.testsuite.cli.KcRegExec.execute; -import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED; +import static org.keycloak.tests.cli.KcRegExec.execute; /** * @author Marko Strukelj */ +@KeycloakIntegrationTest public class KcRegTest extends AbstractRegCliTest { @Test - public void testNoArgs() { + void testNoArgs() { /* * Test (sub)commands without any arguments */ @@ -120,7 +119,7 @@ public void testNoArgs() { } @Test - public void testHelpGlobalOption() { + void testHelpGlobalOption() { /* * Test --help for all commands */ @@ -183,7 +182,7 @@ public void testHelpGlobalOption() { } @Test - public void testBadCommand() { + void testBadCommand() { /* * Test most basic execution with non-existent command */ @@ -194,7 +193,7 @@ public void testBadCommand() { } @Test - public void testBadOptionInPlaceOfCommand() { + void testBadOptionInPlaceOfCommand() { /* * Test most basic execution with non-existent option */ @@ -205,7 +204,7 @@ public void testBadOptionInPlaceOfCommand() { } @Test - public void testBadOption() { + void testBadOption() { /* * Test sub-command execution with non-existent option */ @@ -218,7 +217,7 @@ public void testBadOption() { } @Test - public void testCredentialsServerAndRealmWithDefaultConfig() { + void testCredentialsServerAndRealmWithDefaultConfig() { /* * Test without --server specified */ @@ -228,7 +227,7 @@ public void testCredentialsServerAndRealmWithDefaultConfig() { } @Test - public void testCredentialsNoServerWithDefaultConfig() { + void testCredentialsNoServerWithDefaultConfig() { /* * Test without --server specified */ @@ -240,7 +239,7 @@ public void testCredentialsNoServerWithDefaultConfig() { } @Test - public void testCredentialsNoRealmWithDefaultConfig() { + void testCredentialsNoRealmWithDefaultConfig() { /* * Test without --server specified */ @@ -252,7 +251,7 @@ public void testCredentialsNoRealmWithDefaultConfig() { } @Test - public void testCredentialsWithNoConfig() { + void testCredentialsWithNoConfig() { /* * Test with --no-config specified which is not supported */ @@ -264,7 +263,7 @@ public void testCredentialsWithNoConfig() { } @Test - public void testUserLoginWithDefaultConfig() { + void testUserLoginWithDefaultConfig() { /* * Test most basic user login, using the default admin-cli as a client */ @@ -275,7 +274,7 @@ public void testUserLoginWithDefaultConfig() { } @Test - public void testUserLoginWithDefaultConfigInteractive() throws IOException { + void testUserLoginWithDefaultConfigInteractive() throws IOException { /* * Test user login with interaction - provide user password after prompted for it */ @@ -319,7 +318,7 @@ public void testUserLoginWithDefaultConfigInteractive() throws IOException { } @Test - public void testClientLoginWithDefaultConfigInteractive() throws IOException { + void testClientLoginWithDefaultConfigInteractive() throws IOException { /* * Test client login with interaction - login using service account, and provide a client secret after prompted for it */ @@ -364,7 +363,7 @@ public void testClientLoginWithDefaultConfigInteractive() throws IOException { } @Test - public void testUserLoginWithCustomConfig() { + void testUserLoginWithCustomConfig() { /* * Test user login using a custom config file */ @@ -401,7 +400,7 @@ public void testUserLoginWithCustomConfig() { } @Test - public void testCustomConfigLoginCreateDelete() throws IOException { + void testCustomConfigLoginCreateDelete() throws IOException { /* * Test user login, create, delete session using a custom config file */ @@ -456,7 +455,7 @@ public void testCustomConfigLoginCreateDelete() throws IOException { } @Test - public void testCRUDWithOnTheFlyUserAuth() throws IOException { + void testCRUDWithOnTheFlyUserAuth() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, and password. @@ -466,7 +465,7 @@ public void testCRUDWithOnTheFlyUserAuth() throws IOException { } @Test - public void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { + void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client secret. @@ -504,14 +503,14 @@ public void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { } @Test - public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_JKSKeystore() throws IOException { - KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.JKS); + void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_JKSKeystore() throws IOException { + assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.JKS); testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.JKS.getPrimaryExtension()); } @Test - public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_PKCS12Keystore() throws IOException { - KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.PKCS12); + void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_PKCS12Keystore() throws IOException { + assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.PKCS12); testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.PKCS12.getPrimaryExtension()); } @@ -520,7 +519,7 @@ private void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(String keystoreFile * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client JWT signature. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/reg-cli-keystore." + keystoreFileExtension); + File keystore = resourceFile("org/keycloak/tests/cli/kcreg/reg-cli-keystore." + keystoreFileExtension); Assertions.assertTrue(keystore.isFile(), "reg-cli-keystore." + keystoreFileExtension + " exists"); // try client without direct grants enabled @@ -562,7 +561,7 @@ private void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(String keystoreFile } @Test - public void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOException { + void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using only client secret - service account is used. @@ -572,12 +571,12 @@ public void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOExcept } @Test - public void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOException { + void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using only client JWT signature - service account is used. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/reg-cli-keystore.p12"); + File keystore = resourceFile("org/keycloak/tests/cli/kcreg/reg-cli-keystore.p12"); Assertions.assertTrue(keystore.isFile(), "reg-cli-keystore.p12 exists"); testCRUDWithOnTheFlyAuth(serverUrl, @@ -586,7 +585,7 @@ public void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOExc } @Test - public void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOption() throws IOException { + void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOption() throws IOException { /* * Test create using initial client token, and subsequent delete using registration access token. * A config file is used to save registration access token for newly created client. @@ -595,7 +594,7 @@ public void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOption() } @Test - public void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOptionNoConfig() throws IOException { + void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOptionNoConfig() throws IOException { /* * Test create using initial client token, and subsequent delete using registration access token. * No config file is used so registration access token for newly created client is not saved to config. @@ -604,8 +603,6 @@ public void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOptionNo } private void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOption(boolean useConfig) throws IOException { - Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED); - // prepare for loading a config file // only used when useConfig is true FileConfigHandler handler = initCustomConfigFile(); @@ -617,7 +614,7 @@ private void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOption( final String realm = "master"; KcRegExec exe = execute("create " + (useConfig ? ("--config '" + configFile.getAbsolutePath()) + "'" : "--no-config") - + " --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm " + realm + " -s clientId=test-client2 -o -t " + token); + + " --insecure --server " + keycloakUrls.getBase() + " --realm " + realm + " -s clientId=test-client2 -o -t " + token); Assertions.assertEquals(0, exe.exitCode(), "exitCode == 0"); @@ -631,27 +628,27 @@ private void testCreateDeleteWithInitialAndRegistrationTokensWithUnsecureOption( if (useConfig) { ConfigData config = handler.loadConfig(); Assertions.assertEquals(client.getRegistrationAccessToken(), - config.ensureRealmConfigData(oauth.AUTH_SERVER_ROOT, realm).getClients().get("test-client2"), + config.ensureRealmConfigData(keycloakUrls.getBase(), realm).getClients().get("test-client2"), "Registration Access Token in config file"); } else { Assertions.assertFalse(configFile.isFile(), "There should be no config file"); } exe = execute("delete test-client2 " + (useConfig ? ("--config '" + configFile.getAbsolutePath()) + "'" : "--no-config") - + " --insecure --server " + oauth.AUTH_SERVER_ROOT + " --realm " + realm + " -t " + client.getRegistrationAccessToken()); + + " --insecure --server " + keycloakUrls.getBase() + " --realm " + realm + " -t " + client.getRegistrationAccessToken()); assertExitCodeAndStreamSizes(exe, 0, 0, 2); } } @Test - public void testCreateWithAllowedHostsWithoutAuthenticationNoConfig() throws IOException { + void testCreateWithAllowedHostsWithoutAuthenticationNoConfig() throws IOException { testCreateWithAllowedHostsWithoutAuthentication("test", false); } @Test - public void testCreateWithAllowedHostsWithoutAuthentication() throws IOException { + void testCreateWithAllowedHostsWithoutAuthentication() throws IOException { testCreateWithAllowedHostsWithoutAuthentication("test", true); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegTruststoreTest.java similarity index 83% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegTruststoreTest.java index 80b426b93ffa..22c050e4665b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegTruststoreTest.java @@ -1,32 +1,32 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.File; import java.io.IOException; import org.keycloak.client.cli.config.ConfigData; import org.keycloak.client.cli.config.FileConfigHandler; -import org.keycloak.testsuite.cli.KcRegExec; -import org.keycloak.testsuite.util.TempFileResource; -import org.keycloak.testsuite.util.oauth.OAuthClient; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcRegExec; +import org.keycloak.tests.cli.TempFileResource; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static org.keycloak.client.cli.util.OsUtil.EOL; import static org.keycloak.client.registration.cli.KcRegMain.CMD; import static org.keycloak.client.registration.cli.KcRegMain.DEFAULT_CONFIG_FILE_PATH; -import static org.keycloak.testsuite.cli.KcRegExec.execute; -import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED; +import static org.keycloak.tests.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegTruststoreTest extends AbstractRegCliTest { +@KeycloakIntegrationTest +class KcRegTruststoreTest extends AbstractRegCliTest { @Test - public void testTruststore() throws IOException { + void testTruststore() throws IOException { - File truststore = new File("src/test/resources/keystore/keycloak.truststore"); + File truststore = resourceFile("org/keycloak/tests/cli/keycloak.truststore"); KcRegExec exe = execute("config truststore --no-config '" + truststore.getAbsolutePath() + "'"); @@ -34,12 +34,6 @@ public void testTruststore() throws IOException { Assertions.assertEquals("Unsupported option: --no-config", exe.stderrLines().get(0), "stderr first line"); Assertions.assertEquals("Try '" + CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1), "try help"); - // only run the rest of this test if ssl protected keycloak server is available - if (!AUTH_SERVER_SSL_REQUIRED) { - System.out.println("TEST SKIPPED - This test requires HTTPS. Run with '-Pauth-server-quarkus -Dauth.server.ssl.required=true'"); - return; - } - initCustomConfigFile(); try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) { @@ -53,7 +47,7 @@ public void testTruststore() throws IOException { // perform authentication against server - asks for password, then for truststore password exe = KcRegExec.newBuilder() - .argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" + + .argsLine("config credentials --server " + keycloakUrls.getBase() + " --realm test --user user1" + " --config '" + configFile.getName() + "'") .executeAsync(); @@ -73,7 +67,7 @@ public void testTruststore() throws IOException { // perform authentication against server - asks for password, then for truststore password exe = KcRegExec.newBuilder() - .argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" + + .argsLine("config credentials --server " + keycloakUrls.getBase() + " --realm test --user user1" + " --config '" + configFile.getName() + "'") .executeAsync(); @@ -124,7 +118,7 @@ public void testTruststore() throws IOException { @Test - public void testUpdateTokenTruststore() { + void testUpdateTokenTruststore() { // TODO } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegUpdateTest.java similarity index 94% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegUpdateTest.java index eb0eaf9197ff..b3ebae59f9b8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegUpdateTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -6,24 +6,26 @@ import org.keycloak.client.cli.config.FileConfigHandler; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.testsuite.cli.KcRegExec; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcRegExec; +import org.keycloak.tests.cli.TempFileResource; import org.keycloak.util.JsonSerialization; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import static org.keycloak.client.registration.cli.KcRegMain.CMD; -import static org.keycloak.testsuite.cli.KcRegExec.execute; +import static org.keycloak.tests.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegUpdateTest extends AbstractRegCliTest { +@KeycloakIntegrationTest +class KcRegUpdateTest extends AbstractRegCliTest { @Test - public void testUpdateThoroughly() throws IOException { + void testUpdateThoroughly() throws IOException { FileConfigHandler handler = initCustomConfigFile(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegUpdateTokenTest.java similarity index 86% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java rename to tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegUpdateTokenTest.java index 84dd542597e5..97d71e7205ac 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/cli/registration/KcRegUpdateTokenTest.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli.registration; +package org.keycloak.tests.cli.registration; import java.io.IOException; @@ -7,22 +7,24 @@ import org.keycloak.client.cli.config.RealmConfigData; import org.keycloak.client.cli.util.ConfigUtil; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.testsuite.cli.KcRegExec; -import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.cli.KcRegExec; +import org.keycloak.tests.cli.TempFileResource; import org.keycloak.util.JsonSerialization; -import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.keycloak.testsuite.cli.KcRegExec.execute; +import static org.keycloak.tests.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegUpdateTokenTest extends AbstractRegCliTest { +@KeycloakIntegrationTest +class KcRegUpdateTokenTest extends AbstractRegCliTest { @Test - public void testUpdateToken() throws IOException { + void testUpdateToken() throws IOException { FileConfigHandler handler = initCustomConfigFile(); ConfigUtil.setHandler(handler); diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java index 75614dd68dbc..dabac9f4ec30 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/Base2TestSuite.java @@ -9,6 +9,7 @@ "org.keycloak.tests.actions", "org.keycloak.tests.authz", "org.keycloak.tests.broker", + "org.keycloak.tests.cli", "org.keycloak.tests.client", "org.keycloak.tests.common", "org.keycloak.tests.cookies", diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/ClusterlessTestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/ClusterlessTestSuite.java index d112ddbd681b..a62e022fefac 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/ClusterlessTestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/ClusterlessTestSuite.java @@ -6,6 +6,7 @@ import org.keycloak.testframework.server.KeycloakServerConfigBuilder; import org.keycloak.tests.admin.client.SessionTest; import org.keycloak.tests.admin.concurrency.ConcurrentLoginTest; +import org.keycloak.tests.cli.admin.KcAdmSessionTest; import org.keycloak.tests.model.UserSessionProviderOfflineTest; import org.keycloak.tests.model.UserSessionProviderTest; import org.keycloak.tests.oauth.RefreshTokenTimeoutsTest; @@ -30,6 +31,7 @@ KcOidcUserSessionLimitsBrokerTest.class, KcSamlUserSessionLimitsBrokerTest.class, SessionTimeoutValidationTest.class, + KcAdmSessionTest.class, }) public class ClusterlessTestSuite { diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/FipsNonStrictTestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/FipsNonStrictTestSuite.java index 93bda2f15c22..0b7da08350ff 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/FipsNonStrictTestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/FipsNonStrictTestSuite.java @@ -10,6 +10,11 @@ import org.keycloak.testframework.server.KeycloakServerConfigBuilder; import org.keycloak.tests.admin.ServerInfoTest; import org.keycloak.tests.admin.client.CredentialsTest; +import org.keycloak.tests.cli.AbstractCliTest; +import org.keycloak.tests.cli.admin.KcAdmCreateTest; +import org.keycloak.tests.cli.admin.KcAdmTest; +import org.keycloak.tests.cli.registration.KcRegCreateTest; +import org.keycloak.tests.cli.registration.KcRegTest; import org.keycloak.tests.client.MutualTLSClientTest; import org.keycloak.tests.forms.LoginSSLTest; import org.keycloak.tests.forms.LoginTest; @@ -29,7 +34,11 @@ OID4VCSdJwtIssuingEndpointTest.class, MutualTLSClientTest.class, LoginTest.class, - LoginSSLTest.class + LoginSSLTest.class, + KcAdmTest.class, + KcAdmCreateTest.class, + KcRegTest.class, + KcRegCreateTest.class }) public class FipsNonStrictTestSuite { @@ -39,6 +48,8 @@ public static void beforeSuite() { .registerServerConfig(FipsNonStrictServerConfig.class) .registerSupplierConfig("certificates", FipsNonStrictCertificatesConfig.class) .registerSupplierConfig("crypto", "fips", FipsMode.NON_STRICT.name()); + // the cli tests spawn kcadm/kcreg as external processes, so point them at the FIPS-enabled client tools + AbstractCliTest.useFipsClientTools(); } @AfterSuite diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/FipsStrictTestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/FipsStrictTestSuite.java index 15308709a27a..775834076385 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/FipsStrictTestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/FipsStrictTestSuite.java @@ -10,6 +10,11 @@ import org.keycloak.testframework.server.KeycloakServerConfigBuilder; import org.keycloak.tests.admin.ServerInfoTest; import org.keycloak.tests.admin.client.CredentialsTest; +import org.keycloak.tests.cli.AbstractCliTest; +import org.keycloak.tests.cli.admin.KcAdmCreateTest; +import org.keycloak.tests.cli.admin.KcAdmTest; +import org.keycloak.tests.cli.registration.KcRegCreateTest; +import org.keycloak.tests.cli.registration.KcRegTest; import org.keycloak.tests.client.MutualTLSClientTest; import org.keycloak.tests.forms.LoginSSLTest; import org.keycloak.tests.forms.LoginTest; @@ -29,7 +34,11 @@ OID4VCSdJwtIssuingEndpointTest.class, MutualTLSClientTest.class, LoginTest.class, - LoginSSLTest.class + LoginSSLTest.class, + KcAdmTest.class, + KcAdmCreateTest.class, + KcRegTest.class, + KcRegCreateTest.class }) public class FipsStrictTestSuite { @@ -39,6 +48,8 @@ public static void beforeSuite() { .registerServerConfig(FipsStrictServerConfig.class) .registerSupplierConfig("certificates", FipsStrictCertificatesConfig.class) .registerSupplierConfig("crypto", "fips", FipsMode.STRICT.name()); + // the cli tests spawn kcadm/kcreg as external processes, so point them at the FIPS-enabled client tools + AbstractCliTest.useFipsClientTools(); } @AfterSuite diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/StatelessTestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/StatelessTestSuite.java index a161cb56c3f3..25afe126dd59 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/StatelessTestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/StatelessTestSuite.java @@ -6,6 +6,7 @@ import org.keycloak.testframework.server.KeycloakServerConfigBuilder; import org.keycloak.tests.admin.client.SessionTest; import org.keycloak.tests.admin.concurrency.ConcurrentLoginTest; +import org.keycloak.tests.cli.admin.KcAdmSessionTest; import org.keycloak.tests.loginfailures.LoginFailureExpirationTest; import org.keycloak.tests.model.UserSessionProviderOfflineTest; import org.keycloak.tests.model.UserSessionProviderTest; @@ -29,7 +30,8 @@ UserSessionLimitsTest.class, SessionTimeoutValidationTest.class, LoginFailureExpirationTest.class, - OrganizationMemberTest.class + OrganizationMemberTest.class, + KcAdmSessionTest.class }) public class StatelessTestSuite { diff --git a/tests/base/src/test/java/org/keycloak/tests/suites/VolatileSessionsTestSuite.java b/tests/base/src/test/java/org/keycloak/tests/suites/VolatileSessionsTestSuite.java index dfb10bda8caf..56617c15fcbd 100644 --- a/tests/base/src/test/java/org/keycloak/tests/suites/VolatileSessionsTestSuite.java +++ b/tests/base/src/test/java/org/keycloak/tests/suites/VolatileSessionsTestSuite.java @@ -6,6 +6,7 @@ import org.keycloak.testframework.server.KeycloakServerConfigBuilder; import org.keycloak.tests.admin.client.SessionTest; import org.keycloak.tests.admin.concurrency.ConcurrentLoginTest; +import org.keycloak.tests.cli.admin.KcAdmSessionTest; import org.keycloak.tests.model.UserSessionProviderOfflineTest; import org.keycloak.tests.model.UserSessionProviderTest; import org.keycloak.tests.oauth.RefreshTokenTimeoutsTest; @@ -32,6 +33,7 @@ KcSamlUserSessionLimitsBrokerTest.class, SessionTimeoutValidationTest.class, LastSessionRefreshUnitTest.class, + KcAdmSessionTest.class, }) public class VolatileSessionsTestSuite { diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/idp-keycloak-9167.json b/tests/base/src/test/resources/org/keycloak/tests/cli/idp-keycloak-9167.json similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/idp-keycloak-9167.json rename to tests/base/src/test/resources/org/keycloak/tests/cli/idp-keycloak-9167.json diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/idp-keycloak-without-sync-mode.json b/tests/base/src/test/resources/org/keycloak/tests/cli/idp-keycloak-without-sync-mode.json similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/idp-keycloak-without-sync-mode.json rename to tests/base/src/test/resources/org/keycloak/tests/cli/idp-keycloak-without-sync-mode.json diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.jks b/tests/base/src/test/resources/org/keycloak/tests/cli/kcadm/admin-cli-keystore.jks similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.jks rename to tests/base/src/test/resources/org/keycloak/tests/cli/kcadm/admin-cli-keystore.jks diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.p12 b/tests/base/src/test/resources/org/keycloak/tests/cli/kcadm/admin-cli-keystore.p12 similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.p12 rename to tests/base/src/test/resources/org/keycloak/tests/cli/kcadm/admin-cli-keystore.p12 diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.jks b/tests/base/src/test/resources/org/keycloak/tests/cli/kcreg/reg-cli-keystore.jks similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.jks rename to tests/base/src/test/resources/org/keycloak/tests/cli/kcreg/reg-cli-keystore.jks diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.p12 b/tests/base/src/test/resources/org/keycloak/tests/cli/kcreg/reg-cli-keystore.p12 similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.p12 rename to tests/base/src/test/resources/org/keycloak/tests/cli/kcreg/reg-cli-keystore.p12 diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/saml-sp-metadata.xml b/tests/base/src/test/resources/org/keycloak/tests/cli/kcreg/saml-sp-metadata.xml similarity index 100% rename from testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/saml-sp-metadata.xml rename to tests/base/src/test/resources/org/keycloak/tests/cli/kcreg/saml-sp-metadata.xml diff --git a/tests/base/src/test/resources/org/keycloak/tests/cli/keycloak.truststore b/tests/base/src/test/resources/org/keycloak/tests/cli/keycloak.truststore new file mode 100644 index 0000000000000000000000000000000000000000..3149e7facdde9d682953e3f683fa78114260b5a4 GIT binary patch literal 214735 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%0Dv6=xy^YeTTJKmGmaw&byxFwG*2{*5q`SAbX^b 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 literal 0 HcmV?d00001 diff --git a/tests/base/src/test/resources/org/keycloak/tests/cli/testrealm.json b/tests/base/src/test/resources/org/keycloak/tests/cli/testrealm.json new file mode 100644 index 000000000000..8aebfd1f3a73 --- /dev/null +++ b/tests/base/src/test/resources/org/keycloak/tests/cli/testrealm.json @@ -0,0 +1,725 @@ +{ + "id": "test", + "realm": "test", + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "resetPasswordAllowed": true, + "editUsernameAllowed" : true, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespan": 5184000, + "requiredCredentials": [ "password" ], + "defaultRoles": [ "user" ], + "smtpServer": { + "from": "auto@keycloak.org", + "host": "localhost", + "port":"3025", + "fromDisplayName": "Keycloak SSO", + "replyTo":"reply-to@keycloak.org", + "replyToDisplayName": "Keycloak no-reply", + "envelopeFrom": "auto+bounces@keycloak.org" + }, + "users" : [ + { + "username" : "test-user@localhost", + "enabled": true, + "email" : "test-user@localhost", + "firstName": "Tom", + "lastName": "Brady", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "john-doh@localhost", + "enabled": true, + "email" : "john-doh@localhost", + "firstName": "John", + "lastName": "Doh", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "keycloak-user@localhost", + "enabled": true, + "email" : "keycloak-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "topGroupUser", + "enabled": true, + "email" : "top@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/topGroup" + ] + }, + { + "username" : "level2GroupUser", + "enabled": true, + "email" : "level2@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/topGroup/level2group" + ] + }, + { + "username" : "roleRichUser", + "enabled": true, + "email" : "rich.roles@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/roleRichGroup/level2group" + ], + "clientRoles": { + "test-app-scope": [ "test-app-allowed-by-scope", "test-app-disallowed-by-scope" ] + } + }, + { + "username" : "non-duplicate-email-user", + "enabled": true, + "email" : "non-duplicate-email-user@localhost", + "firstName": "Brian", + "lastName": "Cohen", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "user-with-one-configured-otp", + "enabled": true, + "email" : "otp1@redhat.com", + "credentials" : [ + { + "type" : "password", + "value" : "password" + }, + { + "id" : "unique", + "type" : "otp", + "secretData" : "{\"value\":\"DJmQfC73VGFhw7D4QJ8A\"}", + "credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}" + } + ] + }, + { + "username" : "user-with-two-configured-otp", + "enabled": true, + "email" : "otp2@redhat.com", + "realmRoles": ["user"], + "credentials" : [ + { + "id" : "first", + "userLabel" : "first", + "type" : "otp", + "secretData" : "{\"value\":\"DJmQfC73VGFhw7D4QJ8A\"}", + "credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}" + }, + { + "type" : "password", + "value" : "password" + }, + { + "id" : "second", + "type" : "otp", + "secretData" : "{\"value\":\"ABCQfC73VGFhw7D4QJ8A\"}", + "credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}" + } + ] + }, + { + "username" : "special>>character", + "enabled": true, + "email" : "special-character@localhost", + "firstName": "Special", + "lastName": "Character", + "credentials" : [ + { "type" : "password", + "value" : "" } + ], + "realmRoles": ["user", "offline_access"] + } + ], + "scopeMappings": [ + { + "client": "third-party", + "roles": ["user"] + }, + { + "client": "test-app", + "roles": ["user"] + }, + { + "client": "test-app-scope", + "roles": ["user", "admin"] + } + ], + "clients": [ + { + "clientId": "test-app", + "enabled": true, + "baseUrl": "http://localhost:8180/auth/realms/master/app/auth", + "redirectUris": [ + "http://localhost:8180/auth/realms/master/app/auth/*", + "https://localhost:8543/auth/realms/master/app/auth/*", + "http://localhost:8180/auth/realms/test/app/auth/*", + "https://localhost:8543/auth/realms/test/app/auth/*" + ], + "adminUrl": "http://localhost:8180/auth/realms/master/app/admin", + "secret": "password", + "protocolMappers": [ + { + "name": "audience-confidential-cli", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "confidential-cli", + "access.token.claim": "true" + } + }, + { + "name": "audience-test-app", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "test-app", + "access.token.claim": "true" + } + } + ] + }, + { + "clientId": "root-url-client", + "enabled": true, + "rootUrl": "http://localhost:8180/foo/bar", + "adminUrl": "http://localhost:8180/foo/bar", + "baseUrl": "/baz", + "redirectUris": [ + "http://localhost:8180/foo/bar/*", + "https://localhost:8543/foo/bar/*" + ], + "directAccessGrantsEnabled": true, + "secret": "password" + }, + { + "clientId" : "test-app-scope", + "enabled": true, + + "redirectUris": [ + "http://localhost:8180/auth/realms/master/app/*", + "https://localhost:8543/auth/realms/master/app/*" + ], + "secret": "password", + "fullScopeAllowed": "false", + "protocolMappers": [ + { + "name": "audience-test-app-scope", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "test-app-scope", + "access.token.claim": "true" + } + } + ] + }, + { + "clientId" : "third-party", + "description" : "A third party application", + "enabled": true, + "consentRequired": true, + + "baseUrl": "http://localhost:8180/auth/realms/master/app/auth", + "redirectUris": [ + "http://localhost:8180/auth/realms/master/app/*", + "https://localhost:8543/auth/realms/master/app/*" + ], + "secret": "password" + }, + { + "clientId": "test-app-authz", + "enabled": true, + "baseUrl": "/test-app-authz", + "adminUrl": "/test-app-authz", + "bearerOnly": false, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Admin Resource", + "uri": "/protected/admin/*", + "type": "http://test-app-authz/protected/admin", + "scopes": [ + { + "name": "admin-access" + } + ] + }, + { + "name": "Protected Resource", + "uri": "/*", + "type": "http://test-app-authz/protected/resource", + "scopes": [ + { + "name": "resource-access" + } + ] + }, + { + "name": "Premium Resource", + "uri": "/protected/premium/*", + "type": "urn:test-app-authz:protected:resource", + "scopes": [ + { + "name": "premium-access" + } + ] + }, + { + "name": "Main Page", + "type": "urn:test-app-authz:protected:resource", + "scopes": [ + { + "name": "urn:test-app-authz:page:main:actionForAdmin" + }, + { + "name": "urn:test-app-authz:page:main:actionForUser" + }, + { + "name": "urn:test-app-authz:page:main:actionForPremiumUser" + } + ] + } + ], + "policies": [ + { + "name": "Any Admin Policy", + "description": "Defines that adminsitrators can do something", + "type": "role", + "config": { + "roles": "[{\"id\":\"admin\"}]" + } + }, + { + "name": "Any User Policy", + "description": "Defines that any user can do something", + "type": "role", + "config": { + "roles": "[{\"id\":\"user\"}]" + } + }, + { + "name": "Only Premium User Policy", + "description": "Defines that only premium users can do something", + "type": "role", + "logic": "POSITIVE", + "config": { + "roles": "[{\"id\":\"customer-user-premium\"}]" + } + }, + { + "name": "All Users Policy", + "description": "Defines that all users can do something", + "type": "aggregate", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]" + } + }, + { + "name": "Premium Resource Permission", + "description": "A policy that defines access to premium resources", + "type": "resource", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Premium Resource\"]", + "applyPolicies": "[\"Only Premium User Policy\"]" + } + }, + { + "name": "Administrative Resource Permission", + "description": "A policy that defines access to administrative resources", + "type": "resource", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Admin Resource\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Protected Resource Permission", + "description": "A policy that defines access to any protected resource", + "type": "resource", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "resources": "[\"Protected Resource\"]", + "applyPolicies": "[\"All Users Policy\"]" + } + }, + { + "name": "Action 1 on Main Page Resource Permission", + "description": "A policy that defines access to action 1 on the main page", + "type": "scope", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "scopes": "[\"urn:test-app-authz:page:main:actionForAdmin\"]", + "applyPolicies": "[\"Any Admin Policy\"]" + } + }, + { + "name": "Action 2 on Main Page Resource Permission", + "description": "A policy that defines access to action 2 on the main page", + "type": "scope", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "scopes": "[\"urn:test-app-authz:page:main:actionForUser\"]", + "applyPolicies": "[\"Any User Policy\"]" + } + }, + { + "name": "Action 3 on Main Page Resource Permission", + "description": "A policy that defines access to action 3 on the main page", + "type": "scope", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "scopes": "[\"urn:test-app-authz:page:main:actionForPremiumUser\"]", + "applyPolicies": "[\"Only Premium User Policy\"]" + } + } + ] + }, + "redirectUris": [ + "/test-app-authz/*" + ], + "secret": "secret" + }, + { + "clientId": "named-test-app", + "name": "My Named Test App", + "enabled": true, + "directAccessGrantsEnabled": true, + "baseUrl": "http://localhost:8180/namedapp/base", + "redirectUris": [ + "http://localhost:8180/namedapp/base/*", + "https://localhost:8543/namedapp/base/*" + ], + "adminUrl": "http://localhost:8180/namedapp/base/admin", + "secret": "password" + }, + { + "clientId": "var-named-test-app", + "name": "Test App Named - ${client_account}", + "enabled": true, + "baseUrl": "http://localhost:8180/varnamedapp/base", + "redirectUris": [ + "http://localhost:8180/varnamedapp/base/*", + "https://localhost:8543/varnamedapp/base/*" + ], + "adminUrl": "http://localhost:8180/varnamedapp/base/admin", + "secret": "password" + }, + { + "clientId": "direct-grant", + "enabled": true, + "directAccessGrantsEnabled": true, + "secret": "password", + "webOrigins": [ "http://localtest.me:8180" ], + "protocolMappers": [ + { + "name": "aud-account", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "account", + "id.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "name": "aud-admin", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "security-admin-console", + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "clientId": "custom-audience", + "enabled": true, + "directAccessGrantsEnabled": true, + "secret": "password", + "protocolMappers": [ + { + "name": "aud", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "included.custom.audience": "foo-bar" + } + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "email" + ] + } + ], + "roles" : { + "realm" : [ + { + "name": "user", + "description": "Have User privileges" + }, + { + "name": "admin", + "description": "Have Administrator privileges" + }, + { + "name": "customer-user-premium", + "description": "Have User Premium privileges" + }, + { + "name": "sample-realm-role", + "description": "Sample realm role" + }, + { + "name": "attribute-role", + "description": "has attributes assigned", + "attributes": { + "hello": [ + "world", + "keycloak" + ] + } + }, + { + "name": "realm-composite-role", + "description": "Realm composite role containing client role", + "composite" : true, + "composites" : { + "realm" : [ "sample-realm-role" ], + "client" : { + "test-app" : [ "sample-client-role" ], + "account" : [ "view-profile" ] + } + } + } + ], + "client" : { + "test-app" : [ + { + "name": "manage-account", + "description": "Allows application-initiated actions." + }, + { + "name": "customer-user", + "description": "Have Customer User privileges" + }, + { + "name": "customer-admin", + "description": "Have Customer Admin privileges" + }, + { + "name": "sample-client-role", + "description": "Sample client role", + "attributes": { + "sample-client-role-attribute": [ + "sample-client-role-attribute-value" + ] + } + }, + { + "name": "customer-admin-composite-role", + "description": "Have Customer Admin privileges via composite role", + "composite" : true, + "composites" : { + "realm" : [ "customer-user-premium" ], + "client" : { + "test-app" : [ "customer-admin" ] + } + } + } + ], + "test-app-scope" : [ + { + "name": "test-app-allowed-by-scope", + "description": "Role allowed by scope in test-app-scope" + }, + { + "name": "test-app-disallowed-by-scope", + "description": "Role disallowed by scope in test-app-scope" + } + ] + } + + }, + "groups" : [ + { + "name": "topGroup", + "attributes": { + "topAttribute": ["true"] + + }, + "realmRoles": ["user"], + + "subGroups": [ + { + "name": "level2group", + "realmRoles": ["admin"], + "clientRoles": { + "test-app": ["customer-user"] + }, + "attributes": { + "level2Attribute": ["true"] + + } + }, + { + "name": "level2group2", + "realmRoles": ["admin"], + "clientRoles": { + "test-app": ["customer-user"] + }, + "attributes": { + "level2Attribute": ["true"] + + } + } + ] + }, + { + "name": "roleRichGroup", + "attributes": { + "topAttribute": ["true"] + + }, + "realmRoles": ["user", "realm-composite-role"], + "clientRoles": { + "account": ["manage-account"] + }, + + "subGroups": [ + { + "name": "level2group", + "realmRoles": ["admin"], + "clientRoles": { + "test-app": ["customer-user", "customer-admin-composite-role"] + }, + "attributes": { + "level2Attribute": ["true"] + + } + }, + { + "name": "level2group2", + "realmRoles": ["admin"], + "clientRoles": { + "test-app": ["customer-user"] + }, + "attributes": { + "level2Attribute": ["true"] + + } + } + ] + }, + { + "name": "sample-realm-group" + } + ], + + + "clientScopeMappings": { + "test-app": [ + { + "client": "third-party", + "roles": ["customer-user"] + }, + { + "client": "test-app-scope", + "roles": ["customer-admin-composite-role"] + } + ], + "test-app-scope": [ + { + "client": "test-app-scope", + "roles": ["test-app-allowed-by-scope"] + } + ] + }, + + "internationalizationEnabled": true, + "supportedLocales": ["en", "de"], + "defaultLocale": "en", + "eventsListeners": ["jboss-logging"] +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsArch.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsArch.java deleted file mode 100644 index 144aa7d7291a..000000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsArch.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.cli; - -/** - * @author Marko Strukelj - */ -public class OsArch { - - private String os; - private String arch; - private boolean legacy; - - public OsArch(String os, String arch) { - this(os, arch, false); - } - - public OsArch(String os, String arch, boolean legacy) { - this.os = os; - this.arch = arch; - this.legacy = legacy; - } - - public String os() { - return os; - } - - public String arch() { - return arch; - } - - public boolean isLegacy() { - return legacy; - } - - public boolean isWindows() { - return "win32".equals(os); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsUtils.java deleted file mode 100644 index f917da06ae3e..000000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/OsUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.cli; - -/** - * @author Marko Strukelj - */ -public class OsUtils { - - public static OsArch determineOSAndArch() { - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch"); - - //System.out.println("OS: " + os + ", architecture: " + arch); - if (arch.equals("amd64")) { - arch = "x86_64"; - } - - if (os.startsWith("linux")) { - if (arch.equals("x86") || arch.equals("i386") || arch.equals("i586")) { - arch = "i686"; - } - return new OsArch("linux", arch); - } else if (os.startsWith("windows")) { - if (arch.equals("x86")) { - arch = "i386"; - } - if (os.indexOf("2008") != -1 || os.indexOf("2003") != -1 || os.indexOf("vista") != -1) { - return new OsArch("win32", arch, true); - } else { - return new OsArch("win32", arch); - } - } else if (os.startsWith("sunos")) { - return new OsArch("sunos5", "x86_64"); - } else if (os.startsWith("mac os x")) { - return new OsArch("osx", "x86_64"); - } else if (os.startsWith("freebsd")) { - return new OsArch("freebsd", arch); - } else if (os.startsWith("openbsd")) { - return new OsArch("openbsd", arch); - } - - // unsupported platform - throw new RuntimeException("Could not determine OS and architecture for this operating system: " + os); - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java deleted file mode 100644 index f5e053c99852..000000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.keycloak.testsuite.cli.exec; - -/** - * @author Marko Strukelj - */ -public class ExecutionException extends RuntimeException { - - private int exitCode = -1; - - public ExecutionException(int exitCode) { - this.exitCode = exitCode; - } - - public ExecutionException(String message, int exitCode) { - super(message); - this.exitCode = exitCode; - } - - public int exitCode() { - return exitCode; - } - - @Override - public String toString() { - return super.toString() + ", exitCode: " + exitCode; - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java deleted file mode 100644 index cb3431415845..000000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.testsuite.cli.exec; - -import java.io.IOException; -import java.io.InputStream; - -class NullInputStream extends InputStream { - - @Override - public int read() throws IOException { - return -1; - } -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java deleted file mode 100644 index f702b3d58ed3..000000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.keycloak.testsuite.cli; - -import java.util.List; - -import org.keycloak.common.crypto.FipsMode; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; -import org.keycloak.testsuite.cli.exec.AbstractExec; - -import org.junit.jupiter.api.Assertions; - -/** - * @author Marko Strukelj - */ -public abstract class AbstractCliTest extends AbstractKeycloakTest { - - protected String serverUrl = "http://localhost:" + getAuthServerHttpPort() + "/auth"; - - static int getAuthServerHttpPort() { - try { - return Integer.valueOf(System.getProperty("auth.server.http.port")); - } catch (Exception e) { - throw new RuntimeException("System property 'auth.server.http.port' not set or invalid: '" - + System.getProperty("auth.server.http.port") + "'"); - } - } - - @Override - public void addTestRealms(List testRealms) { - for (RealmRepresentation tr : testRealms) { - tr.setSslRequired("external"); - } - } - - public void assertExitCodeAndStdOutSize(AbstractExec exe, int exitCode, int stdOutLineCount) { - assertExitCodeAndStreamSizes(exe, exitCode, stdOutLineCount, -1); - } - - public void assertExitCodeAndStdErrSize(AbstractExec exe, int exitCode, int stdErrLineCount) { - assertExitCodeAndStreamSizes(exe, exitCode, -1, stdErrLineCount); - } - - public void assertExitCodeAndStreamSizes(AbstractExec exe, int exitCode, int stdOutLineCount, int stdErrLineCount) { - Assertions.assertEquals(exitCode, exe.exitCode(), "exitCode == " + exitCode); - if (stdOutLineCount != -1) { - assertLineCount("STDOUT: " + exe.stdoutString(), exe.stdoutLines(), stdOutLineCount); - } - // There is additional logging in case that BC FIPS libraries are used, so the count of logged lines don't match with the case with plain BC used - // Hence we test count of lines just with FIPS disabled - if (stdErrLineCount != -1 && isFipsDisabled()) { - assertLineCount("STDERR: " + exe.stderrString(), exe.stderrLines(), stdErrLineCount); - } - } - - private void assertLineCount(String label, List lines, int count) { - if (lines.size() == count) { - return; - } - // there is some kind of race condition in 'kcreg' that results in intermittent extra empty line - if (lines.size() == count + 1) { - if ("".equals(lines.get(lines.size()-1))) { - return; - } - } - Assertions.assertTrue(lines.size() == count, label + " has " + lines.size() + " lines (expected: " + count + ")"); - } - - private boolean isFipsDisabled() { - return AuthServerTestEnricher.AUTH_SERVER_FIPS_MODE == FipsMode.DISABLED; - } - -} diff --git a/testsuite/integration-arquillian/tests/base/testsuites/base-suite b/testsuite/integration-arquillian/tests/base/testsuites/base-suite index 9725a7840b54..bbc5150224d5 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/base-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/base-suite @@ -3,7 +3,6 @@ actions,1 adapter,IGNORED authz,3 broker,2 -cli,4 client,4 cluster,IGNORED composites,4 diff --git a/testsuite/integration-arquillian/tests/base/testsuites/fips-suite b/testsuite/integration-arquillian/tests/base/testsuites/fips-suite index 471f274b9e9c..c0bce6941407 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/fips-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/fips-suite @@ -7,10 +7,6 @@ LDAPUserLoginTest org.keycloak.testsuite.x509.** FAPI1Test FAPICIBATest -KcRegTest -KcRegCreateTest -KcAdmTest -KcAdmCreateTest SAMLServletAdapterTest SamlSignatureTest KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test diff --git a/testsuite/integration-arquillian/tests/base/testsuites/stateless-suite b/testsuite/integration-arquillian/tests/base/testsuites/stateless-suite index acf5cd5ba1eb..05447c5d4c90 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/stateless-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/stateless-suite @@ -1,7 +1,6 @@ KcSamlBrokerSessionNotOnOrAfterTest OidcClaimToUserSessionNoteMapperTest KcOidcBrokerTransientSessionsTest -KcAdmSessionTest TransientSessionTest OAuthDanceClientSessionExtensionTest SessionNotOnOrAfterTest diff --git a/testsuite/integration-arquillian/tests/base/testsuites/volatile-sessions-suite b/testsuite/integration-arquillian/tests/base/testsuites/volatile-sessions-suite index acf5cd5ba1eb..05447c5d4c90 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/volatile-sessions-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/volatile-sessions-suite @@ -1,7 +1,6 @@ KcSamlBrokerSessionNotOnOrAfterTest OidcClaimToUserSessionNoteMapperTest KcOidcBrokerTransientSessionsTest -KcAdmSessionTest TransientSessionTest OAuthDanceClientSessionExtensionTest SessionNotOnOrAfterTest