diff --git a/authz/client/pom.xml b/authz/client/pom.xml index d410042e7ec0..c5123b947f17 100644 --- a/authz/client/pom.xml +++ b/authz/client/pom.xml @@ -67,6 +67,28 @@ jackson-annotations provided + + org.junit.jupiter + junit-jupiter + test + + + org.hamcrest + hamcrest + test + + + com.github.stefanbirkner + system-rules + 1.19.0 + test + + + uk.org.webcompere + system-stubs-jupiter + 2.1.6 + test + diff --git a/authz/client/src/test/java/org/keycloak/authorization/client/AuthzClientTest.java b/authz/client/src/test/java/org/keycloak/authorization/client/AuthzClientTest.java new file mode 100644 index 000000000000..36562015a19b --- /dev/null +++ b/authz/client/src/test/java/org/keycloak/authorization/client/AuthzClientTest.java @@ -0,0 +1,45 @@ +package org.keycloak.authorization.client; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.io.ByteArrayInputStream; + +@ExtendWith(SystemStubsExtension.class) +public class AuthzClientTest { + + @SystemStub + public final EnvironmentVariables envVars = new EnvironmentVariables(); + + @Test + public void testCreateWithEnvVars() { + envVars.set("KEYCLOAK_REALM", "test"); + envVars.set("KEYCLOAK_AUTH_SERVER", "http://test"); + + RuntimeException runtimeException = Assertions.assertThrows(RuntimeException.class, () -> { + + AuthzClient.create(new ByteArrayInputStream(("{\n" + + " \"realm\": \"${env.KEYCLOAK_REALM}\",\n" + + " \"auth-server-url\": \"${env.KEYCLOAK_AUTH_SERVER}\",\n" + + " \"ssl-required\": \"external\",\n" + + " \"enable-cors\": true,\n" + + " \"resource\": \"my-server\",\n" + + " \"credentials\": {\n" + + " \"secret\": \"${env.KEYCLOAK_SECRET}\"\n" + + " },\n" + + " \"confidential-port\": 0,\n" + + " \"policy-enforcer\": {\n" + + " \"enforcement-mode\": \"ENFORCING\"\n" + + " }\n" + + "}").getBytes())); + }); + + MatcherAssert.assertThat(runtimeException.getMessage(), Matchers.containsString("Could not obtain configuration from server")); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AbstractPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AbstractPage.java index 5a18c851c02e..d7cbe40138f6 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AbstractPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AbstractPage.java @@ -36,7 +36,7 @@ public abstract class AbstractPage { @ArquillianResource protected SuiteContext suiteContext; - @ArquillianResource + //@ArquillianResource protected WebDriver driver; @ArquillianResource diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index fc2bbe463c99..85f17b48487e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -27,6 +27,7 @@ import org.jboss.logging.Logger; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.runner.RunWith; import org.junit.runners.model.TestTimedOutException; @@ -61,6 +62,7 @@ import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.TestCleanup; import org.keycloak.testsuite.util.TestEventsLogger; +import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.WebDriver; import jakarta.ws.rs.NotFoundException; @@ -92,6 +94,9 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import org.keycloak.models.UserModel; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; + import static org.keycloak.testsuite.admin.Users.setPasswordFor; import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST; @@ -131,7 +136,7 @@ public abstract class AbstractKeycloakTest { protected List testRealmReps; @Drone - protected WebDriver driver; + protected static WebDriver driver; @Page protected AuthServerContextRoot authServerContextRootPage; @@ -157,6 +162,20 @@ public abstract class AbstractKeycloakTest { private boolean resetTimeOffset; + /*@BeforeClass + public static void initDriver() { + ChromeOptions options = new ChromeOptions(); + // Waits for webpage setup + options.setPageLoadStrategy(PageLoadStrategy.NORMAL); + // Chromium headless browser + options.addArguments("--headless=new"); + // Turn on BiDi protocol + //options.setCapability("webSocketUrl", true); + options.addArguments("--ignore-certificate-errors"); + + driver = new ChromeDriver(options); + }*/ + @Before public void beforeAbstractKeycloakTest() throws Exception { adminClient = testContext.getAdminClient(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java index c4361967cbff..d5ae7f5572a1 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -21,11 +21,7 @@ import org.hamcrest.Matchers; import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; -import org.junit.After; -import org.junit.Before; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; +import org.junit.*; import org.keycloak.TokenVerifier; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.GroupResource; @@ -97,6 +93,7 @@ import org.keycloak.userprofile.validator.UsernameProhibitedCharactersValidator; import org.keycloak.util.JsonSerialization; import org.openqa.selenium.By; +import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.WebDriver; import jakarta.mail.internet.MimeMessage; @@ -105,6 +102,9 @@ import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; + import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -152,8 +152,7 @@ public class UserTest extends AbstractAdminTest { @Rule public GreenMailRule greenMail = new GreenMailRule(); - @Drone - protected WebDriver driver; + protected static WebDriver driver; @Page protected LoginPasswordUpdatePage passwordUpdatePage; @@ -192,9 +191,29 @@ public class UserTest extends AbstractAdminTest { } } + /*@BeforeClass + public static void beforeClass() { + ChromeOptions options = new ChromeOptions(); + // Waits for webpage setup + options.setPageLoadStrategy(PageLoadStrategy.NORMAL); + // Chromium headless browser + options.addArguments("--headless=new"); + // Turn on BiDi protocol + //options.setCapability("webSocketUrl", true); + options.addArguments("--ignore-certificate-errors"); + + driver = new ChromeDriver(options); + }*/ + @Before public void beforeUserTest() throws IOException { - createAppClientInRealm(REALM_NAME); + + /*proceedPage.setDriver(driver); + infoPage.setDriver(driver); + loginPage.setDriver(driver); + errorPage.setDriver(driver); + oauth.setDriver(driver); + createAppClientInRealm(REALM_NAME);*/ VerifyProfileTest.setUserProfileConfiguration(realm, null); UPConfig upConfig = realm.users().userProfile().getConfiguration(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java index 59dbe39550ab..9e16b49f6a1b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserFlowTest.java @@ -3,9 +3,7 @@ import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.test.api.ArquillianResource; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; +import org.junit.*; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticator; @@ -49,8 +47,11 @@ import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.URLUtils; import org.openqa.selenium.By; +import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; import java.util.Arrays; import java.util.Collections; @@ -74,8 +75,8 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { @ArquillianResource protected OAuthClient oauth; - @Drone - protected WebDriver driver; + //@Drone + //protected WebDriver driver; @Page protected LoginPage loginPage; @@ -98,6 +99,17 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest { @Rule public AssertEvents events = new AssertEvents(this); + @Before + public void beforeBrowserFlowTest() { + loginPage.setDriver(driver); + loginUsernameOnlyPage.setDriver(driver); + passwordPage.setDriver(driver); + errorPage.setDriver(driver); + loginTotpPage.setDriver(driver); + oneTimeCodePage.setDriver(driver); + oauth.setDriver(driver); + } + @Override public void configureTestRealm(RealmRepresentation testRealm) { } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/DirectGrantFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/DirectGrantFlowTest.java index 99e72ee071f7..98c5089fa486 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/DirectGrantFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/DirectGrantFlowTest.java @@ -51,8 +51,8 @@ public class DirectGrantFlowTest extends AbstractTestRealmKeycloakTest { @ArquillianResource protected OAuthClient oauth; - @Drone - protected WebDriver driver; + //@Drone + //protected WebDriver driver; @Rule public AssertEvents events = new AssertEvents(this); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultiFactorAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultiFactorAuthenticationTest.java index 2381f7d33499..7750b35dd914 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultiFactorAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultiFactorAuthenticationTest.java @@ -25,6 +25,7 @@ import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.test.api.ArquillianResource; import org.junit.Assert; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.authentication.AuthenticationFlow; @@ -62,8 +63,8 @@ public class MultiFactorAuthenticationTest extends AbstractTestRealmKeycloakTest @ArquillianResource protected OAuthClient oauth; - @Drone - protected WebDriver driver; + //@Drone + //protected WebDriver driver; @Page protected LoginPage loginPage; @@ -90,6 +91,17 @@ public class MultiFactorAuthenticationTest extends AbstractTestRealmKeycloakTest public void configureTestRealm(RealmRepresentation testRealm) { } + @Before + public void beforeMultiFactorAuthenticationTest() { + loginPage.setDriver(driver); + loginUsernameOnlyPage.setDriver(driver); + passwordPage.setDriver(driver); + errorPage.setDriver(driver); + loginTotpPage.setDriver(driver); + selectAuthenticatorPage.setDriver(driver); + oauth.setDriver(driver); + } + private RealmRepresentation loadTestRealm() { RealmRepresentation res = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); res.setBrowserFlow("browser"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ReAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ReAuthenticationTest.java index 24207a2d7157..e9d5c50b50ce 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ReAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ReAuthenticationTest.java @@ -27,6 +27,7 @@ import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.test.api.ArquillianResource; +import org.junit.Before; import org.junit.Test; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.authentication.authenticators.browser.PasswordFormFactory; @@ -68,8 +69,8 @@ public class ReAuthenticationTest extends AbstractTestRealmKeycloakTest { @ArquillianResource protected OAuthClient oauth; - @Drone - protected WebDriver driver; + //@Drone + //protected WebDriver driver; @Page protected LoginPage loginPage; @@ -92,6 +93,17 @@ public class ReAuthenticationTest extends AbstractTestRealmKeycloakTest { @Page protected AppPage appPage; + @Before + public void beforeReAuthenticationTest() { + loginPage.setDriver(driver); + loginUsernameOnlyPage.setDriver(driver); + passwordPage.setDriver(driver); + errorPage.setDriver(driver); + loginTotpPage.setDriver(driver); + appPage.setDriver(driver); + oauth.setDriver(driver); + } + @Override public void configureTestRealm(RealmRepresentation testRealm) { } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java index 8b85457c2886..3bbfe7e3eb05 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java @@ -79,7 +79,7 @@ public void addTestRealms(List testRealms) { public void beforeAbstractKeycloakTest() throws Exception { Assume.assumeThat("Test skipped", suiteContext.getAuthServerInfo().isJBossBased(), - Matchers.is(true)); + Matchers.is(false)); DroneUtils.replaceDefaultWebDriver(this, phantomJS); setDefaultPageUriParameters(); } diff --git a/testsuite/integration-playwright/pom.xml b/testsuite/integration-playwright/pom.xml new file mode 100644 index 000000000000..3da6df4a33f9 --- /dev/null +++ b/testsuite/integration-playwright/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + + + org.keycloak + keycloak-testsuite-pom + 999.0.0-SNAPSHOT + ../pom.xml + + + org.keycloak.testsuite + integration-playwright + + Keycloak Playwright Integration TestSuite + + + UTF-8 + + + + com.microsoft.playwright + playwright + 1.41.2 + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.keycloak + keycloak-junit5 + test + + + org.keycloak + keycloak-admin-client + test + + + org.keycloak + keycloak-test-helper + 999.0.0-SNAPSHOT + test + + + org.keycloak.testsuite + integration-arquillian-tests-base + 999.0.0-SNAPSHOT + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + maven-surefire-plugin + + + + admin + admin + + + + junit.jupiter.execution.parallel.enabled=false + junit.jupiter.execution.parallel.mode.default=concurrent + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/KeycloakTest.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/KeycloakTest.java new file mode 100644 index 000000000000..051d67a44324 --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/KeycloakTest.java @@ -0,0 +1,756 @@ +package org.keycloak.testsuite; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.playwright.*; +import io.appium.java_client.AppiumDriver; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Response; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.jboss.logging.Logger; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.runners.model.TestTimedOutException; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.*; +import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.common.util.Time; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.TimeBasedOTP; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RequiredActionProviderRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.userprofile.config.UPAttribute; +import org.keycloak.representations.userprofile.config.UPAttributePermissions; +import org.keycloak.representations.userprofile.config.UPAttributeRequired; +import org.keycloak.representations.userprofile.config.UPConfig; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.arquillian.TestContext; +import org.keycloak.testsuite.client.KeycloakTestingClient; +import org.keycloak.testsuite.server.EmbeddedKeycloakLifecycle; +import org.keycloak.testsuite.server.KeycloakLifecycle; +import org.keycloak.testsuite.util.DroneUtils; +import org.keycloak.testsuite.util.TestCleanup; +import org.keycloak.util.JsonSerialization; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.Consumer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.keycloak.testsuite.admin.Users.setPasswordFor; +import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; +import static org.keycloak.testsuite.util.ServerURLs.*; +import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_PORT; +import static org.keycloak.testsuite.util.URLUtils.navigateToUri; +import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN; +import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER; + +public abstract class KeycloakTest { + + protected Logger log = Logger.getLogger(this.getClass()); + protected static Playwright playwright; + protected static Browser browser; + + protected BrowserContext context; + protected Page page; + + protected static KeycloakLifecycle keycloak; + + protected static Keycloak adminClient; + + //protected TestContext testContext; + + protected List testRealmReps; + + private boolean resetTimeOffset; + + //protected KeycloakTestingClient testingClient; + + @BeforeAll + static void launchBrowser() throws Throwable { + playwright = Playwright.create(); + BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions().setHeadless(true); + browser = playwright.chromium().launch(launchOptions); + + keycloak = EmbeddedKeycloakLifecycle.getInstance(); + if (!keycloak.isRunning()) { + keycloak.start(); + } + + adminClient = Keycloak.getInstance(keycloak.getBaseUrl(), "master", "admin", "admin", "admin-cli"); + } + + @AfterAll + static void closeBrowser() { + playwright.close(); + adminClient.close(); + } + + @BeforeEach + void createContextAndPage() throws Exception { + context = browser.newContext(); + page = context.newPage(); + + //adminClient = testContext.getAdminClient(); + if (adminClient == null || adminClient.isClosed()) { + adminClient = Keycloak.getInstance(keycloak.getBaseUrl(), "master", "admin", "admin", "admin-cli"); + } + + //getTestingClient(); + + //setDefaultPageUriParameters(); + + //TestEventsLogger.setDriver(driver); + + // The backend cluster nodes may not be yet started. Password will be updated later for cluster setup. + /*if (!AuthServerTestEnricher.AUTH_SERVER_CLUSTER) { + updateMasterAdminPassword(); + }*/ + + beforeAbstractKeycloakTestRealmImport(); + + importTestRealms(); + + /*if (testContext.getTestRealmReps().isEmpty()) { + importTestRealms(); + + if (!isImportAfterEachMethod()) { + testContext.setTestRealmReps(testRealmReps); + } + + afterAbstractKeycloakTestRealmImport(); + }*/ + + //oauth.init(driver); + } + + @AfterEach + void closeContext() throws IOException { + context.close(); + //adminClient.realms().realm("test").remove(); + } + + + private boolean importTestRealm(String realmJsonPath) throws IOException { + + ObjectMapper mapper = new ObjectMapper(); + try (InputStream fis = getClass().getResourceAsStream(realmJsonPath)) { + RealmRepresentation realmRepresentation = mapper.readValue(fis, RealmRepresentation.class); + adminClient.realms().create(realmRepresentation); + return true; + } + + } + + public static UPConfig setUserProfileConfiguration(RealmResource testRealm, String configuration) { + try { + UPConfig config = configuration == null ? null : JsonSerialization.readValue(configuration, UPConfig.class); + + if (config != null) { + UPAttribute username = config.getAttribute(UserModel.USERNAME); + + if (username == null) { + config.addOrReplaceAttribute(new UPAttribute(UserModel.USERNAME)); + } + + UPAttribute email = config.getAttribute(UserModel.EMAIL); + + if (email == null) { + config.addOrReplaceAttribute(new UPAttribute(UserModel.EMAIL, new UPAttributePermissions(new HashSet<>(Arrays.asList(ROLE_USER, ROLE_ADMIN)), new HashSet<>(Arrays.asList(ROLE_USER, ROLE_ADMIN))), new UPAttributeRequired(new HashSet<>(Collections.singletonList(ROLE_USER)), new HashSet<>()))); + } + } + + testRealm.users().userProfile().update(config); + + return config; + } catch (IOException ioe) { + throw new RuntimeException("Failed to read configuration", ioe); + } + } + + /*public void reconnectAdminClient() throws Exception { + testContext.reconnectAdminClient(); + adminClient = testContext.getAdminClient(); + }*/ + + /** + * Executed before test realms import + *

+ * In @Before block + */ + protected void beforeAbstractKeycloakTestRealmImport() throws Exception { + } + + /** + * Executed after test realms import + *

+ * In @Before block + */ + protected void afterAbstractKeycloakTestRealmImport() { + } + + /** + * Executed as the last task of each test case + *

+ * In @After block + */ + protected void postAfterAbstractKeycloak() throws Exception { + } + + @AfterEach + public void afterAbstractKeycloakTest() throws Exception { + /*if (resetTimeOffset) { + resetTimeOffset(); + }*/ + + if (isImportAfterEachMethod()) { + log.info("removing test realms after test method"); + for (RealmRepresentation testRealm : testRealmReps) { + removeRealm(testRealm.getRealm()); + } + } else { + /*log.info("calling all TestCleanup"); + // Remove all sessions + testContext.getTestRealmReps().stream().forEach((r)->testingClient.testing().removeUserSessions(r.getRealm())); + + // Cleanup objects + for (TestCleanup cleanup : testContext.getCleanups().values()) { + try { + if (cleanup != null) cleanup.executeCleanup(); + } catch (Exception e) { + log.error("failed cleanup!", e); + throw new RuntimeException(e); + } + } + testContext.getCleanups().clear();*/ + } + + postAfterAbstractKeycloak(); + + // Remove all browsers from queue + DroneUtils.resetQueue(); + } + + /*protected TestCleanup getCleanup(String realmName) { + return testContext.getOrCreateCleanup(realmName); + } + + protected TestCleanup getCleanup() { + return getCleanup("test"); + }*/ + + protected boolean isImportAfterEachMethod() { + return false; + } + + /*protected void updateMasterAdminPassword() { + if (!suiteContext.isAdminPasswordUpdated()) { + log.debug("updating admin password"); + + welcomePage.navigateTo(); + if (!welcomePage.isPasswordSet()) { + welcomePage.setPassword("admin", "admin"); + } + + suiteContext.setAdminPasswordUpdated(true); + } + } + + public void deleteAllCookiesForMasterRealm() { + deleteAllCookiesForRealm(MASTER); + } + + protected void deleteAllCookiesForRealm(String realmName) { + navigateToUri(oauth.SERVER_ROOT + "/auth/realms/" + realmName + "/testing/blank"); + log.info("deleting cookies in '" + realmName + "' realm"); + driver.manage().deleteAllCookies(); + }*/ + + // this is useful mainly for smartphones as cookies deletion doesn't work there + protected void deleteAllSessionsInRealm(String realmName) { + log.info("removing all sessions from '" + realmName + "' realm..."); + try { + adminClient.realm(realmName).logoutAll(); + log.info("sessions successfully deleted"); + } + catch (NotFoundException e) { + log.warn("realm not found"); + } + } + + /*protected void resetRealmSession(String realmName) { + deleteAllCookiesForRealm(realmName); + + if (driver instanceof AppiumDriver) { // smartphone drivers don't support cookies deletion + try { + log.info("resetting realm session"); + + final RealmRepresentation realmRep = adminClient.realm(realmName).toRepresentation(); + + deleteAllSessionsInRealm(realmName); // logout users + + if (realmRep.isInternationalizationEnabled()) { // reset the locale + String locale = getDefaultLocaleName(realmRep.getRealm()); + loginPage.localeDropdown().selectByText(locale); + log.info("locale reset to " + locale); + } + } catch (NotFoundException e) { + log.warn("realm not found"); + } + } + } + + protected String getDefaultLocaleName(String realmName) { + return ENGLISH_LOCALE_NAME; + } + + public void setDefaultPageUriParameters() { + masterRealmPage.setAuthRealm(MASTER); + loginPage.setAuthRealm(MASTER); + } + + public KeycloakTestingClient getTestingClient() { + if (testingClient == null) { + testingClient = testContext.getTestingClient(); + } + return testingClient; + } + + public TestContext getTestContext() { + return testContext; + }*/ + + public Keycloak getAdminClient() { + return adminClient; + } + + public abstract void addTestRealms(List testRealms); + + private void addTestRealms() { + log.debug("loading test realms"); + if (testRealmReps == null) { + testRealmReps = new ArrayList<>(); + } + if (testRealmReps.isEmpty()) { + addTestRealms(testRealmReps); + } + } + + public void fixAuthServerHostAndPortForClientRepresentation(ClientRepresentation cr) { + cr.setBaseUrl(removeDefaultPorts(replaceAuthHostWithRealHost(cr.getBaseUrl()))); + cr.setAdminUrl(removeDefaultPorts(replaceAuthHostWithRealHost(cr.getAdminUrl()))); + + if (cr.getRedirectUris() != null && !cr.getRedirectUris().isEmpty()) { + List fixedUrls = new ArrayList<>(cr.getRedirectUris().size()); + for (String url : cr.getRedirectUris()) { + fixedUrls.add(removeDefaultPorts(replaceAuthHostWithRealHost(url))); + } + + cr.setRedirectUris(fixedUrls); + } + } + + public String replaceAuthHostWithRealHost(String url) { + if (url != null && (url.contains("localhost:8180") || url.contains("localhost:8543"))) { + return url.replaceFirst("localhost:(\\d)+", AUTH_SERVER_HOST + ":" + AUTH_SERVER_PORT); + } + + return url; + } + + public void importTestRealms() { + addTestRealms(); + log.info("importing test realms"); + for (RealmRepresentation testRealm : testRealmReps) { + importRealm(testRealm); + } + } + + private void modifySamlAttributes(ClientRepresentation cr) { + if (cr.getProtocol() != null && cr.getProtocol().equals("saml")) { + log.debug("Modifying attributes of SAML client: " + cr.getClientId()); + for (Map.Entry entry : cr.getAttributes().entrySet()) { + cr.getAttributes().put(entry.getKey(), replaceHttpValuesWithHttps(entry.getValue())); + } + } + } + + private void modifyRedirectUrls(ClientRepresentation cr) { + if (cr.getRedirectUris() != null && cr.getRedirectUris().size() > 0) { + List redirectUrls = cr.getRedirectUris(); + List fixedRedirectUrls = new ArrayList<>(redirectUrls.size()); + for (String url : redirectUrls) { + fixedRedirectUrls.add(replaceHttpValuesWithHttps(url)); + } + cr.setRedirectUris(fixedRedirectUrls); + } + } + + private void modifyMainUrls(ClientRepresentation cr) { + cr.setBaseUrl(replaceHttpValuesWithHttps(cr.getBaseUrl())); + cr.setAdminUrl(replaceHttpValuesWithHttps(cr.getAdminUrl())); + } + + private String replaceHttpValuesWithHttps(String input) { + if (input == null) { + return null; + } + if ("".equals(input)) { + return ""; + } + return input + .replace("http", "https") + .replace("8080", "8543") + .replace("8180", "8543"); + } + + protected interface ExecutableTestMethod { + void execute() throws Exception; + } + + protected void runTestWithTimeout(long timeout, ExecutableTestMethod executableTestMethod) throws Exception { + ExecutorService service = Executors.newSingleThreadExecutor(); + Callable callable = new Callable() { + public Object call() throws Exception { + executableTestMethod.execute(); + return null; + } + }; + Future result = service.submit(callable); + service.shutdown(); + try { + boolean terminated = service.awaitTermination(timeout, + TimeUnit.MILLISECONDS); + if (!terminated) { + service.shutdownNow(); + } + result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation + } catch (TimeoutException e) { + throw new TestTimedOutException(timeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new Exception(e); + } + } + + /** + * @return Return true if you wish to automatically post-process realm and replace + * all http values with https (and correct ports). + */ + protected boolean modifyRealmForSSL() { + return false; + } + + + protected void removeAllRealmsDespiteMaster() { + // remove all realms (accidentally left by other tests) except for master + adminClient.realms().findAll().stream() + .map(RealmRepresentation::getRealm) + .filter(realmName -> ! realmName.equals("master")) + .forEach(this::removeRealm); + assertThat(adminClient.realms().findAll().size(), is(equalTo(1))); + } + + protected boolean removeVerifyProfileAtImport() { + // remove verify profile by default because most tests are not prepared + return true; + } + + public void importRealm(RealmRepresentation realm) { + if (modifyRealmForSSL()) { + if (AUTH_SERVER_SSL_REQUIRED) { + log.debugf("Modifying %s for SSL", realm.getId()); + for (ClientRepresentation cr : realm.getClients()) { + modifyMainUrls(cr); + modifyRedirectUrls(cr); + modifySamlAttributes(cr); + } + } + } + + if (!AUTH_SERVER_HOST.equals("localhost")) { + if (!AUTH_SERVER_SSL_REQUIRED) { + realm.setSslRequired("none"); + } + if (realm.getClients() != null) { + for (ClientRepresentation cr : realm.getClients()) { + fixAuthServerHostAndPortForClientRepresentation(cr); + } + } + + if (realm.getApplications() != null) { + for (ClientRepresentation cr : realm.getApplications()) { + fixAuthServerHostAndPortForClientRepresentation(cr); + } + } + } + + log.debug("--importing realm: " + realm.getRealm()); + try { + adminClient.realms().realm(realm.getRealm()).remove(); + log.debug("realm already existed on server, re-importing"); + } catch (NotFoundException ignore) { + // expected when realm does not exist + } + adminClient.realms().create(realm); + + if (removeVerifyProfileAtImport()) { + try { + RequiredActionProviderRepresentation vpModel = adminClient.realm(realm.getRealm()).flows() + .getRequiredAction(UserModel.RequiredAction.VERIFY_PROFILE.name()); + vpModel.setEnabled(false); + vpModel.setDefaultAction(false); + adminClient.realm(realm.getRealm()).flows().updateRequiredAction( + UserModel.RequiredAction.VERIFY_PROFILE.name(), vpModel); + //testingClient.testing().pollAdminEvent(); // remove the event + } catch (NotFoundException ignore) { + } + } + } + + public void removeRealm(String realmName) { + log.info("removing realm: " + realmName); + try { + adminClient.realms().realm(realmName).remove(); + } catch (NotFoundException e) { + } + } + + public RealmsResource realmsResouce() { + return adminClient.realms(); + } + + /** + * Creates a user in the given realm and returns its ID. + * + * @param realm Realm name + * @param username Username + * @param password Password + * @param requiredActions + * @return ID of the newly created user + */ + public String createUser(String realm, String username, String password, String... requiredActions) { + UserRepresentation homer = createUserRepresentation(username, password); + homer.setRequiredActions(Arrays.asList(requiredActions)); + + return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer); + } + + public String createUser(String realm, String username, String password, String firstName, String lastName, String email, Consumer customizer) { + UserRepresentation user = createUserRepresentation(username, email, firstName, lastName, true, password); + customizer.accept(user); + return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), user); + } + + public String createUser(String realm, String username, String password, String firstName, String lastName, String email) { + UserRepresentation homer = createUserRepresentation(username, email, firstName, lastName, true, password); + return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer); + } + + public static UserRepresentation createUserRepresentation(String id, String username, String email, String firstName, String lastName, List groups, boolean enabled) { + UserRepresentation user = new UserRepresentation(); + user.setId(id); + user.setUsername(username); + user.setEmail(email); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setGroups(groups); + user.setEnabled(enabled); + return user; + } + + public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, List groups, boolean enabled) { + return createUserRepresentation(null, username, email, firstName, lastName, groups, enabled); + } + + public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, boolean enabled) { + return createUserRepresentation(username, email, firstName, lastName, null, enabled); + } + + public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, boolean enabled, String password) { + UserRepresentation user = createUserRepresentation(username, email, firstName, lastName, enabled); + setPasswordFor(user, password); + return user; + } + + public static UserRepresentation createUserRepresentation(String username, String password) { + UserRepresentation user = createUserRepresentation(username, null, null, null, true, password); + return user; + } + + protected void createAppClientInRealm(String realm) { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId("test-app"); + client.setName("test-app"); + client.setSecret("password"); + client.setEnabled(true); + client.setDirectAccessGrantsEnabled(true); + + client.setRedirectUris(Collections.singletonList(keycloak.getBaseUrl() + "/auth/*")); + client.setBaseUrl(keycloak.getBaseUrl() + "/auth/realms/" + realm + "/app"); + + OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+")); + + Response response = adminClient.realm(realm).clients().create(client); + response.close(); + } + + public void setRequiredActionEnabled(String realm, String requiredAction, boolean enabled, boolean defaultAction) { + AuthenticationManagementResource managementResource = adminClient.realm(realm).flows(); + + RequiredActionProviderRepresentation action = managementResource.getRequiredAction(requiredAction); + action.setEnabled(enabled); + action.setDefaultAction(defaultAction); + + managementResource.updateRequiredAction(requiredAction, action); + } + + public void setRequiredActionEnabled(String realm, String userId, String requiredAction, boolean enabled) { + UsersResource usersResource = adminClient.realm(realm).users(); + + UserResource userResource = usersResource.get(userId); + UserRepresentation userRepresentation = userResource.toRepresentation(); + + List requiredActions = userRepresentation.getRequiredActions(); + if (enabled && !requiredActions.contains(requiredAction)) { + requiredActions.add(requiredAction); + } else if (!enabled && requiredActions.contains(requiredAction)) { + requiredActions.remove(requiredAction); + } + + userResource.update(userRepresentation); + } + + /** + * Sets time of day by calculating time offset and using setTimeOffset() to set it. + * + * @param hour hour of day + * @param minute minute + * @param second second + */ + public void setTimeOfDay(int hour, int minute, int second) { + setTimeOfDay(hour, minute, second, 0); + } + + /** + * Sets time of day by calculating time offset and using setTimeOffset() to set it. + * + * @param hour hour of day + * @param minute minute + * @param second second + * @param addSeconds additional seconds to add to offset time + */ + public void setTimeOfDay(int hour, int minute, int second, int addSeconds) { + Calendar now = Calendar.getInstance(); + now.set(Calendar.HOUR_OF_DAY, hour); + now.set(Calendar.MINUTE, minute); + now.set(Calendar.SECOND, second); + int offset = (int) ((now.getTime().getTime() - System.currentTimeMillis()) / 1000); + + //setTimeOffset(offset + addSeconds); + } + + /** + * Sets time offset in seconds that will be added to Time.currentTime() and Time.currentTimeMillis() both for client and server. + * Moves time on the remote Infinispan server as well if the HotRod storage is used. + * + * @param + */ + /*public void setTimeOffset(int offset) { + String response = invokeTimeOffset(offset); + resetTimeOffset = offset != 0; + log.debugv("Set time offset, response {0}", response); + } + + public void resetTimeOffset() { + String response = invokeTimeOffset(0); + resetTimeOffset = false; + log.debugv("Reset time offset, response {0}", response); + } + + public void setOtpTimeOffset(int offsetSeconds, TimeBasedOTP otp) { + setTimeOffset(offsetSeconds); + final Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.SECOND, offsetSeconds); + otp.setCalendar(calendar); + } + + public int getCurrentTime() { + return Time.currentTime(); + } + + protected String invokeTimeOffset(int offset) { + // adminClient depends on Time.offset for auto-refreshing tokens + Time.setOffset(offset); + Map result = testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset))); + + // force refreshing token after time offset has changed + try { + adminClient.tokenManager().refreshToken(); + } catch (RuntimeException e) { + adminClient.tokenManager().grantToken(); + } + + return String.valueOf(result); + }*/ + + /*private void loadConstantsProperties() throws ConfigurationException { + constantsProperties = new PropertiesConfiguration(System.getProperty("testsuite.constants")); + constantsProperties.setThrowExceptionOnMissing(true); + } + + protected PropertiesConfiguration getConstantsProperties() throws ConfigurationException { + if (constantsProperties == null) { + loadConstantsProperties(); + } + return constantsProperties; + } + + public URI getAuthServerRoot() { + try { + return KeycloakUriBuilder.fromUri(suiteContext.getAuthServerInfo().getContextRoot().toURI()).path("/auth/").build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + }*/ + + public Logger getLogger() { + return log; + } + + protected static InputStream httpsAwareConfigurationStream(InputStream input) throws IOException { + if (!AUTH_SERVER_SSL_REQUIRED) { + return input; + } + PipedInputStream in = new PipedInputStream(); + final PipedOutputStream out = new PipedOutputStream(in); + try (PrintWriter pw = new PrintWriter(out)) { + try (Scanner s = new Scanner(input)) { + while (s.hasNextLine()) { + String lineWithReplaces = s.nextLine().replace("http://localhost:8180/auth", AUTH_SERVER_SCHEME + "://localhost:" + AUTH_SERVER_PORT + "/auth"); + pw.println(lineWithReplaces); + } + } + } + return in; + } + + protected void assertResponseSuccessful(Response response) { + try { + assertEquals(Response.Status.Family.SUCCESSFUL, response.getStatusInfo().getFamily()); + } catch (AssertionError ex) { + throw new AssertionError("unexpected response code " + response.getStatus() + ", body is:\n" + response.readEntity(String.class), ex); + } + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/admin/UserTest.java new file mode 100755 index 000000000000..575aeb8672c5 --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -0,0 +1,3646 @@ +/* + * 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.admin; + +import jakarta.mail.internet.MimeMessage; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.*; +import org.keycloak.TokenVerifier; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.*; +import org.keycloak.common.VerificationException; +import org.keycloak.common.util.Base64; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.ObjectUtil; +import org.keycloak.credential.CredentialModel; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; +import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory; +import org.keycloak.models.Constants; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.UserModel; +import org.keycloak.models.credential.OTPCredentialModel; +import org.keycloak.models.credential.PasswordCredentialModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.StripSecretsUtils; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.*; +import org.keycloak.representations.userprofile.config.UPAttribute; +import org.keycloak.representations.userprofile.config.UPAttributePermissions; +import org.keycloak.representations.userprofile.config.UPConfig; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.testsuite.KeycloakTest; +import org.keycloak.testsuite.events.TestEventsListenerProviderFactory; +import org.keycloak.userprofile.DefaultAttributes; +import org.keycloak.userprofile.validator.UsernameProhibitedCharactersValidator; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED; +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; + +/** + * @author Stian Thorgersen + */ +public class UserTest extends KeycloakTest { + + protected static final String REALM_NAME = "admin-client-test"; + + protected RealmResource realm; + + protected Set managedAttributes = new HashSet<>(); + + { + managedAttributes.add("test"); + managedAttributes.add("attr"); + managedAttributes.add("attr1"); + managedAttributes.add("attr2"); + managedAttributes.add("attr3"); + managedAttributes.add("foo"); + managedAttributes.add("bar"); + managedAttributes.add("phoneNumber"); + managedAttributes.add("usercertificate"); + managedAttributes.add("saml.persistent.name.id.for.foo"); + managedAttributes.add(LDAPConstants.LDAP_ID); + managedAttributes.add("LDap_Id"); + managedAttributes.add("deniedSomeAdmin"); + + for (int i = 1; i < 10; i++) { + managedAttributes.add("test" + i); + } + } + + @BeforeEach + public void beforeUserTest() throws IOException { + realm = adminClient.realm(REALM_NAME); + + setUserProfileConfiguration(realm, null); + UPConfig upConfig = realm.users().userProfile().getConfiguration(); + + for (String name : managedAttributes) { + upConfig.addOrReplaceAttribute(createAttributeMetadata(name)); + } + + setUserProfileConfiguration(realm, JsonSerialization.writeValueAsString(upConfig)); + } + + @AfterEach + public void after() { + realm.identityProviders().findAll() + .forEach(ip -> realm.identityProviders().get(ip.getAlias()).remove()); + + realm.groups().groups() + .forEach(g -> realm.groups().group(g.getId()).remove()); + } + + @Override + public void addTestRealms(List testRealms) { + //super.addTestRealms(testRealms); + + RealmRepresentation adminRealmRep = new RealmRepresentation(); + adminRealmRep.setId(REALM_NAME); + adminRealmRep.setRealm(REALM_NAME); + adminRealmRep.setEnabled(true); + Map config = new HashMap<>(); + config.put("from", "auto@keycloak.org"); + config.put("host", "localhost"); + config.put("port", "3025"); + adminRealmRep.setSmtpServer(config); + + List eventListeners = new ArrayList<>(); + eventListeners.add(JBossLoggingEventListenerProviderFactory.ID); + eventListeners.add(TestEventsListenerProviderFactory.PROVIDER_ID); + adminRealmRep.setEventsListeners(eventListeners); + + testRealms.add(adminRealmRep); + } + + public String createUser() { + return createUser("user1", "user1@localhost"); + } + + public String createUser(String username, String email) { + UserRepresentation user = new UserRepresentation(); + user.setUsername(username); + user.setEmail(email); + user.setRequiredActions(Collections.emptyList()); + user.setEnabled(true); + + return createUser(user); + } + + private String createUser(UserRepresentation userRep) { + return createUser(userRep, true); + } + + private String createUser(UserRepresentation userRep, boolean assertAdminEvent) { + final String createdId; + try (Response response = realm.users().create(userRep)) { + createdId = ApiUtil.getCreatedId(response); + } + Assertions.assertNotNull(createdId); + StripSecretsUtils.strip(userRep); + + /*if (assertAdminEvent) { + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userResourcePath(createdId), userRep, + ResourceType.USER); + }*/ + + //getCleanup().addUserId(createdId); + + return createdId; + } + + /*private void updateUser(UserResource user, UserRepresentation userRep) { + user.update(userRep); + List credentials = userRep.getCredentials(); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.userResourcePath(userRep.getId()), StripSecretsUtils.strip(userRep), ResourceType.USER); + userRep.setCredentials(credentials); + }*/ + + @Test + public void verifyCreateUser() { + createUser(); + } + + /** + * See KEYCLOAK-11003 + */ + @Test + public void createUserWithTemporaryPasswordWithAdditionalPasswordUpdateShouldRemoveUpdatePasswordRequiredAction() { + + String userId = createUser(); + + CredentialRepresentation credTmp = new CredentialRepresentation(); + credTmp.setType(CredentialRepresentation.PASSWORD); + credTmp.setValue("temp"); + credTmp.setTemporary(Boolean.TRUE); + + realm.users().get(userId).resetPassword(credTmp); + + CredentialRepresentation credPerm = new CredentialRepresentation(); + credPerm.setType(CredentialRepresentation.PASSWORD); + credPerm.setValue("perm"); + credPerm.setTemporary(null); + + realm.users().get(userId).resetPassword(credPerm); + + UserRepresentation userRep = realm.users().get(userId).toRepresentation(); + + Assertions.assertFalse(userRep.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_PASSWORD.name())); + } + + @Test + public void createDuplicatedUser1() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user1"); + try (Response response = realm.users().create(user)) { + Assertions.assertEquals(409, response.getStatus()); + //assertAdminEvents.assertEmpty(); + + // Just to show how to retrieve underlying error message + ErrorRepresentation error = response.readEntity(ErrorRepresentation.class); + Assertions.assertEquals("User exists with same username", error.getErrorMessage()); + } + } + + /*@Test + public void createDuplicatedUser2() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user2"); + user.setEmail("user1@localhost"); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + assertAdminEvents.assertEmpty(); + + ErrorRepresentation error = response.readEntity(ErrorRepresentation.class); + Assert.assertEquals("User exists with same email", error.getErrorMessage()); + } + } + + //KEYCLOAK-14611 + @Test + public void createDuplicateEmailWithExistingDuplicates() { + //Allow duplicate emails + RealmRepresentation rep = realm.toRepresentation(); + rep.setDuplicateEmailsAllowed(true); + realm.update(rep); + + //Create 2 users with the same email + UserRepresentation user = new UserRepresentation(); + user.setEmail("user1@localhost"); + user.setUsername("user1"); + createUser(user, false); + user.setUsername("user2"); + createUser(user, false); + + //Disallow duplicate emails + rep.setDuplicateEmailsAllowed(false); + realm.update(rep); + + //Create a third user with the same email + user.setUsername("user3"); + assertAdminEvents.clear(); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + ErrorRepresentation error = response.readEntity(ErrorRepresentation.class); + Assert.assertEquals("User exists with same email", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createUserWithHashedCredentials() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_creds"); + user.setEmail("email@localhost"); + + PasswordCredentialModel pcm = PasswordCredentialModel.createFromValues("my-algorithm", "theSalt".getBytes(), 22, "ABC"); + CredentialRepresentation hashedPassword = ModelToRepresentation.toRepresentation(pcm); + hashedPassword.setCreatedDate(1001L); + hashedPassword.setUserLabel("deviceX"); + hashedPassword.setType(CredentialRepresentation.PASSWORD); + + user.setCredentials(Arrays.asList(hashedPassword)); + + createUser(user); + + CredentialModel credentialHashed = fetchCredentials("user_creds"); + PasswordCredentialModel pcmh = PasswordCredentialModel.createFromCredentialModel(credentialHashed); + assertNotNull("Expecting credential", credentialHashed); + assertEquals("my-algorithm", pcmh.getPasswordCredentialData().getAlgorithm()); + assertEquals(Long.valueOf(1001), credentialHashed.getCreatedDate()); + assertEquals("deviceX", credentialHashed.getUserLabel()); + assertEquals(22, pcmh.getPasswordCredentialData().getHashIterations()); + assertEquals("ABC", pcmh.getPasswordSecretData().getValue()); + assertEquals("theSalt", new String(pcmh.getPasswordSecretData().getSalt())); + assertEquals(CredentialRepresentation.PASSWORD, credentialHashed.getType()); + } + + + @Test + public void createUserWithDeprecatedCredentialsFormat() throws IOException { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_creds"); + user.setEmail("email@localhost"); + + PasswordCredentialModel pcm = PasswordCredentialModel.createFromValues("my-algorithm", "theSalt".getBytes(), 22, "ABC"); + //CredentialRepresentation hashedPassword = ModelToRepresentation.toRepresentation(pcm); + String deprecatedCredential = "{\n" + + " \"type\" : \"password\",\n" + + " \"hashedSaltedValue\" : \"" + pcm.getPasswordSecretData().getValue() + "\",\n" + + " \"salt\" : \"" + Base64.encodeBytes(pcm.getPasswordSecretData().getSalt()) + "\",\n" + + " \"hashIterations\" : " + pcm.getPasswordCredentialData().getHashIterations() + ",\n" + + " \"algorithm\" : \"" + pcm.getPasswordCredentialData().getAlgorithm() + "\"\n" + + " }"; + + CredentialRepresentation deprecatedHashedPassword = JsonSerialization.readValue(deprecatedCredential, CredentialRepresentation.class); + Assert.assertNotNull(deprecatedHashedPassword.getHashedSaltedValue()); + Assert.assertNull(deprecatedHashedPassword.getCredentialData()); + + deprecatedHashedPassword.setCreatedDate(1001l); + deprecatedHashedPassword.setUserLabel("deviceX"); + deprecatedHashedPassword.setType(CredentialRepresentation.PASSWORD); + + user.setCredentials(Arrays.asList(deprecatedHashedPassword)); + + createUser(user, false); + + CredentialModel credentialHashed = fetchCredentials("user_creds"); + PasswordCredentialModel pcmh = PasswordCredentialModel.createFromCredentialModel(credentialHashed); + assertNotNull("Expecting credential", credentialHashed); + assertEquals("my-algorithm", pcmh.getPasswordCredentialData().getAlgorithm()); + assertEquals(Long.valueOf(1001), credentialHashed.getCreatedDate()); + assertEquals("deviceX", credentialHashed.getUserLabel()); + assertEquals(22, pcmh.getPasswordCredentialData().getHashIterations()); + assertEquals("ABC", pcmh.getPasswordSecretData().getValue()); + assertEquals("theSalt", new String(pcmh.getPasswordSecretData().getSalt())); + assertEquals(CredentialRepresentation.PASSWORD, credentialHashed.getType()); + } + + @Test + public void updateUserWithHashedCredentials() { + String userId = createUser("user_hashed_creds", "user_hashed_creds@localhost"); + + byte[] salt = new byte[]{-69, 85, 87, 99, 26, -107, 125, 99, -77, 30, -111, 118, 108, 100, -117, -56}; + + PasswordCredentialModel credentialModel = PasswordCredentialModel.createFromValues("pbkdf2-sha256", salt, + 27500, "uskEPZWMr83pl2mzNB95SFXfIabe2UH9ClENVx/rrQqOjFEjL2aAOGpWsFNNF3qoll7Qht2mY5KxIDm3Rnve2w=="); + credentialModel.setCreatedDate(1001l); + CredentialRepresentation hashedPassword = ModelToRepresentation.toRepresentation(credentialModel); + + UserRepresentation userRepresentation = new UserRepresentation(); + userRepresentation.setCredentials(Collections.singletonList(hashedPassword)); + + realm.users().get(userId).update(userRepresentation); + + oauth.realm(REALM_NAME); + driver.navigate().to(oauth.getLoginFormUrl()); + + assertEquals("Sign in to your account", PageUtils.getPageTitle(driver)); + + loginPage.login("user_hashed_creds", "admin"); + + assertTrue(driver.getTitle().contains("AUTH_RESPONSE")); + + // oauth cleanup + oauth.realm("test"); + } + + @Test + public void createUserWithTempolaryCredentials() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_temppw"); + user.setEmail("email.temppw@localhost"); + + CredentialRepresentation password = new CredentialRepresentation(); + password.setValue("password"); + password.setType(CredentialRepresentation.PASSWORD); + password.setTemporary(true); + user.setCredentials(Arrays.asList(password)); + + String userId = createUser(user); + + UserRepresentation userRep = realm.users().get(userId).toRepresentation(); + Assert.assertEquals(1, userRep.getRequiredActions().size()); + Assert.assertEquals(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), userRep.getRequiredActions().get(0)); + } + + @Test + public void createUserWithRawCredentials() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_rawpw"); + user.setEmail("email.raw@localhost"); + + CredentialRepresentation rawPassword = new CredentialRepresentation(); + rawPassword.setValue("ABCD"); + rawPassword.setType(CredentialRepresentation.PASSWORD); + user.setCredentials(Arrays.asList(rawPassword)); + + createUser(user); + + CredentialModel credential = fetchCredentials("user_rawpw"); + assertNotNull("Expecting credential", credential); + PasswordCredentialModel pcm = PasswordCredentialModel.createFromCredentialModel(credential); + assertEquals(PasswordPolicy.HASH_ALGORITHM_DEFAULT, pcm.getPasswordCredentialData().getAlgorithm()); + assertEquals(PasswordPolicy.HASH_ITERATIONS_DEFAULT, pcm.getPasswordCredentialData().getHashIterations()); + assertNotEquals("ABCD", pcm.getPasswordSecretData().getValue()); + assertEquals(CredentialRepresentation.PASSWORD, credential.getType()); + } + + private CredentialModel fetchCredentials(String username) { + return getTestingClient().server(REALM_NAME).fetch(RunHelpers.fetchCredentials(username)); + } + + @Test + public void createDuplicatedUser3() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("User1"); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createDuplicatedUser4() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("USER1"); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createDuplicatedUser5() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user2"); + user.setEmail("User1@localhost"); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createDuplicatedUser6() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user2"); + user.setEmail("user1@LOCALHOST"); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createDuplicatedUser7() { + createUser("user1", "USer1@Localhost"); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user2"); + user.setEmail("user1@localhost"); + + try (Response response = realm.users().create(user)) { + assertEquals(409, response.getStatus()); + assertAdminEvents.assertEmpty(); + } + } + + // KEYCLOAK-7015 + @Test + public void createTwoUsersWithEmptyStringEmails() { + createUser("user1", ""); + createUser("user2", ""); + } + + @Test + public void createUserWithFederationLink() { + + // add a dummy federation provider + ComponentRepresentation dummyFederationProvider = new ComponentRepresentation(); + String componentId = KeycloakModelUtils.generateId(); + dummyFederationProvider.setId(componentId); + dummyFederationProvider.setName(DummyUserFederationProviderFactory.PROVIDER_NAME); + dummyFederationProvider.setProviderId(DummyUserFederationProviderFactory.PROVIDER_NAME); + dummyFederationProvider.setProviderType(UserStorageProvider.class.getName()); + adminClient.realms().realm(REALM_NAME).components().add(dummyFederationProvider); + + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.componentPath(componentId), dummyFederationProvider, ResourceType.COMPONENT); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user1"); + user.setEmail("user1@localhost"); + user.setFederationLink(componentId); + + String userId = createUser(user); + + // fetch user again and see federation link filled in + UserRepresentation createdUser = realm.users().get(userId).toRepresentation(); + assertNotNull(createdUser); + assertEquals(user.getFederationLink(), createdUser.getFederationLink()); + } + + @Test + public void createUserWithoutUsername() { + UserRepresentation user = new UserRepresentation(); + user.setEmail("user1@localhost"); + + try (Response response = realm.users().create(user)) { + assertEquals(400, response.getStatus()); + ErrorRepresentation error = response.readEntity(ErrorRepresentation.class); + Assert.assertEquals("User name is missing", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createUserWithEmailAsUsername() { + RealmRepresentation realmRep = realm.toRepresentation(); + Boolean registrationEmailAsUsername = realmRep.isRegistrationEmailAsUsername(); + Boolean editUsernameAllowed = realmRep.isEditUsernameAllowed(); + getCleanup().addCleanup(() -> { + realmRep.setRegistrationEmailAsUsername(registrationEmailAsUsername); + realm.update(realmRep); + }); + getCleanup().addCleanup(() -> { + realmRep.setEditUsernameAllowed(editUsernameAllowed); + realm.update(realmRep); + }); + + switchRegistrationEmailAsUsername(true); + switchEditUsernameAllowedOn(false); + String id = createUser(); + UserResource user = realm.users().get(id); + UserRepresentation userRep = user.toRepresentation(); + assertEquals("user1@localhost", userRep.getEmail()); + assertEquals(userRep.getEmail(), userRep.getUsername()); + deleteUser(id); + + switchRegistrationEmailAsUsername(true); + switchEditUsernameAllowedOn(true); + id = createUser(); + user = realm.users().get(id); + userRep = user.toRepresentation(); + assertEquals("user1@localhost", userRep.getEmail()); + assertEquals(userRep.getEmail(), userRep.getUsername()); + deleteUser(id); + + switchRegistrationEmailAsUsername(false); + switchEditUsernameAllowedOn(true); + id = createUser(); + user = realm.users().get(id); + userRep = user.toRepresentation(); + assertEquals("user1", userRep.getUsername()); + assertEquals("user1@localhost", userRep.getEmail()); + deleteUser(id); + + switchRegistrationEmailAsUsername(false); + switchEditUsernameAllowedOn(false); + id = createUser(); + user = realm.users().get(id); + userRep = user.toRepresentation(); + assertEquals("user1", userRep.getUsername()); + assertEquals("user1@localhost", userRep.getEmail()); + } + + private void deleteUser(String id) { + try (Response response = realm.users().delete(id)) { + assertEquals(204, response.getStatus()); + } + assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.userResourcePath(id), ResourceType.USER); + } + + @Test + public void createUserWithEmptyUsername() { + UserRepresentation user = new UserRepresentation(); + user.setUsername(""); + user.setEmail("user2@localhost"); + + try (Response response = realm.users().create(user)) { + assertEquals(400, response.getStatus()); + ErrorRepresentation error = response.readEntity(ErrorRepresentation.class); + Assert.assertEquals("User name is missing", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void createUserWithInvalidPolicyPassword() { + RealmRepresentation rep = realm.toRepresentation(); + String passwordPolicy = rep.getPasswordPolicy(); + rep.setPasswordPolicy("length(8)"); + realm.update(rep); + UserRepresentation user = new UserRepresentation(); + user.setUsername("user4"); + user.setEmail("user4@localhost"); + CredentialRepresentation rawPassword = new CredentialRepresentation(); + rawPassword.setValue("ABCD"); + rawPassword.setType(CredentialRepresentation.PASSWORD); + user.setCredentials(Collections.singletonList(rawPassword)); + assertAdminEvents.clear(); + + try (Response response = realm.users().create(user)) { + assertEquals(400, response.getStatus()); + ErrorRepresentation error = response.readEntity(ErrorRepresentation.class); + Assert.assertEquals("Password policy not met", error.getErrorMessage()); + rep.setPasswordPolicy(passwordPolicy); + assertAdminEvents.assertEmpty(); + realm.update(rep); + } + } + + @Test + public void createUserWithCreateTimestamp() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user1"); + user.setEmail("user1@localhost"); + Long createdTimestamp = 1695238476L; + user.setCreatedTimestamp(createdTimestamp); + + String userId = createUser(user); + + // fetch user again and see created timestamp filled in + UserRepresentation createdUser = realm.users().get(userId).toRepresentation(); + assertNotNull(createdUser); + assertEquals(user.getCreatedTimestamp(), createdUser.getCreatedTimestamp()); + } + + private List createUsers() { + List ids = new ArrayList<>(); + + for (int i = 1; i < 10; i++) { + UserRepresentation user = new UserRepresentation(); + user.setUsername("username" + i); + user.setEmail("user" + i + "@localhost"); + user.setFirstName("First" + i); + user.setLastName("Last" + i); + + addAttribute(user, "test", Collections.singletonList("test" + i)); + addAttribute(user, "test" + i, Collections.singletonList("test" + i)); + addAttribute(user, "attr", Collections.singletonList("common")); + + ids.add(createUser(user)); + } + + return ids; + } + + private void addAttribute(UserRepresentation user, String name, List values) { + Map> attributes = Optional.ofNullable(user.getAttributes()).orElse(new HashMap<>()); + + attributes.put(name, values); + managedAttributes.add(name); + + user.setAttributes(attributes); + } + + @Test + public void countByAttribute() { + createUsers(); + + Map attributes = new HashMap<>(); + attributes.put("test1", "test2"); + assertThat(realm.users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(0)); + + attributes = new HashMap<>(); + attributes.put("test", "test1"); + assertThat(realm.users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(1)); + + attributes = new HashMap<>(); + attributes.put("test", "test2"); + attributes.put("attr", "common"); + assertThat(realm.users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(1)); + + attributes = new HashMap<>(); + attributes.put("attr", "common"); + assertThat(realm.users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(9)); + } + + @Test + public void countUsersByEnabledFilter() { + + // create 2 enabled and 1 disabled user + UserRepresentation enabledUser1 = new UserRepresentation(); + enabledUser1.setUsername("enabled1"); + enabledUser1.setEmail("enabled1@enabledfilter.com"); + enabledUser1.setEnabled(true); + createUser(enabledUser1); + + UserRepresentation enabledUser2 = new UserRepresentation(); + enabledUser2.setUsername("enabled2"); + enabledUser2.setEmail("enabled2@enabledfilter.com"); + enabledUser2.setEnabled(true); + createUser(enabledUser2); + + UserRepresentation disabledUser1 = new UserRepresentation(); + disabledUser1.setUsername("disabled1"); + disabledUser1.setEmail("disabled1@enabledfilter.com"); + disabledUser1.setEnabled(false); + createUser(disabledUser1); + + Boolean enabled = true; + Boolean disabled = false; + + // count all users with @enabledfilter.com + assertThat(realm.users().count(null, null, null, "@enabledfilter.com", null, null, null, null), is(3)); + + // count users that are enabled and have username enabled1 + assertThat(realm.users().count(null, null, null, "@enabledfilter.com", null, "enabled1", enabled, null),is(1)); + + // count users that are disabled + assertThat(realm.users().count(null, null, null, "@enabledfilter.com", null, null, disabled, null), is(1)); + + // count users that are enabled + assertThat(realm.users().count(null, null, null, "@enabledfilter.com", null, null, enabled, null), is(2)); + } + + @Test + public void searchByEmail() { + createUsers(); + + List users = realm.users().search(null, null, null, "user1@localhost", null, null); + assertEquals(1, users.size()); + + users = realm.users().search(null, null, null, "@localhost", null, null); + assertEquals(9, users.size()); + } + + @Test + public void searchByEmailExactMatch() { + createUsers(); + List users = realm.users().searchByEmail("user1@localhost", true); + assertEquals(1, users.size()); + + users = realm.users().search("@localhost", true); + assertEquals(0, users.size()); + } + + @Test + public void searchByUsername() { + createUsers(); + + List users = realm.users().search("username1", null, null, null, null, null); + assertEquals(1, users.size()); + + users = realm.users().search("user", null, null, null, null, null); + assertEquals(9, users.size()); + } + + private String mapToSearchQuery(Map search) { + return search.entrySet() + .stream() + .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) + .collect(Collectors.joining(" ")); + } + + @Test + public void searchByAttribute() { + createUsers(); + + Map attributes = new HashMap<>(); + attributes.put("test", "test1"); + List users = realm.users().searchByAttributes(mapToSearchQuery(attributes)); + assertEquals(1, users.size()); + + attributes.clear(); + attributes.put("attr", "common"); + + users = realm.users().searchByAttributes(mapToSearchQuery(attributes)); + assertEquals(9, users.size()); + + attributes.clear(); + attributes.put("x", "common"); + users = realm.users().searchByAttributes(mapToSearchQuery(attributes)); + assertEquals(0, users.size()); + } + + @Test + public void searchByMultipleAttributes() { + createUsers(); + + Map attributes = new HashMap<>(); + attributes.put("test", "test1"); + attributes.put("attr", "common"); + attributes.put("test1", "test1"); + + List users = realm.users().searchByAttributes(mapToSearchQuery(attributes)); + assertEquals(1, users.size()); + } + + @Test + public void searchByAttributesWithPagination() { + createUsers(); + + Map attributes = new HashMap<>(); + attributes.put("attr", "common"); + for (int i = 1; i < 10; i++) { + List users = realm.users().searchByAttributes(i - 1, 1, null, false, mapToSearchQuery(attributes)); + assertEquals(1, users.size()); + assertTrue(users.get(0).getAttributes().keySet().stream().anyMatch(attributes::containsKey)); + } + } + + @Test + public void storeAndReadUserWithLongAttributeValue() { + String longValue = RandomStringUtils.random(Integer.parseInt(DefaultAttributes.DEFAULT_MAX_LENGTH_ATTRIBUTES), true, true); + + getCleanup().addUserId(createUser(REALM_NAME, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com", + user -> user.setAttributes(Map.of("attr", List.of(longValue))))); + + List users = realm.users().search("user1", true); + + assertThat(users, hasSize(1)); + assertThat(users.get(0).getAttributes().get("attr").get(0), equalTo(longValue)); + + WebApplicationException ex = assertThrows(WebApplicationException.class, () -> getCleanup().addUserId(createUser(REALM_NAME, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com", + user -> user.setAttributes(Map.of("attr", List.of(longValue + "a")))))); + assertThat(ex.getResponse().getStatusInfo().getStatusCode(), equalTo(400)); + assertThat(ex.getResponse().readEntity(ErrorRepresentation.class).getErrorMessage(), equalTo("error-invalid-length")); + } + + @Test + public void searchByLongAttributes() { + // random string with suffix that makes it case-sensitive and distinct + String longValue = RandomStringUtils.random(Integer.parseInt(DefaultAttributes.DEFAULT_MAX_LENGTH_ATTRIBUTES) - 1, true, true) + "u"; + String longValue2 = RandomStringUtils.random(Integer.parseInt(DefaultAttributes.DEFAULT_MAX_LENGTH_ATTRIBUTES) - 1, true, true) + "v"; + + getCleanup().addUserId(createUser(REALM_NAME, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com", + user -> user.setAttributes(Map.of("test1", List.of(longValue, "v2"), "test2", List.of("v2"))))); + getCleanup().addUserId(createUser(REALM_NAME, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com", + user -> user.setAttributes(Map.of("test1", List.of(longValue, "v2"), "test2", List.of(longValue2))))); + getCleanup().addUserId(createUser(REALM_NAME, "user3", "password", "user3FirstName", "user3LastName", "user3@example.com", + user -> user.setAttributes(Map.of("test2", List.of(longValue, "v3"), "test4", List.of("v4"))))); + + assertThat(realm.users().searchByAttributes(mapToSearchQuery(Map.of("test1", longValue))).stream().map(UserRepresentation::getUsername).collect(Collectors.toList()), + containsInAnyOrder("user1", "user2")); + assertThat(realm.users().searchByAttributes(mapToSearchQuery(Map.of("test1", longValue, "test2", longValue2))).stream().map(UserRepresentation::getUsername).collect(Collectors.toList()), + contains("user2")); + + //case-insensitive search + assertThat(realm.users().searchByAttributes(mapToSearchQuery(Map.of("test1", longValue, "test2", longValue2.toLowerCase(Locale.ENGLISH)))).stream().map(UserRepresentation::getUsername).collect(Collectors.toList()), + contains("user2")); + } + + @Test + public void searchByUsernameExactMatch() { + createUsers(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("username11"); + + createUser(user); + + List users = realm.users().search("username1", true); + assertEquals(1, users.size()); + + users = realm.users().searchByUsername("username1", true); + assertEquals(1, users.size()); + + users = realm.users().search("user", true); + assertEquals(0, users.size()); + } + + @Test + public void searchByFirstNameExact() { + createUsers(); + List users = realm.users().searchByFirstName("First1", true); + assertEquals(1, users.size()); + } + + @Test + public void searchByLastNameExact() { + createUsers(); + List users = realm.users().searchByLastName("Last1", true); + assertEquals(1, users.size()); + } + + @Test + public void searchByFirstNameNullForLastName() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user1"); + user.setFirstName("Erik"); + user.setRequiredActions(Collections.emptyList()); + user.setEnabled(true); + + createUser(user); + + List users = realm.users().search("Erik", 0, 50); + assertEquals(1, users.size()); + } + + @Test + public void searchByLastNameNullForFirstName() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user1"); + user.setLastName("de Wit"); + user.setRequiredActions(Collections.emptyList()); + user.setEnabled(true); + + createUser(user); + + List users = realm.users().search("*wit*", null, null); + assertEquals(1, users.size()); + } + + @Test + public void searchByEnabled() { + String userCommonName = "enabled-disabled-user"; + + UserRepresentation user1 = new UserRepresentation(); + user1.setUsername(userCommonName + "1"); + user1.setRequiredActions(Collections.emptyList()); + user1.setEnabled(true); + createUser(user1); + + UserRepresentation user2 = new UserRepresentation(); + user2.setUsername(userCommonName + "2"); + user2.setRequiredActions(Collections.emptyList()); + user2.setEnabled(false); + createUser(user2); + + List enabledUsers = realm.users().search(null, null, null, null, null, null, true, false); + assertEquals(1, enabledUsers.size()); + + List enabledUsersWithFilter = realm.users().search(userCommonName, null, null, null, null, null, true, true); + assertEquals(1, enabledUsersWithFilter.size()); + assertEquals(user1.getUsername(), enabledUsersWithFilter.get(0).getUsername()); + + List disabledUsers = realm.users().search(userCommonName, null, null, null, null, null, false, false); + assertEquals(1, disabledUsers.size()); + assertEquals(user2.getUsername(), disabledUsers.get(0).getUsername()); + + List allUsers = realm.users().search(userCommonName, null, null, null, 0, 100, null, true); + assertEquals(2, allUsers.size()); + } + + @Test + public void searchWithFilters() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user2"); + user.setFirstName("First"); + user.setLastName("Last"); + user.setEmail("user2@localhost"); + user.setRequiredActions(Collections.emptyList()); + user.setEnabled(false); + createUser(user); + + List searchFirstNameAndDisabled = realm.users().search(null, "First", null, null, null, null, false, true); + assertEquals(1, searchFirstNameAndDisabled.size()); + assertEquals(user.getUsername(), searchFirstNameAndDisabled.get(0).getUsername()); + + List searchLastNameAndEnabled = realm.users().search(null, null, "Last", null, null, null, true, false); + assertEquals(0, searchLastNameAndEnabled.size()); + + List searchEmailAndDisabled = realm.users().search(null, null, null, "user2@localhost", 0, 50, false, true); + assertEquals(1, searchEmailAndDisabled.size()); + assertEquals(user.getUsername(), searchEmailAndDisabled.get(0).getUsername()); + + List searchInvalidSizeAndDisabled = realm.users().search(null, null, null, null, 10, 20, null, false); + assertEquals(0, searchInvalidSizeAndDisabled.size()); + } + + @Test + public void searchByIdp() { + // Add user without IDP + createUser(); + + // add sample Identity Providers + final String identityProviderAlias1 = "identity-provider-alias1"; + addSampleIdentityProvider(identityProviderAlias1, 0); + final String identityProviderAlias2 = "identity-provider-alias2"; + addSampleIdentityProvider(identityProviderAlias2, 1); + + final String commonIdpUserId = "commonIdpUserId"; + + // create first IDP1 User with link + final String idp1User1Username = "idp1user1"; + final String idp1User1KeycloakId = createUser(idp1User1Username, "idp1user1@localhost"); + final String idp1User1UserId = "idp1user1Id"; + FederatedIdentityRepresentation link1_1 = new FederatedIdentityRepresentation(); + link1_1.setUserId(idp1User1UserId); + link1_1.setUserName(idp1User1Username); + addFederatedIdentity(idp1User1KeycloakId, identityProviderAlias1, link1_1); + + // create second IDP1 User with link + final String idp1User2Username = "idp1user2"; + final String idp1User2KeycloakId = createUser(idp1User2Username, "idp1user2@localhost"); + FederatedIdentityRepresentation link1_2 = new FederatedIdentityRepresentation(); + link1_2.setUserId(commonIdpUserId); + link1_2.setUserName(idp1User2Username); + addFederatedIdentity(idp1User2KeycloakId, identityProviderAlias1, link1_2); + + // create IDP2 user with link + final String idp2UserUsername = "idp2user"; + final String idp2UserKeycloakId = createUser(idp2UserUsername, "idp2user@localhost"); + FederatedIdentityRepresentation link2 = new FederatedIdentityRepresentation(); + link2.setUserId(commonIdpUserId); + link2.setUserName(idp2UserUsername); + addFederatedIdentity(idp2UserKeycloakId, identityProviderAlias2, link2); + + // run search tests + List searchForAllUsers = + realm.users().search(null, null, null, null, null, null, null, null, null, null, null); + assertEquals(4, searchForAllUsers.size()); + + List searchByIdpAlias = + realm.users().search(null, null, null, null, null, identityProviderAlias1, null, null, null, null, + null); + assertEquals(2, searchByIdpAlias.size()); + assertEquals(idp1User1Username, searchByIdpAlias.get(0).getUsername()); + assertEquals(idp1User2Username, searchByIdpAlias.get(1).getUsername()); + + List searchByIdpUserId = + realm.users().search(null, null, null, null, null, null, commonIdpUserId, null, null, null, null); + assertEquals(2, searchByIdpUserId.size()); + assertEquals(idp1User2Username, searchByIdpUserId.get(0).getUsername()); + assertEquals(idp2UserUsername, searchByIdpUserId.get(1).getUsername()); + + List searchByIdpAliasAndUserId = + realm.users().search(null, null, null, null, null, identityProviderAlias1, idp1User1UserId, null, null, + null, + null); + assertEquals(1, searchByIdpAliasAndUserId.size()); + assertEquals(idp1User1Username, searchByIdpAliasAndUserId.get(0).getUsername()); + } + + private void addFederatedIdentity(String keycloakUserId, String identityProviderAlias1, + FederatedIdentityRepresentation link) { + Response response1 = realm.users().get(keycloakUserId).addFederatedIdentity(identityProviderAlias1, link); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, + AdminEventPaths.userFederatedIdentityLink(keycloakUserId, identityProviderAlias1), link, + ResourceType.USER); + assertEquals(204, response1.getStatus()); + } + + @Test + public void searchByIdpAndEnabled() { + // add sample Identity Provider + final String identityProviderAlias = "identity-provider-alias"; + addSampleIdentityProvider(identityProviderAlias, 0); + + // add disabled user with IDP link + UserRepresentation disabledUser = new UserRepresentation(); + final String disabledUsername = "disabled_username"; + disabledUser.setUsername(disabledUsername); + disabledUser.setEmail("disabled@localhost"); + disabledUser.setEnabled(false); + final String disabledUserKeycloakId = createUser(disabledUser); + FederatedIdentityRepresentation disabledUserLink = new FederatedIdentityRepresentation(); + final String disabledUserId = "disabledUserId"; + disabledUserLink.setUserId(disabledUserId); + disabledUserLink.setUserName(disabledUsername); + addFederatedIdentity(disabledUserKeycloakId, identityProviderAlias, disabledUserLink); + + // add enabled user with IDP link + UserRepresentation enabledUser = new UserRepresentation(); + final String enabledUsername = "enabled_username"; + enabledUser.setUsername(enabledUsername); + enabledUser.setEmail("enabled@localhost"); + enabledUser.setEnabled(true); + final String enabledUserKeycloakId = createUser(enabledUser); + FederatedIdentityRepresentation enabledUserLink = new FederatedIdentityRepresentation(); + final String enabledUserId = "enabledUserId"; + enabledUserLink.setUserId(enabledUserId); + enabledUserLink.setUserName(enabledUsername); + addFederatedIdentity(enabledUserKeycloakId, identityProviderAlias, enabledUserLink); + + // run search tests + List searchByIdpAliasAndEnabled = + realm.users().search(null, null, null, null, null, identityProviderAlias, null, null, null, true, null); + assertEquals(1, searchByIdpAliasAndEnabled.size()); + assertEquals(enabledUsername, searchByIdpAliasAndEnabled.get(0).getUsername()); + + List searchByIdpAliasAndDisabled = + realm.users().search(null, null, null, null, null, identityProviderAlias, null, null, null, false, + null); + assertEquals(1, searchByIdpAliasAndDisabled.size()); + assertEquals(disabledUsername, searchByIdpAliasAndDisabled.get(0).getUsername()); + + List searchByIdpAliasWithoutEnabledFlag = + realm.users().search(null, null, null, null, null, identityProviderAlias, null, null, null, null, null); + assertEquals(2, searchByIdpAliasWithoutEnabledFlag.size()); + assertEquals(disabledUsername, searchByIdpAliasWithoutEnabledFlag.get(0).getUsername()); + assertEquals(enabledUsername, searchByIdpAliasWithoutEnabledFlag.get(1).getUsername()); + } + + @Test + public void searchById() { + String expectedUserId = createUsers().get(0); + List users = realm.users().search("id:" + expectedUserId, null, null); + + assertEquals(1, users.size()); + assertEquals(expectedUserId, users.get(0).getId()); + + users = realm.users().search("id: " + expectedUserId + " ", null, null); + + assertEquals(1, users.size()); + assertEquals(expectedUserId, users.get(0).getId()); + } + + @Test + public void infixSearch() { + List userIds = createUsers(); + + // Username search + List users = realm.users().search("*1*", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("*y*", null, null); + assertThat(users.size(), is(0)); + + users = realm.users().search("*name*", null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("**", null, null); + assertThat(users, hasSize(9)); + + // First/Last name search + users = realm.users().search("*first1*", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("*last*", null, null); + assertThat(users, hasSize(9)); + + // Email search + users = realm.users().search("*@localhost*", null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("*1@local*", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + } + + @Test + public void prefixSearch() { + List userIds = createUsers(); + + // Username search + List users = realm.users().search("user", null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("user*", null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("name", null, null); + assertThat(users, hasSize(0)); + + users = realm.users().search("name*", null, null); + assertThat(users, hasSize(0)); + + users = realm.users().search("username1", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("username1*", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search(null, null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("", null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("*", null, null); + assertThat(users, hasSize(9)); + + // First/Last name search + users = realm.users().search("first1", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("first1*", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("last", null, null); + assertThat(users, hasSize(9)); + + users = realm.users().search("last*", null, null); + assertThat(users, hasSize(9)); + + // Email search + users = realm.users().search("user1@local", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("user1@local*", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + } + + @Test + public void circumfixSearch() { + createUsers(); + + List users = realm.users().search("u*name", null, null); + assertThat(users, hasSize(9)); + } + + @Test + public void wildcardSearch() { + UserProfileResource upResource = realm.users().userProfile(); + UPConfig upConfig = upResource.getConfiguration(); + Map prohibitedCharsOrigCfg = upConfig.getAttribute(UserModel.USERNAME).getValidations().get(UsernameProhibitedCharactersValidator.ID); + upConfig.getAttribute(UserModel.USERNAME).getValidations().remove(UsernameProhibitedCharactersValidator.ID); + upResource.update(upConfig); + assertAdminEvents.clear(); + + try { + createUser("0user\\\\0", "email0@emal"); + createUser("1user\\\\", "email1@emal"); + createUser("2user\\\\%", "email2@emal"); + createUser("3user\\\\*", "email3@emal"); + createUser("4user\\\\_", "email4@emal"); + + assertThat(realm.users().search("*", null, null), hasSize(5)); + assertThat(realm.users().search("*user\\", null, null), hasSize(5)); + assertThat(realm.users().search("\"2user\\\\%\"", null, null), hasSize(1)); + } finally { + upConfig.getAttribute(UserModel.USERNAME).addValidation(UsernameProhibitedCharactersValidator.ID, prohibitedCharsOrigCfg); + upResource.update(upConfig); + } + } + + @Test + public void exactSearch() { + List userIds = createUsers(); + + // Username search + List users = realm.users().search("\"username1\"", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + users = realm.users().search("\"user\"", null, null); + assertThat(users, hasSize(0)); + + users = realm.users().search("\"\"", null, null); + assertThat(users, hasSize(0)); + + // First/Last name search + users = realm.users().search("\"first1\"", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + + // Email search + users = realm.users().search("\"user1@localhost\"", null, null); + assertThat(users, hasSize(1)); + assertThat(userIds.get(0), equalTo(users.get(0).getId())); + } + + @Test + public void searchWithExactMatch() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("test_username"); + user.setFirstName("test_first_name"); + user.setLastName("test_last_name"); + user.setEmail("test_email@test.com"); + user.setEnabled(true); + user.setEmailVerified(true); + createUser(user); + + UserRepresentation user2 = new UserRepresentation(); + user2.setUsername("test_username2"); + user2.setFirstName("test_first_name2"); + user2.setLastName("test_last_name"); + user2.setEmail("test_email@test.com2"); + user2.setEnabled(true); + user2.setEmailVerified(true); + createUser(user2); + + UserRepresentation user3 = new UserRepresentation(); + user3.setUsername("test_username3"); + user3.setFirstName("test_first_name"); + user3.setLastName("test_last_name3"); + user3.setEmail("test_email@test.com3"); + user3.setEnabled(true); + user3.setEmailVerified(true); + createUser(user3); + + List users = realm.users().search( + null, null, null, "test_email@test.co", + 0, 10, null, null, true + ); + assertEquals(0, users.size()); + users = realm.users().search( + null, null, null, "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(1, users.size()); + users = realm.users().search( + null, null, "test_last", "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(0, users.size()); + users = realm.users().search( + null, null, "test_last_name", "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(1, users.size()); + users = realm.users().search( + null, "test_first", "test_last_name", "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(0, users.size()); + users = realm.users().search( + null, "test_first_name", "test_last_name", "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(1, users.size()); + users = realm.users().search( + "test_usernam", "test_first_name", "test_last_name", "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(0, users.size()); + users = realm.users().search( + "test_username", "test_first_name", "test_last_name", "test_email@test.com", + 0, 10, null, null, true + ); + assertEquals(1, users.size()); + + users = realm.users().search( + null, null, "test_last_name", null, + 0, 10, null, null, true + ); + assertEquals(2, users.size()); + users = realm.users().search( + null, "test_first_name", null, null, + 0, 10, null, null, true + ); + assertEquals(2, users.size()); + } + + @Test + public void countUsersNotServiceAccount() { + createUsers(); + + Integer count = realm.users().count(); + assertEquals(9, count.intValue()); + + ClientRepresentation client = new ClientRepresentation(); + + client.setClientId("test-client"); + client.setPublicClient(false); + client.setSecret("secret"); + client.setServiceAccountsEnabled(true); + client.setEnabled(true); + client.setRedirectUris(Arrays.asList("http://url")); + + getAdminClient().realm(REALM_NAME).clients().create(client); + + // KEYCLOAK-5660, should not consider service accounts + assertEquals(9, realm.users().count().intValue()); + } + + @Test + public void delete() { + String userId = createUser(); + deleteUser(userId); + } + + @Test + public void deleteNonExistent() { + try (Response response = realm.users().delete("does-not-exist")) { + assertEquals(404, response.getStatus()); + } + assertAdminEvents.assertEmpty(); + } + + @Test + public void searchPaginated() { + createUsers(); + + List users = realm.users().search("username", 0, 1); + assertEquals(1, users.size()); + assertEquals("username1", users.get(0).getUsername()); + + users = realm.users().search("username", 5, 2); + assertEquals(2, users.size()); + assertEquals("username6", users.get(0).getUsername()); + assertEquals("username7", users.get(1).getUsername()); + + users = realm.users().search("username", 7, 20); + assertEquals(2, users.size()); + assertEquals("username8", users.get(0).getUsername()); + assertEquals("username9", users.get(1).getUsername()); + + users = realm.users().search("username", 0, 20); + assertEquals(9, users.size()); + } + + @Test + public void getFederatedIdentities() { + // Add sample identity provider + addSampleIdentityProvider(); + + // Add sample user + String id = createUser(); + UserResource user = realm.users().get(id); + assertEquals(0, user.getFederatedIdentity().size()); + + // Add social link to the user + FederatedIdentityRepresentation link = new FederatedIdentityRepresentation(); + link.setUserId("social-user-id"); + link.setUserName("social-username"); + addFederatedIdentity(id, "social-provider-id", link); + + // Verify social link is here + user = realm.users().get(id); + List federatedIdentities = user.getFederatedIdentity(); + assertEquals(1, federatedIdentities.size()); + link = federatedIdentities.get(0); + assertEquals("social-provider-id", link.getIdentityProvider()); + assertEquals("social-user-id", link.getUserId()); + assertEquals("social-username", link.getUserName()); + + // Remove social link now + user.removeFederatedIdentity("social-provider-id"); + assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.userFederatedIdentityLink(id, "social-provider-id"), ResourceType.USER); + assertEquals(0, user.getFederatedIdentity().size()); + + removeSampleIdentityProvider(); + } + + private void addSampleIdentityProvider() { + addSampleIdentityProvider("social-provider-id", 0); + } + + private void addSampleIdentityProvider(final String alias, final int expectedInitialIdpCount) { + List providers = realm.identityProviders().findAll(); + Assert.assertEquals(expectedInitialIdpCount, providers.size()); + + IdentityProviderRepresentation rep = new IdentityProviderRepresentation(); + rep.setAlias(alias); + rep.setProviderId("oidc"); + + realm.identityProviders().create(rep); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.identityProviderPath(rep.getAlias()), rep, ResourceType.IDENTITY_PROVIDER); + } + + private void removeSampleIdentityProvider() { + IdentityProviderResource resource = realm.identityProviders().get("social-provider-id"); + Assert.assertNotNull(resource); + resource.remove(); + assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.identityProviderPath("social-provider-id"), ResourceType.IDENTITY_PROVIDER); + } + + @Test + public void addRequiredAction() { + String id = createUser(); + + UserResource user = realm.users().get(id); + assertTrue(user.toRepresentation().getRequiredActions().isEmpty()); + + UserRepresentation userRep = user.toRepresentation(); + userRep.getRequiredActions().add("UPDATE_PASSWORD"); + updateUser(user, userRep); + + assertEquals(1, user.toRepresentation().getRequiredActions().size()); + assertEquals("UPDATE_PASSWORD", user.toRepresentation().getRequiredActions().get(0)); + } + + @Test + public void removeRequiredAction() { + String id = createUser(); + + UserResource user = realm.users().get(id); + assertTrue(user.toRepresentation().getRequiredActions().isEmpty()); + + UserRepresentation userRep = user.toRepresentation(); + userRep.getRequiredActions().add("UPDATE_PASSWORD"); + updateUser(user, userRep); + + user = realm.users().get(id); + userRep = user.toRepresentation(); + userRep.getRequiredActions().clear(); + updateUser(user, userRep); + + assertTrue(user.toRepresentation().getRequiredActions().isEmpty()); + } + + @Test + public void attributes() { + UserRepresentation user1 = new UserRepresentation(); + user1.setUsername("user1"); + user1.singleAttribute("attr1", "value1user1"); + user1.singleAttribute("attr2", "value2user1"); + + String user1Id = createUser(user1); + + UserRepresentation user2 = new UserRepresentation(); + user2.setUsername("user2"); + user2.singleAttribute("attr1", "value1user2"); + List vals = new ArrayList<>(); + vals.add("value2user2"); + vals.add("value2user2_2"); + user2.getAttributes().put("attr2", vals); + + String user2Id = createUser(user2); + + user1 = realm.users().get(user1Id).toRepresentation(); + assertEquals(2, user1.getAttributes().size()); + assertAttributeValue("value1user1", user1.getAttributes().get("attr1")); + assertAttributeValue("value2user1", user1.getAttributes().get("attr2")); + + user2 = realm.users().get(user2Id).toRepresentation(); + assertEquals(2, user2.getAttributes().size()); + assertAttributeValue("value1user2", user2.getAttributes().get("attr1")); + vals = user2.getAttributes().get("attr2"); + assertEquals(2, vals.size()); + assertTrue(vals.contains("value2user2") && vals.contains("value2user2_2")); + + user1.singleAttribute("attr1", "value3user1"); + user1.singleAttribute("attr3", "value4user1"); + + updateUser(realm.users().get(user1Id), user1); + + user1 = realm.users().get(user1Id).toRepresentation(); + assertEquals(3, user1.getAttributes().size()); + assertAttributeValue("value3user1", user1.getAttributes().get("attr1")); + assertAttributeValue("value2user1", user1.getAttributes().get("attr2")); + assertAttributeValue("value4user1", user1.getAttributes().get("attr3")); + + user1.getAttributes().remove("attr1"); + updateUser(realm.users().get(user1Id), user1); + + user1 = realm.users().get(user1Id).toRepresentation(); + assertEquals(2, user1.getAttributes().size()); + assertAttributeValue("value2user1", user1.getAttributes().get("attr2")); + assertAttributeValue("value4user1", user1.getAttributes().get("attr3")); + + // null attributes should not remove attributes + user1.setAttributes(null); + updateUser(realm.users().get(user1Id), user1); + user1 = realm.users().get(user1Id).toRepresentation(); + assertNotNull(user1.getAttributes()); + assertEquals(2, user1.getAttributes().size()); + + // empty attributes should remove attributes + user1.setAttributes(Collections.emptyMap()); + updateUser(realm.users().get(user1Id), user1); + + user1 = realm.users().get(user1Id).toRepresentation(); + assertNull(user1.getAttributes()); + + Map> attributes = new HashMap<>(); + + attributes.put("foo", List.of("foo")); + attributes.put("bar", List.of("bar")); + + user1.setAttributes(attributes); + + realm.users().get(user1Id).update(user1); + user1 = realm.users().get(user1Id).toRepresentation(); + assertEquals(2, user1.getAttributes().size()); + + user1.getAttributes().remove("foo"); + + realm.users().get(user1Id).update(user1); + user1 = realm.users().get(user1Id).toRepresentation(); + assertEquals(1, user1.getAttributes().size()); + } + + @Test + public void updateUserWithReadOnlyAttributes() { + // Admin is able to update "usercertificate" attribute + UserRepresentation user1 = new UserRepresentation(); + user1.setUsername("user1"); + user1.singleAttribute("usercertificate", "foo1"); + String user1Id = createUser(user1); + user1 = realm.users().get(user1Id).toRepresentation(); + + // Update of the user should be rejected due adding the "denied" attribute LDAP_ID + try { + user1.singleAttribute("usercertificate", "foo"); + user1.singleAttribute("saml.persistent.name.id.for.foo", "bar"); + user1.singleAttribute(LDAPConstants.LDAP_ID, "baz"); + updateUser(realm.users().get(user1Id), user1); + Assert.fail("Not supposed to successfully update user"); + } catch (BadRequestException bre) { + // Expected + assertAdminEvents.assertEmpty(); + } + + // The same test as before, but with the case-sensitivity used + try { + user1.getAttributes().remove(LDAPConstants.LDAP_ID); + user1.singleAttribute("LDap_Id", "baz"); + updateUser(realm.users().get(user1Id), user1); + Assert.fail("Not supposed to successfully update user"); + } catch (BadRequestException bre) { + // Expected + assertAdminEvents.assertEmpty(); + } + + // Attribute "deniedSomeAdmin" was denied for administrator + try { + user1.getAttributes().remove("LDap_Id"); + user1.singleAttribute("deniedSomeAdmin", "baz"); + updateUser(realm.users().get(user1Id), user1); + Assert.fail("Not supposed to successfully update user"); + } catch (BadRequestException bre) { + // Expected + assertAdminEvents.assertEmpty(); + } + + // usercertificate and saml attribute are allowed by admin + user1.getAttributes().remove("deniedSomeAdmin"); + updateUser(realm.users().get(user1Id), user1); + + user1 = realm.users().get(user1Id).toRepresentation(); + assertEquals("foo", user1.getAttributes().get("usercertificate").get(0)); + assertEquals("bar", user1.getAttributes().get("saml.persistent.name.id.for.foo").get(0)); + assertFalse(user1.getAttributes().containsKey(LDAPConstants.LDAP_ID)); + } + + @Test + public void testImportUserWithNullAttribute() { + RealmRepresentation rep = loadJson(getClass().getResourceAsStream("/import/testrealm-user-null-attr.json"), RealmRepresentation.class); + + try (Creator c = Creator.create(adminClient, rep)) { + List users = c.resource().users().list(); + // there should be only one user + assertThat(users, hasSize(1)); + // test there are only 2 attributes imported from json file, attribute "key3" : [ null ] shoudn't be imported + assertThat(users.get(0).getAttributes().size(), equalTo(2)); + } + } + + private void assertAttributeValue(String expectedValue, List attrValues) { + assertEquals(1, attrValues.size()); + assertEquals(expectedValue, attrValues.get(0)); + } + + @Test + public void sendResetPasswordEmail() { + UserRepresentation userRep = new UserRepresentation(); + userRep.setUsername("user1"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + try { + user.executeActionsEmail(actions); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("User email missing", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + try { + userRep = user.toRepresentation(); + userRep.setEmail("user1@localhost"); + userRep.setEnabled(false); + updateUser(user, userRep); + + user.executeActionsEmail(actions); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("User is disabled", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + try { + userRep.setEnabled(true); + updateUser(user, userRep); + + user.executeActionsEmail(Arrays.asList( + UserModel.RequiredAction.UPDATE_PASSWORD.name(), + "invalid\"") + ); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Provided invalid required actions", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + + try { + user.executeActionsEmail( + "invalidClientId", + "invalidUri", + Collections.singletonList(UserModel.RequiredAction.UPDATE_PASSWORD.name()) + ); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Client doesn't exist", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void sendResetPasswordEmailSuccess() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail(actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + MailUtils.EmailBody body = MailUtils.getBody(message); + + assertTrue(body.getText().contains("Update Password")); + assertTrue(body.getText().contains("your Admin-client-test account")); + assertTrue(body.getText().contains("This link will expire within 12 hours")); + + assertTrue(body.getHtml().contains("Update Password")); + assertTrue(body.getHtml().contains("your Admin-client-test account")); + assertTrue(body.getHtml().contains("This link will expire within 12 hours")); + + String link = MailUtils.getPasswordResetEmailLink(body); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver)); + + driver.navigate().to(link); + + assertEquals("We are sorry...", PageUtils.getPageTitle(driver)); + } + + @Test + public void testEmailLinkBasedOnRealmFrontEndUrl() throws Exception { + try { + updateRealmFrontEndUrl(adminClient.realm("master"), suiteContext.getAuthServerInfo().getContextRoot().toString()); + String expectedFrontEndUrl = "https://mytestrealm"; + updateRealmFrontEndUrl(adminClient.realm(REALM_NAME), expectedFrontEndUrl); + + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep, false); + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail(actions); + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + MailUtils.EmailBody body = MailUtils.getBody(message); + String link = MailUtils.getPasswordResetEmailLink(body); + assertTrue(link.contains(expectedFrontEndUrl)); + } finally { + updateRealmFrontEndUrl(adminClient.realm("master"), null); + updateRealmFrontEndUrl(adminClient.realm(REALM_NAME), null); + } + } + + private void updateRealmFrontEndUrl(RealmResource realm, String url) throws Exception { + RealmRepresentation master = realm.toRepresentation(); + Map attributes = Optional.ofNullable(master.getAttributes()).orElse(new HashMap<>()); + + if (url == null) { + attributes.remove("frontendUrl"); + } else { + attributes.put("frontendUrl", url); + } + + realm.update(master); + reconnectAdminClient(); + this.realm = adminClient.realm(REALM_NAME); + } + + @Test + public void sendResetPasswordEmailWithCustomLifespan() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + + final int lifespan = (int) TimeUnit.HOURS.toSeconds(5); + user.executeActionsEmail(actions, lifespan); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + MailUtils.EmailBody body = MailUtils.getBody(message); + + assertTrue(body.getText().contains("Update Password")); + assertTrue(body.getText().contains("your Admin-client-test account")); + assertTrue(body.getText().contains("This link will expire within 5 hours")); + + assertTrue(body.getHtml().contains("Update Password")); + assertTrue(body.getHtml().contains("your Admin-client-test account")); + assertTrue(body.getHtml().contains("This link will expire within 5 hours")); + + String link = MailUtils.getPasswordResetEmailLink(body); + + String token = link.substring(link.indexOf("key=") + "key=".length()); + + try { + final AccessToken accessToken = TokenVerifier.create(token, AccessToken.class).getToken(); + assertThat(accessToken.getExp() - accessToken.getIat(), allOf(greaterThanOrEqualTo(lifespan - 1l), lessThanOrEqualTo(lifespan + 1l))); + } catch (VerificationException e) { + throw new IOException(e); + } + + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver)); + + driver.navigate().to(link); + + assertEquals("We are sorry...", PageUtils.getPageTitle(driver)); + } + + @Test + public void sendResetPasswordEmailSuccessTwoLinks() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail(actions); + user.executeActionsEmail(actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(2, greenMail.getReceivedMessages().length); + + int i = 1; + for (MimeMessage message : greenMail.getReceivedMessages()) { + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass" + i, "new-pass" + i); + i++; + + assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver)); + } + + for (MimeMessage message : greenMail.getReceivedMessages()) { + String link = MailUtils.getPasswordResetEmailLink(message); + driver.navigate().to(link); + errorPage.assertCurrent(); + } + } + + @Test + public void sendResetPasswordEmailSuccessTwoLinksReverse() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail(actions); + user.executeActionsEmail(actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(2, greenMail.getReceivedMessages().length); + + int i = 1; + for (int j = greenMail.getReceivedMessages().length - 1; j >= 0; j--) { + MimeMessage message = greenMail.getReceivedMessages()[j]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass" + i, "new-pass" + i); + i++; + + assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver)); + } + + for (MimeMessage message : greenMail.getReceivedMessages()) { + String link = MailUtils.getPasswordResetEmailLink(message); + driver.navigate().to(link); + errorPage.assertCurrent(); + } + } + + @Test + public void sendResetPasswordEmailSuccessLinkOpenDoesNotExpireWhenOpenedOnly() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail(actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + driver.manage().deleteAllCookies(); + driver.navigate().to("about:blank"); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver)); + } + + @Test + public void sendResetPasswordEmailSuccessTokenShortLifespan() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + final AtomicInteger originalValue = new AtomicInteger(); + + RealmRepresentation realmRep = realm.toRepresentation(); + originalValue.set(realmRep.getActionTokenGeneratedByAdminLifespan()); + realmRep.setActionTokenGeneratedByAdminLifespan(60); + realm.update(realmRep); + + try { + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail(actions); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + setTimeOffset(70); + + driver.navigate().to(link); + + errorPage.assertCurrent(); + assertEquals("Action expired.", errorPage.getError()); + } finally { + setTimeOffset(0); + + realmRep.setActionTokenGeneratedByAdminLifespan(originalValue.get()); + realm.update(realmRep); + } + } + + @Test + public void sendResetPasswordEmailSuccessWithRecycledAuthSession() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + + // The following block creates a client and requests updating password with redirect to this client. + // After clicking the link (starting a fresh auth session with client), the user goes away and sends the email + // with password reset again - now without the client - and attempts to complete the password reset. + { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId("myclient2"); + client.setRedirectUris(new LinkedList<>()); + client.getRedirectUris().add("http://myclient.com/*"); + client.setName("myclient2"); + client.setEnabled(true); + Response response = realm.clients().create(client); + String createdId = ApiUtil.getCreatedId(response); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT); + + user.executeActionsEmail("myclient2", "http://myclient.com/home.html", actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + } + + user.executeActionsEmail(actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(2, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[greenMail.getReceivedMessages().length - 1]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver)); + + driver.navigate().to(link); + + assertEquals("We are sorry...", PageUtils.getPageTitle(driver)); + } + + @Test + public void sendResetPasswordEmailWithRedirect() throws IOException { + + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + + ClientRepresentation client = new ClientRepresentation(); + client.setClientId("myclient"); + client.setRedirectUris(new LinkedList<>()); + client.getRedirectUris().add("http://myclient.com/*"); + client.setName("myclient"); + client.setEnabled(true); + Response response = realm.clients().create(client); + String createdId = ApiUtil.getCreatedId(response); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT); + + + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + + try { + // test that an invalid redirect uri is rejected. + user.executeActionsEmail("myclient", "http://unregistered-uri.com/", actions); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Invalid redirect uri.", error.getErrorMessage()); + } + + + user.executeActionsEmail("myclient", "http://myclient.com/home.html", actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", driver.findElement(By.id("kc-page-title")).getText()); + + String pageSource = driver.getPageSource(); + + // check to make sure the back link is set. + Assert.assertTrue(pageSource.contains("http://myclient.com/home.html")); + + driver.navigate().to(link); + + assertEquals("We are sorry...", PageUtils.getPageTitle(driver)); + } + + @Test + public void sendResetPasswordEmailWithRedirectAndCustomLifespan() throws IOException { + + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + + ClientRepresentation client = new ClientRepresentation(); + client.setClientId("myclient"); + client.setRedirectUris(new LinkedList<>()); + client.getRedirectUris().add("http://myclient.com/*"); + client.setName("myclient"); + client.setEnabled(true); + Response response = realm.clients().create(client); + String createdId = ApiUtil.getCreatedId(response); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT); + + + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + + final int lifespan = (int) TimeUnit.DAYS.toSeconds(128); + + try { + // test that an invalid redirect uri is rejected. + user.executeActionsEmail("myclient", "http://unregistered-uri.com/", lifespan, actions); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Invalid redirect uri.", error.getErrorMessage()); + } + + + user.executeActionsEmail("myclient", "http://myclient.com/home.html", lifespan, actions); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + MailUtils.EmailBody body = MailUtils.getBody(message); + + assertTrue(body.getText().contains("This link will expire within 128 days")); + assertTrue(body.getHtml().contains("This link will expire within 128 days")); + + String link = MailUtils.getPasswordResetEmailLink(message); + + String token = link.substring(link.indexOf("key=") + "key=".length()); + + try { + final AccessToken accessToken = TokenVerifier.create(token, AccessToken.class).getToken(); + assertEquals(lifespan, accessToken.getExpiration() - accessToken.getIssuedAt()); + } catch (VerificationException e) { + throw new IOException(e); + } + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Update Password")); + proceedPage.clickProceedLink(); + passwordUpdatePage.assertCurrent(); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", driver.findElement(By.id("kc-page-title")).getText()); + + String pageSource = driver.getPageSource(); + + // check to make sure the back link is set. + Assert.assertTrue(pageSource.contains("http://myclient.com/home.html")); + + driver.navigate().to(link); + + assertEquals("We are sorry...", PageUtils.getPageTitle(driver)); + } + + + @Test + public void sendVerifyEmail() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setUsername("user1"); + String id = createUser(userRep); + UserResource user = realm.users().get(id); + + try { + user.sendVerifyEmail(); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("User email missing", error.getErrorMessage()); + } + try { + userRep = user.toRepresentation(); + userRep.setEmail("user1@localhost"); + userRep.setEnabled(false); + updateUser(user, userRep); + + user.sendVerifyEmail(); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("User is disabled", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + try { + userRep.setEnabled(true); + updateUser(user, userRep); + + user.sendVerifyEmail("invalidClientId"); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Client doesn't exist", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + + user.sendVerifyEmail(); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/send-verify-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + String link = MailUtils.getPasswordResetEmailLink(greenMail.getReceivedMessages()[0]); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Confirm validity of e-mail address")); + proceedPage.clickProceedLink(); + + Assert.assertEquals("Your account has been updated.", infoPage.getInfo()); + driver.navigate().to("about:blank"); + + driver.navigate().to(link); + infoPage.assertCurrent(); + assertEquals("Your email address has been verified already.", infoPage.getInfo()); + } + + @Test + public void sendVerifyEmailWithRedirect() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + + String clientId = "test-app"; + String redirectUri = OAuthClient.SERVER_ROOT + "/auth/some-page"; + try { + // test that an invalid redirect uri is rejected. + user.sendVerifyEmail(clientId, "http://unregistered-uri.com/"); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Invalid redirect uri.", error.getErrorMessage()); + } + + + user.sendVerifyEmail(clientId, redirectUri); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/send-verify-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String link = MailUtils.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Confirm validity of e-mail address")); + proceedPage.clickProceedLink(); + + assertEquals("Your account has been updated.", infoPage.getInfo()); + + String pageSource = driver.getPageSource(); + Assert.assertTrue(pageSource.contains(redirectUri)); + } + + @Test + public void sendVerifyEmailWithRedirectAndCustomLifespan() throws IOException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + + String id = createUser(userRep); + + UserResource user = realm.users().get(id); + + final int lifespan = (int) TimeUnit.DAYS.toSeconds(1); + String redirectUri = OAuthClient.SERVER_ROOT + "/auth/some-page"; + try { + // test that an invalid redirect uri is rejected. + user.sendVerifyEmail("test-app", "http://unregistered-uri.com/", lifespan); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("Invalid redirect uri.", error.getErrorMessage()); + } + + + user.sendVerifyEmail("test-app", redirectUri, lifespan); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/send-verify-email", ResourceType.USER); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getReceivedMessages()[0]; + + MailUtils.EmailBody body = MailUtils.getBody(message); + assertThat(body.getText(), Matchers.containsString("This link will expire within 1 day")); + assertThat(body.getHtml(), Matchers.containsString("This link will expire within 1 day")); + + String link = MailUtils.getPasswordResetEmailLink(message); + String token = link.substring(link.indexOf("key=") + "key=".length()); + + try { + final AccessToken accessToken = TokenVerifier.create(token, AccessToken.class).getToken(); + assertEquals(lifespan, accessToken.getExp() - accessToken.getIat()); + } catch (VerificationException e) { + throw new IOException(e); + } + + driver.navigate().to(link); + + proceedPage.assertCurrent(); + assertThat(proceedPage.getInfo(), Matchers.containsString("Confirm validity of e-mail address")); + proceedPage.clickProceedLink(); + + assertEquals("Your account has been updated.", infoPage.getInfo()); + + String pageSource = driver.getPageSource(); + Assert.assertTrue(pageSource.contains(redirectUri)); + } + + @Test + public void updateUserWithNewUsername() { + switchEditUsernameAllowedOn(true); + getCleanup().addCleanup(() -> switchEditUsernameAllowedOn(false)); + + String id = createUser(); + + UserResource user = realm.users().get(id); + UserRepresentation userRep = user.toRepresentation(); + userRep.setUsername("user11"); + updateUser(user, userRep); + + userRep = realm.users().get(id).toRepresentation(); + assertEquals("user11", userRep.getUsername()); + } + + @Test + public void updateUserWithoutUsername() { + switchEditUsernameAllowedOn(true); + getCleanup().addCleanup(() -> switchEditUsernameAllowedOn(false)); + + String id = createUser(); + + UserResource user = realm.users().get(id); + + UserRepresentation rep = new UserRepresentation(); + rep.setFirstName("Firstname"); + + user.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.userResourcePath(id), rep, ResourceType.USER); + + rep = new UserRepresentation(); + rep.setLastName("Lastname"); + + user.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.userResourcePath(id), rep, ResourceType.USER); + + rep = realm.users().get(id).toRepresentation(); + + assertEquals("user1", rep.getUsername()); + assertEquals("user1@localhost", rep.getEmail()); + assertEquals("Firstname", rep.getFirstName()); + assertEquals("Lastname", rep.getLastName()); + } + + @Test + public void updateUserWithEmailAsUsernameEditUsernameDisabled() { + switchRegistrationEmailAsUsername(true); + getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false)); + RealmRepresentation rep = realm.toRepresentation(); + assertFalse(rep.isEditUsernameAllowed()); + String id = createUser(); + + UserResource user = realm.users().get(id); + UserRepresentation userRep = user.toRepresentation(); + assertEquals("user1@localhost", userRep.getUsername()); + + userRep.setEmail("user11@localhost"); + updateUser(user, userRep); + + userRep = realm.users().get(id).toRepresentation(); + assertEquals("user11@localhost", userRep.getUsername()); + assertEquals("user11@localhost", userRep.getEmail()); + } + + @Test + public void updateUserWithEmailAsUsernameEditUsernameAllowed() { + switchRegistrationEmailAsUsername(true); + getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false)); + switchEditUsernameAllowedOn(true); + getCleanup().addCleanup(() -> switchEditUsernameAllowedOn(false)); + + String id = createUser(); + UserResource user = realm.users().get(id); + UserRepresentation userRep = user.toRepresentation(); + assertEquals("user1@localhost", userRep.getUsername()); + + userRep.setEmail("user11@localhost"); + updateUser(user, userRep); + + userRep = realm.users().get(id).toRepresentation(); + assertEquals("user11@localhost", userRep.getUsername()); + assertEquals("user11@localhost", userRep.getEmail()); + } + + @Test + public void updateUserWithExistingEmail() { + final String userId = createUser(); + assertNotNull(userId); + assertNotNull(createUser("user2", "user2@localhost")); + + UserResource user = realm.users().get(userId); + UserRepresentation userRep = user.toRepresentation(); + assertNotNull(userRep); + userRep.setEmail("user2@localhost"); + + try { + updateUser(user, userRep); + fail("Expected failure - Email conflict"); + } catch (ClientErrorException e) { + assertNotNull(e.getResponse()); + assertThat(e.getResponse().getStatus(), is(409)); + + ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class); + Assert.assertEquals("User exists with same username or email", error.getErrorMessage()); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void testKeepRootAttributeWhenOtherAttributesAreSet() { + String random = UUID.randomUUID().toString(); + String userName = String.format("username-%s", random); + String email = String.format("my@mail-%s.com", random); + UserRepresentation user = new UserRepresentation(); + user.setUsername(userName); + user.setEmail(email); + String userId = createUser(user); + + UserRepresentation created = realm.users().get(userId).toRepresentation(); + assertThat(created.getEmail(), equalTo(email)); + assertThat(created.getUsername(), equalTo(userName)); + assertThat(created.getAttributes(), Matchers.nullValue()); + + UserRepresentation update = new UserRepresentation(); + update.setId(userId); + // user profile requires sending all attributes otherwise they are removed + update.setEmail(email); + + update.setAttributes(Map.of("phoneNumber", List.of("123"))); + updateUser(realm.users().get(userId), update); + + UserRepresentation updated = realm.users().get(userId).toRepresentation(); + assertThat(updated.getUsername(), equalTo(userName)); + assertThat(updated.getAttributes().get("phoneNumber"), equalTo(List.of("123"))); + + assertThat(updated.getEmail(), equalTo(email)); + } + + @Test + public void updateUserWithNewUsernameNotPossible() { + RealmRepresentation realmRep = realm.toRepresentation(); + assertFalse(realmRep.isEditUsernameAllowed()); + String id = createUser(); + + UserResource user = realm.users().get(id); + UserRepresentation userRep = user.toRepresentation(); + userRep.setUsername("user11"); + + try { + updateUser(user, userRep); + fail("Should fail because realm does not allow edit username"); + } catch (BadRequestException expected) { + ErrorRepresentation error = expected.getResponse().readEntity(ErrorRepresentation.class); + assertEquals("error-user-attribute-read-only", error.getErrorMessage()); + } + + userRep = realm.users().get(id).toRepresentation(); + assertEquals("user1", userRep.getUsername()); + } + + @Test + public void updateUserWithNewUsernameAccessingViaOldUsername() { + switchEditUsernameAllowedOn(true); + createUser(); + + try { + UserResource user = realm.users().get("user1"); + UserRepresentation userRep = user.toRepresentation(); + userRep.setUsername("user1"); + updateUser(user, userRep); + + realm.users().get("user11").toRepresentation(); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(404, e.getResponse().getStatus()); + assertAdminEvents.assertEmpty(); + } finally { + switchEditUsernameAllowedOn(false); + } + } + + @Test + public void updateUserWithExistingUsername() { + switchEditUsernameAllowedOn(true); + enableBruteForce(true); + createUser(); + + UserRepresentation userRep = new UserRepresentation(); + userRep.setUsername("user2"); + + String createdId = createUser(userRep); + + try { + UserResource user = realm.users().get(createdId); + userRep = user.toRepresentation(); + userRep.setUsername("user1"); + user.update(userRep); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(409, e.getResponse().getStatus()); + + assertAdminEvents.assertEmpty(); + } finally { + enableBruteForce(false); + switchEditUsernameAllowedOn(false); + } + } + + @Test + public void updateUserWithRawCredentials() { + UserRepresentation user = new UserRepresentation(); + user.setUsername("user_rawpw"); + user.setEmail("email.raw@localhost"); + + CredentialRepresentation rawPassword = new CredentialRepresentation(); + rawPassword.setValue("ABCD"); + rawPassword.setType(CredentialRepresentation.PASSWORD); + user.setCredentials(Arrays.asList(rawPassword)); + + String id = createUser(user); + + PasswordCredentialModel credential = PasswordCredentialModel + .createFromCredentialModel(fetchCredentials("user_rawpw")); + assertNotNull("Expecting credential", credential); + assertEquals(PasswordPolicy.HASH_ALGORITHM_DEFAULT, credential.getPasswordCredentialData().getAlgorithm()); + assertEquals(PasswordPolicy.HASH_ITERATIONS_DEFAULT, credential.getPasswordCredentialData().getHashIterations()); + assertNotEquals("ABCD", credential.getPasswordSecretData().getValue()); + assertEquals(CredentialRepresentation.PASSWORD, credential.getType()); + + UserResource userResource = realm.users().get(id); + UserRepresentation userRep = userResource.toRepresentation(); + + CredentialRepresentation rawPasswordForUpdate = new CredentialRepresentation(); + rawPasswordForUpdate.setValue("EFGH"); + rawPasswordForUpdate.setType(CredentialRepresentation.PASSWORD); + userRep.setCredentials(Arrays.asList(rawPasswordForUpdate)); + + updateUser(userResource, userRep); + + PasswordCredentialModel updatedCredential = PasswordCredentialModel + .createFromCredentialModel(fetchCredentials("user_rawpw")); + assertNotNull("Expecting credential", updatedCredential); + assertEquals(PasswordPolicy.HASH_ALGORITHM_DEFAULT, updatedCredential.getPasswordCredentialData().getAlgorithm()); + assertEquals(PasswordPolicy.HASH_ITERATIONS_DEFAULT, updatedCredential.getPasswordCredentialData().getHashIterations()); + assertNotEquals("EFGH", updatedCredential.getPasswordSecretData().getValue()); + assertEquals(CredentialRepresentation.PASSWORD, updatedCredential.getType()); + } + + @Test + public void resetUserPassword() { + String userId = createUser("user1", "user1@localhost"); + + CredentialRepresentation cred = new CredentialRepresentation(); + cred.setType(CredentialRepresentation.PASSWORD); + cred.setValue("password"); + cred.setTemporary(false); + + realm.users().get(userId).resetPassword(cred); + assertAdminEvents.assertEvent(realmId, OperationType.ACTION, AdminEventPaths.userResetPasswordPath(userId), ResourceType.USER); + + oauth.realm(REALM_NAME); + driver.navigate().to(oauth.getLoginFormUrl()); + + assertEquals("Sign in to your account", PageUtils.getPageTitle(driver)); + + loginPage.login("user1", "password"); + + assertTrue(driver.getTitle().contains("AUTH_RESPONSE")); + + // oauth cleanup + oauth.realm("test"); + } + + @Test + public void resetUserInvalidPassword() { + String userId = createUser("user1", "user1@localhost"); + + try { + CredentialRepresentation cred = new CredentialRepresentation(); + cred.setType(CredentialRepresentation.PASSWORD); + cred.setValue(" "); + cred.setTemporary(false); + realm.users().get(userId).resetPassword(cred); + fail("Expected failure"); + } catch (ClientErrorException e) { + assertEquals(400, e.getResponse().getStatus()); + e.getResponse().close(); + assertAdminEvents.assertEmpty(); + } + } + + @Test + public void testDefaultRequiredActionAdded() { + // Add UPDATE_PASSWORD as default required action + RequiredActionProviderRepresentation updatePasswordReqAction = realm.flows().getRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString()); + updatePasswordReqAction.setDefaultAction(true); + realm.flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePasswordReqAction); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(UserModel.RequiredAction.UPDATE_PASSWORD.toString()), updatePasswordReqAction, ResourceType.REQUIRED_ACTION); + + // Create user + String userId = createUser("user1", "user1@localhost"); + + UserRepresentation userRep = realm.users().get(userId).toRepresentation(); + Assert.assertEquals(1, userRep.getRequiredActions().size()); + Assert.assertEquals(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), userRep.getRequiredActions().get(0)); + + // Remove UPDATE_PASSWORD default action + updatePasswordReqAction = realm.flows().getRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString()); + updatePasswordReqAction.setDefaultAction(false); + realm.flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePasswordReqAction); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(UserModel.RequiredAction.UPDATE_PASSWORD.toString()), updatePasswordReqAction, ResourceType.REQUIRED_ACTION); + } + + private RoleRepresentation getRoleByName(String name, List roles) { + for(RoleRepresentation role : roles) { + if(role.getName().equalsIgnoreCase(name)) { + return role; + } + } + + return null; + } + + @Test + public void roleMappings() { + RealmResource realm = adminClient.realms().realm("test"); + String realmId = realm.toRepresentation().getId(); + + // Enable events + RealmRepresentation realmRep = RealmBuilder.edit(realm.toRepresentation()).testEventListener().build(); + realm.update(realmRep); + + RoleRepresentation realmCompositeRole = RoleBuilder.create().name("realm-composite").singleAttribute("attribute1", "value1").build(); + + realm.roles().create(RoleBuilder.create().name("realm-role").build()); + realm.roles().create(realmCompositeRole); + realm.roles().create(RoleBuilder.create().name("realm-child").build()); + realm.roles().get("realm-composite").addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation())); + + final String clientUuid; + try (Response response = realm.clients().create(ClientBuilder.create().clientId("myclient").build())) { + clientUuid = ApiUtil.getCreatedId(response); + } + + RoleRepresentation clientCompositeRole = RoleBuilder.create().name("client-composite").singleAttribute("attribute1", "value1").build(); + + + realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-role").build()); + realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-role2").build()); + realm.clients().get(clientUuid).roles().create(clientCompositeRole); + realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-child").build()); + realm.clients().get(clientUuid).roles().get("client-composite").addComposites(Collections.singletonList(realm.clients().get(clientUuid).roles().get("client-child").toRepresentation())); + + final String userId; + try (Response response = realm.users().create(UserBuilder.create().username("myuser").build())) { + userId = ApiUtil.getCreatedId(response); + } + + // Admin events for creating role, client or user tested already in other places + assertAdminEvents.clear(); + + RoleMappingResource roles = realm.users().get(userId).roles(); + assertNames(roles.realmLevel().listAll(), Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + assertNames(roles.realmLevel().listEffective(), "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + + // Add realm roles + List l = new LinkedList<>(); + l.add(realm.roles().get("realm-role").toRepresentation()); + l.add(realm.roles().get("realm-composite").toRepresentation()); + roles.realmLevel().add(l); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userRealmRoleMappingsPath(userId), l, ResourceType.REALM_ROLE_MAPPING); + + // Add client roles + List list = Collections.singletonList(realm.clients().get(clientUuid).roles().get("client-role").toRepresentation()); + roles.clientLevel(clientUuid).add(list); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userClientRoleMappingsPath(userId, clientUuid), list, ResourceType.CLIENT_ROLE_MAPPING); + + list = Collections.singletonList(realm.clients().get(clientUuid).roles().get("client-composite").toRepresentation()); + roles.clientLevel(clientUuid).add(list); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userClientRoleMappingsPath(userId, clientUuid), ResourceType.CLIENT_ROLE_MAPPING); + + // List realm roles + assertNames(roles.realmLevel().listAll(), "realm-role", "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + assertNames(roles.realmLevel().listAvailable(), "realm-child", "admin", "customer-user-premium", "realm-composite-role", "sample-realm-role", "attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); + assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + + // List realm effective role with full representation + List realmRolesFullRepresentations = roles.realmLevel().listEffective(false); + RoleRepresentation realmCompositeRoleFromList = getRoleByName("realm-composite", realmRolesFullRepresentations); + assertNotNull(realmCompositeRoleFromList); + assertTrue(realmCompositeRoleFromList.getAttributes().containsKey("attribute1")); + + // List client roles + assertNames(roles.clientLevel(clientUuid).listAll(), "client-role", "client-composite"); + assertNames(roles.clientLevel(clientUuid).listAvailable(), "client-role2", "client-child"); + assertNames(roles.clientLevel(clientUuid).listEffective(), "client-role", "client-composite", "client-child"); + + // List client effective role with full representation + List rolesFullRepresentations = roles.clientLevel(clientUuid).listEffective(false); + RoleRepresentation clientCompositeRoleFromList = getRoleByName("client-composite", rolesFullRepresentations); + assertNotNull(clientCompositeRoleFromList); + assertTrue(clientCompositeRoleFromList.getAttributes().containsKey("attribute1")); + + // Get mapping representation + MappingsRepresentation all = roles.getAll(); + assertNames(all.getRealmMappings(), "realm-role", "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + assertEquals(1, all.getClientMappings().size()); + assertNames(all.getClientMappings().get("myclient").getMappings(), "client-role", "client-composite"); + + // Remove realm role + RoleRepresentation realmRoleRep = realm.roles().get("realm-role").toRepresentation(); + roles.realmLevel().remove(Collections.singletonList(realmRoleRep)); + assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.userRealmRoleMappingsPath(userId), Collections.singletonList(realmRoleRep), ResourceType.REALM_ROLE_MAPPING); + + assertNames(roles.realmLevel().listAll(), "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + + // Remove client role + RoleRepresentation clientRoleRep = realm.clients().get(clientUuid).roles().get("client-role").toRepresentation(); + roles.clientLevel(clientUuid).remove(Collections.singletonList(clientRoleRep)); + assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.userClientRoleMappingsPath(userId, clientUuid), Collections.singletonList(clientRoleRep), ResourceType.CLIENT_ROLE_MAPPING); + + assertNames(roles.clientLevel(clientUuid).listAll(), "client-composite"); + } + + + @Test + public void rolesCanBeAssignedEvenWhenTheyAreAlreadyIndirectlyAssigned() { + RealmResource realm = adminClient.realms().realm("test"); + + RoleRepresentation realmCompositeRole = RoleBuilder.create().name("realm-composite").build(); + realm.roles().create(realmCompositeRole); + realm.roles().create(RoleBuilder.create().name("realm-child").build()); + realm.roles().get("realm-composite") + .addComposites(Collections.singletonList(realm.roles().get("realm-child").toRepresentation())); + realm.roles().create(RoleBuilder.create().name("realm-role-in-group").build()); + + Response response = realm.clients().create(ClientBuilder.create().clientId("myclient").build()); + String clientUuid = ApiUtil.getCreatedId(response); + response.close(); + + RoleRepresentation clientCompositeRole = RoleBuilder.create().name("client-composite").build(); + realm.clients().get(clientUuid).roles().create(clientCompositeRole); + realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-child").build()); + realm.clients().get(clientUuid).roles().get("client-composite").addComposites(Collections + .singletonList(realm.clients().get(clientUuid).roles().get("client-child").toRepresentation())); + realm.clients().get(clientUuid).roles().create(RoleBuilder.create().name("client-role-in-group").build()); + + GroupRepresentation group = GroupBuilder.create().name("mygroup").build(); + response = realm.groups().add(group); + String groupId = ApiUtil.getCreatedId(response); + response.close(); + + response = realm.users().create(UserBuilder.create().username("myuser").build()); + String userId = ApiUtil.getCreatedId(response); + response.close(); + + // Make indirect assignments + // .. add roles to the group and add it to the user + realm.groups().group(groupId).roles().realmLevel() + .add(Collections.singletonList(realm.roles().get("realm-role-in-group").toRepresentation())); + realm.groups().group(groupId).roles().clientLevel(clientUuid).add(Collections + .singletonList(realm.clients().get(clientUuid).roles().get("client-role-in-group").toRepresentation())); + realm.users().get(userId).joinGroup(groupId); + // .. assign composite roles + RoleMappingResource userRoles = realm.users().get(userId).roles(); + userRoles.realmLevel().add(Collections.singletonList(realm.roles().get("realm-composite").toRepresentation())); + userRoles.clientLevel(clientUuid).add(Collections + .singletonList(realm.clients().get(clientUuid).roles().get("client-composite").toRepresentation())); + + // check state before making the direct assignments + assertNames(userRoles.realmLevel().listAll(), "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + assertNames(userRoles.realmLevel().listAvailable(), "realm-child", "realm-role-in-group", + "admin", "customer-user-premium", "realm-composite-role", + "sample-realm-role", + "attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); + assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group", + "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, + Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + + assertNames(userRoles.clientLevel(clientUuid).listAll(), "client-composite"); + assertNames(userRoles.clientLevel(clientUuid).listAvailable(), "client-child", + "client-role-in-group"); + assertNames(userRoles.clientLevel(clientUuid).listEffective(), "client-composite", "client-child", + "client-role-in-group"); + + // Make direct assignments for roles which are already indirectly assigned + userRoles.realmLevel().add(Collections.singletonList(realm.roles().get("realm-child").toRepresentation())); + userRoles.realmLevel() + .add(Collections.singletonList(realm.roles().get("realm-role-in-group").toRepresentation())); + userRoles.clientLevel(clientUuid).add(Collections + .singletonList(realm.clients().get(clientUuid).roles().get("client-child").toRepresentation())); + userRoles.clientLevel(clientUuid).add(Collections + .singletonList(realm.clients().get(clientUuid).roles().get("client-role-in-group").toRepresentation())); + + // List realm roles + assertNames(userRoles.realmLevel().listAll(), "realm-composite", + "realm-child", "realm-role-in-group", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + assertNames(userRoles.realmLevel().listAvailable(), "admin", "customer-user-premium", "realm-composite-role", + "sample-realm-role", "attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); + assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group", + "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, + Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + + // List client roles + assertNames(userRoles.clientLevel(clientUuid).listAll(), "client-composite", "client-child", + "client-role-in-group"); + assertNames(userRoles.clientLevel(clientUuid).listAvailable()); + assertNames(userRoles.clientLevel(clientUuid).listEffective(), "client-composite", "client-child", + "client-role-in-group"); + + // Get mapping representation + MappingsRepresentation all = userRoles.getAll(); + assertNames(all.getRealmMappings(), "realm-composite", + "realm-child", "realm-role-in-group", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test"); + assertEquals(1, all.getClientMappings().size()); + assertNames(all.getClientMappings().get("myclient").getMappings(), "client-composite", "client-child", + "client-role-in-group"); + } + + @Test + public void defaultMaxResults() { + UserProfileResource upResource = adminClient.realm("test").users().userProfile(); + UPConfig upConfig = upResource.getConfiguration(); + upConfig.addOrReplaceAttribute(createAttributeMetadata("aName")); + upResource.update(upConfig); + + try { + UsersResource users = adminClient.realms().realm("test").users(); + + for (int i = 0; i < 110; i++) { + users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close(); + } + + List result = users.search("test", null, null); + assertEquals(100, result.size()); + for (UserRepresentation user : result) { + assertThat(user.getAttributes(), Matchers.notNullValue()); + assertThat(user.getAttributes().keySet(), Matchers.hasSize(1)); + assertThat(user.getAttributes(), Matchers.hasEntry(is("aName"), Matchers.contains("aValue"))); + } + + assertEquals(105, users.search("test", 0, 105).size()); + assertEquals(111, users.search("test", 0, 1000).size()); + } finally { + upConfig.removeAttribute("aName"); + upResource.update(upConfig); + } + } + + @Test + public void defaultMaxResultsBrief() { + UserProfileResource upResource = adminClient.realm("test").users().userProfile(); + UPConfig upConfig = upResource.getConfiguration(); + upConfig.addOrReplaceAttribute(createAttributeMetadata("aName")); + upResource.update(upConfig); + + try { + UsersResource users = adminClient.realms().realm("test").users(); + + for (int i = 0; i < 110; i++) { + users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close(); + } + + List result = users.search("test", null, null, true); + assertEquals(100, result.size()); + for (UserRepresentation user : result) { + assertThat(user.getAttributes(), Matchers.nullValue()); + } + } finally { + upConfig.removeAttribute("aName"); + upResource.update(upConfig); + } + } + + @Test + public void testAccessUserFromOtherRealm() { + RealmRepresentation firstRealm = new RealmRepresentation(); + + firstRealm.setRealm("first-realm"); + + adminClient.realms().create(firstRealm); + getCleanup().addCleanup(new AutoCloseable() { + @Override + public void close() throws Exception { + adminClient.realms().realm(firstRealm.getRealm()).remove(); + } + }); + + realm = adminClient.realm(firstRealm.getRealm()); + realmId = realm.toRepresentation().getId(); + + UserRepresentation firstUser = new UserRepresentation(); + + firstUser.setUsername("first"); + firstUser.setEmail("first@first-realm.org"); + + firstUser.setId(createUser(firstUser, false)); + + RealmRepresentation secondRealm = new RealmRepresentation(); + + secondRealm.setRealm("second-realm"); + + adminClient.realms().create(secondRealm); + + adminClient.realm(firstRealm.getRealm()).users().get(firstUser.getId()).update(firstUser); + + try { + adminClient.realm(secondRealm.getRealm()).users().get(firstUser.getId()).toRepresentation(); + fail("Should not have access to firstUser from another realm"); + } catch (NotFoundException nfe) { + // ignore + } finally { + adminClient.realm(secondRealm.getRealm()).remove(); + } + } + + private void switchEditUsernameAllowedOn(boolean enable) { + RealmRepresentation rep = realm.toRepresentation(); + rep.setEditUsernameAllowed(enable); + realm.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM); + } + + protected void switchRegistrationEmailAsUsername(boolean enable) { + RealmRepresentation rep = realm.toRepresentation(); + rep.setRegistrationEmailAsUsername(enable); + realm.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM); + } + + private void enableBruteForce(boolean enable) { + RealmRepresentation rep = realm.toRepresentation(); + rep.setBruteForceProtected(enable); + realm.update(rep); + assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM); + } + + @Test + public void loginShouldFailAfterPasswordDeleted() { + String userName = "credential-tester"; + String userPass = "s3cr37"; + String userId = createUser(REALM_NAME, userName, userPass); + getCleanup(REALM_NAME).addUserId(userId); + + oauth.realm(REALM_NAME); + driver.navigate().to(oauth.getLoginFormUrl()); + assertEquals("Test user should be on the login page.", "Sign in to your account", PageUtils.getPageTitle(driver)); + loginPage.login(userName, userPass); + assertTrue("Test user should be successfully logged in.", driver.getTitle().contains("AUTH_RESPONSE")); + AccountHelper.logout(realm, userName); + + Optional passwordCredential = + realm.users().get(userId).credentials().stream() + .filter(c -> CredentialRepresentation.PASSWORD.equals(c.getType())) + .findFirst(); + assertTrue("Test user should have a password credential set.", passwordCredential.isPresent()); + realm.users().get(userId).removeCredential(passwordCredential.get().getId()); + + driver.navigate().to(oauth.getLoginFormUrl()); + assertEquals("Test user should be on the login page.", "Sign in to your account", PageUtils.getPageTitle(driver)); + loginPage.login(userName, userPass); + assertTrue("Test user should fail to log in after password was deleted.", + driver.getCurrentUrl().contains(String.format("/realms/%s/login-actions/authenticate", REALM_NAME))); + + //oauth cleanup + oauth.realm("test"); + } + + @Test + public void testGetAndMoveCredentials() { + importTestRealms(); + + UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "user-with-two-configured-otp"); + List creds = user.credentials(); + List expectedCredIds = Arrays.asList(creds.get(0).getId(), creds.get(1).getId(), creds.get(2).getId()); + + // Check actual user credentials + assertSameIds(expectedCredIds, user.credentials()); + + // Move first credential after second one + user.moveCredentialAfter(expectedCredIds.get(0), expectedCredIds.get(1)); + List newOrderCredIds = Arrays.asList(expectedCredIds.get(1), expectedCredIds.get(0), expectedCredIds.get(2)); + assertSameIds(newOrderCredIds, user.credentials()); + + // Move last credential in first position + user.moveCredentialToFirst(expectedCredIds.get(2)); + newOrderCredIds = Arrays.asList(expectedCredIds.get(2), expectedCredIds.get(1), expectedCredIds.get(0)); + assertSameIds(newOrderCredIds, user.credentials()); + + // Restore initial state + user.moveCredentialToFirst(expectedCredIds.get(1)); + user.moveCredentialToFirst(expectedCredIds.get(0)); + assertSameIds(expectedCredIds, user.credentials()); + } + + private void assertSameIds(List expectedIds, List actual) { + Assert.assertEquals(expectedIds.size(), actual.size()); + for (int i = 0; i < expectedIds.size(); i++) { + Assert.assertEquals(expectedIds.get(i), actual.get(i).getId()); + } + } + + @Test + public void testUpdateCredentials() { + importTestRealms(); + + // Get user user-with-one-configured-otp and assert he has no label linked to its OTP credential + UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "user-with-one-configured-otp"); + CredentialRepresentation otpCred = user.credentials().get(0); + Assert.assertNull(otpCred.getUserLabel()); + + // Set and check a new label + String newLabel = "the label"; + user.setCredentialUserLabel(otpCred.getId(), newLabel); + Assert.assertEquals(newLabel, user.credentials().get(0).getUserLabel()); + } + + @Test + public void testUpdateCredentialLabelForFederatedUser() { + // Create user federation + ComponentRepresentation memProvider = new ComponentRepresentation(); + memProvider.setName("memory"); + memProvider.setProviderId(UserMapStorageFactory.PROVIDER_ID); + memProvider.setProviderType(UserStorageProvider.class.getName()); + memProvider.setConfig(new MultivaluedHashMap<>()); + memProvider.getConfig().putSingle("priority", Integer.toString(0)); + memProvider.getConfig().putSingle(IMPORT_ENABLED, Boolean.toString(false)); + + RealmResource realm = adminClient.realms().realm(REALM_NAME); + Response resp = realm.components().add(memProvider); + resp.close(); + String memProviderId = ApiUtil.getCreatedId(resp); + getCleanup().addComponentId(memProviderId); + + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.componentPath(memProviderId), memProvider, ResourceType.COMPONENT); + + // Create federated user + String username = "fed-user1"; + UserRepresentation userRepresentation = new UserRepresentation(); + userRepresentation.setUsername(username); + userRepresentation.setEmail("feduser1@mail.com"); + userRepresentation.setRequiredActions(Collections.emptyList()); + userRepresentation.setEnabled(true); + userRepresentation.setFederationLink(memProviderId); + + PasswordCredentialModel pcm = PasswordCredentialModel.createFromValues("my-algorithm", "theSalt".getBytes(), 22, "ABC"); + CredentialRepresentation hashedPassword = ModelToRepresentation.toRepresentation(pcm); + hashedPassword.setCreatedDate(1001L); + hashedPassword.setUserLabel("label"); + hashedPassword.setType(CredentialRepresentation.PASSWORD); + + userRepresentation.setCredentials(Arrays.asList(hashedPassword)); + String userId = createUser(userRepresentation); + Assert.assertFalse(StorageId.isLocalStorage(userId)); + + UserResource user = ApiUtil.findUserByUsernameId(realm, username); + List credentials = user.credentials(); + Assert.assertNotNull(credentials); + Assert.assertEquals(1, credentials.size()); + Assert.assertEquals("label", credentials.get(0).getUserLabel()); + + // Update federated credential user label + user.setCredentialUserLabel(credentials.get(0).getId(), "updatedLabel"); + credentials = user.credentials(); + Assert.assertNotNull(credentials); + Assert.assertEquals(1, credentials.size()); + Assert.assertEquals("updatedLabel", credentials.get(0).getUserLabel()); + } + + @Test + public void testDeleteCredentials() { + UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "john-doh@localhost"); + List creds = user.credentials(); + Assert.assertEquals(1, creds.size()); + CredentialRepresentation credPasswd = creds.get(0); + Assert.assertEquals("password", credPasswd.getType()); + + // Remove password + user.removeCredential(credPasswd.getId()); + Assert.assertEquals(0, user.credentials().size()); + + // Restore password + credPasswd.setValue("password"); + user.resetPassword(credPasswd); + Assert.assertEquals(1, user.credentials().size()); + } + + @Test + public void testCRUDCredentialsOfDifferentUser() { + // Get credential ID of the OTP credential of the user1 + UserResource user1 = ApiUtil.findUserByUsernameId(testRealm(), "user-with-one-configured-otp"); + CredentialRepresentation otpCredential = user1.credentials().stream() + .filter(credentialRep -> OTPCredentialModel.TYPE.equals(credentialRep.getType())) + .findFirst() + .get(); + + // Test that when admin operates on user "user2", he can't update, move or remove credentials of different user "user1" + UserResource user2 = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost"); + try { + user2.setCredentialUserLabel(otpCredential.getId(), "new-label"); + Assert.fail("Not expected to successfully update user label"); + } catch (NotFoundException nfe) { + // Expected + } + + try { + user2.moveCredentialToFirst(otpCredential.getId()); + Assert.fail("Not expected to successfully move credential"); + } catch (NotFoundException nfe) { + // Expected + } + + try { + user2.removeCredential(otpCredential.getId()); + Assert.fail("Not expected to successfully remove credential"); + } catch (NotFoundException nfe) { + // Expected + } + + // Assert credential was not removed or updated + CredentialRepresentation otpCredentialLoaded = user1.credentials().stream() + .filter(credentialRep -> OTPCredentialModel.TYPE.equals(credentialRep.getType())) + .findFirst() + .get(); + Assert.assertTrue(ObjectUtil.isEqualOrBothNull(otpCredential.getUserLabel(), otpCredentialLoaded.getUserLabel())); + Assert.assertTrue(ObjectUtil.isEqualOrBothNull(otpCredential.getPriority(), otpCredentialLoaded.getPriority())); + } + + @Test + public void testGetGroupsForUserFullRepresentation() { + RealmResource realm = adminClient.realms().realm("test"); + + String userName = "averagejoe"; + String groupName = "groupWithAttribute"; + Map> attributes = new HashMap>(); + attributes.put("attribute1", Arrays.asList("attribute1","attribute2")); + + UserRepresentation userRepresentation = UserBuilder + .edit(createUserRepresentation(userName, "joe@average.com", "average", "joe", true)) + .addPassword("password") + .build(); + + try (Creator u = Creator.create(realm, userRepresentation); + Creator g = Creator.create(realm, GroupBuilder.create().name(groupName).attributes(attributes).build())) { + + String groupId = g.id(); + UserResource user = u.resource(); + user.joinGroup(groupId); + + List userGroups = user.groups(0, 100, false); + + assertFalse(userGroups.isEmpty()); + assertTrue(userGroups.get(0).getAttributes().containsKey("attribute1")); + } + } + + @Test + public void testGetSearchedGroupsForUserFullRepresentation() { + RealmResource realm = adminClient.realms().realm("test"); + + String userName = "averagejoe"; + String groupName1 = "group1WithAttribute"; + String groupName2 = "group2WithAttribute"; + Map> attributes1 = new HashMap>(); + attributes1.put("attribute1", Arrays.asList("attribute1")); + Map> attributes2 = new HashMap>(); + attributes2.put("attribute2", Arrays.asList("attribute2")); + + UserRepresentation userRepresentation = UserBuilder + .edit(createUserRepresentation(userName, "joe@average.com", "average", "joe", true)) + .addPassword("password") + .build(); + + try (Creator u = Creator.create(realm, userRepresentation); + Creator g1 = Creator.create(realm, GroupBuilder.create().name(groupName1).attributes(attributes1).build()); + Creator g2 = Creator.create(realm, GroupBuilder.create().name(groupName2).attributes(attributes2).build())) { + + String group1Id = g1.id(); + String group2Id = g2.id(); + UserResource user = u.resource(); + user.joinGroup(group1Id); + user.joinGroup(group2Id); + + List userGroups = user.groups("group2", false); + assertFalse(userGroups.isEmpty()); + assertTrue(userGroups.stream().collect(Collectors.toMap(GroupRepresentation::getName, Function.identity())).get(groupName2).getAttributes().containsKey("attribute2")); + + userGroups = user.groups("group3", false); + assertTrue(userGroups.isEmpty()); + } + } + + @Test + public void groupMembershipPaginated() { + String userId = createUser(UserBuilder.create().username("user-a").build()); + + for (int i = 1; i <= 10; i++) { + GroupRepresentation group = new GroupRepresentation(); + group.setName("group-" + i); + String groupId = createGroup(realm, group).getId(); + realm.users().get(userId).joinGroup(groupId); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userGroupPath(userId, groupId), group, ResourceType.GROUP_MEMBERSHIP); + } + + List groups = realm.users().get(userId).groups(5, 6); + assertEquals(groups.size(), 5); + assertNames(groups, "group-5","group-6","group-7","group-8","group-9"); + } + + @Test + public void groupMembershipSearch() { + String userId = createUser(UserBuilder.create().username("user-b").build()); + + for (int i = 1; i <= 10; i++) { + GroupRepresentation group = new GroupRepresentation(); + group.setName("group-" + i); + String groupId = createGroup(realm, group).getId(); + realm.users().get(userId).joinGroup(groupId); + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.userGroupPath(userId, groupId), group, ResourceType.GROUP_MEMBERSHIP); + } + + List groups = realm.users().get(userId).groups("-3", 0, 10); + assertThat(realm.users().get(userId).groupsCount("-3").get("count"), is(1L)); + assertEquals(1, groups.size()); + assertNames(groups, "group-3"); + + List groups2 = realm.users().get(userId).groups("1", 0, 10); + assertThat(realm.users().get(userId).groupsCount("1").get("count"), is(2L)); + assertEquals(2, groups2.size()); + assertNames(groups2, "group-1", "group-10"); + + List groups3 = realm.users().get(userId).groups("1", 2, 10); + assertEquals(0, groups3.size()); + + List groups4 = realm.users().get(userId).groups("gr", 2, 10); + assertThat(realm.users().get(userId).groupsCount("gr").get("count"), is(10L)); + assertEquals(8, groups4.size()); + + List groups5 = realm.users().get(userId).groups("Gr", 2, 10); + assertEquals(8, groups5.size()); + } + + @Test + public void createFederatedIdentities() { + String identityProviderAlias = "social-provider-id"; + String username = "federated-identities"; + String federatedUserId = "federated-user-id"; + + addSampleIdentityProvider(); + + UserRepresentation build = UserBuilder.create() + .username(username) + .federatedLink(identityProviderAlias, federatedUserId) + .build(); + + //when + String userId = createUser(build, false); + List obtainedFederatedIdentities = realm.users().get(userId).getFederatedIdentity(); + + //then + assertEquals(1, obtainedFederatedIdentities.size()); + assertEquals(federatedUserId, obtainedFederatedIdentities.get(0).getUserId()); + assertEquals(username, obtainedFederatedIdentities.get(0).getUserName()); + assertEquals(identityProviderAlias, obtainedFederatedIdentities.get(0).getIdentityProvider()); + } + + @Test + public void createUserWithGroups() { + String username = "user-with-groups"; + String groupToBeAdded = "test-group"; + + createGroup(realm, GroupBuilder.create().name(groupToBeAdded).build()); + + UserRepresentation build = UserBuilder.create() + .username(username) + .addGroups(groupToBeAdded) + .build(); + + //when + String userId = createUser(build); + List obtainedGroups = realm.users().get(userId).groups(); + + //then + assertEquals(1, obtainedGroups.size()); + assertEquals(groupToBeAdded, obtainedGroups.get(0).getName()); + } + + private GroupRepresentation createGroup(RealmResource realm, GroupRepresentation group) { + final String groupId; + try (Response response = realm.groups().add(group)) { + groupId = ApiUtil.getCreatedId(response); + getCleanup().addGroupId(groupId); + } + + assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.groupPath(groupId), group, ResourceType.GROUP); + + // Set ID to the original rep + group.setId(groupId); + return group; + } + + @Test + public void failCreateUserUsingRegularUser() throws Exception { + String regularUserId = ApiUtil.getCreatedId( + testRealm().users().create(UserBuilder.create().username("regular-user").password("password").build())); + UserResource regularUser = testRealm().users().get(regularUserId); + UserRepresentation regularUserRep = regularUser.toRepresentation(); + + try (Keycloak adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), + TEST, regularUserRep.getUsername(), "password", Constants.ADMIN_CLI_CLIENT_ID, null)) { + UserRepresentation invalidUser = UserBuilder.create().username("do-not-create-me").build(); + + Response response = adminClient.realm(TEST).users().create(invalidUser); + assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus()); + + invalidUser.setGroups(Collections.emptyList()); + response = adminClient.realm(TEST).users().create(invalidUser); + + assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus()); + } + } + + @Test + public void testCreateUserDoNotGrantRole() { + testRealm().roles().create(RoleBuilder.create().name("realm-role").build()); + + try { + UserRepresentation userRep = UserBuilder.create().username("alice").password("password").addRoles("realm-role") + .build(); + String userId = ApiUtil.getCreatedId(testRealm().users().create(userRep)); + UserResource user = testRealm().users().get(userId); + List realmMappings = user.roles().getAll().getRealmMappings(); + + assertFalse(realmMappings.stream().map(RoleRepresentation::getName).anyMatch("realm-role"::equals)); + } finally { + testRealm().roles().get("realm-role").remove(); + } + } + + + @Test + public void joinParentGroupAfterSubGroup() { + String username = "user-with-sub-and-parent-group"; + String parentGroupName = "parent-group"; + String subGroupName = "sub-group"; + + UserRepresentation userRepresentation = UserBuilder.create().username(username).build(); + + GroupRepresentation subGroupRep = GroupBuilder.create().name(subGroupName).build(); + GroupRepresentation parentGroupRep = GroupBuilder.create().name(parentGroupName).subGroups(List.of(subGroupRep)).build(); + + try (Creator u = Creator.create(realm, userRepresentation); + Creator subgroup = Creator.create(realm, subGroupRep); + Creator parentGroup = Creator.create(realm, parentGroupRep)) { + + UserResource user = u.resource(); + + //when + user.joinGroup(subgroup.id()); + List obtainedGroups = realm.users().get(u.id()).groups(); + + //then + assertEquals(1, obtainedGroups.size()); + assertEquals(subGroupName, obtainedGroups.get(0).getName()); + + //when + user.joinGroup(parentGroup.id()); + obtainedGroups = realm.users().get(u.id()).groups(); + + //then + assertEquals(2, obtainedGroups.size()); + assertEquals(parentGroupName, obtainedGroups.get(0).getName()); + assertEquals(subGroupName, obtainedGroups.get(1).getName()); + } + } + + @Test + public void joinSubGroupAfterParentGroup() { + String username = "user-with-sub-and-parent-group"; + String parentGroupName = "parent-group"; + String subGroupName = "sub-group"; + + UserRepresentation userRepresentation = UserBuilder.create().username(username).build(); + GroupRepresentation subGroupRep = GroupBuilder.create().name(subGroupName).build(); + GroupRepresentation parentGroupRep = GroupBuilder.create().name(parentGroupName).subGroups(List.of(subGroupRep)).build(); + + try (Creator u = Creator.create(realm, userRepresentation); + Creator subgroup = Creator.create(realm, subGroupRep); + Creator parentGroup = Creator.create(realm, parentGroupRep)) { + + UserResource user = u.resource(); + + //when + user.joinGroup(parentGroup.id()); + List obtainedGroups = realm.users().get(u.id()).groups(); + + //then + assertEquals(1, obtainedGroups.size()); + assertEquals(parentGroupName, obtainedGroups.get(0).getName()); + + //when + user.joinGroup(subgroup.id()); + obtainedGroups = realm.users().get(u.id()).groups(); + + //then + assertEquals(2, obtainedGroups.size()); + assertEquals(parentGroupName, obtainedGroups.get(0).getName()); + assertEquals(subGroupName, obtainedGroups.get(1).getName()); + } + } + + @Test + public void expectNoPasswordShownWhenCreatingUserWithPassword() throws IOException { + CredentialRepresentation credential = new CredentialRepresentation(); + credential.setType(CredentialRepresentation.PASSWORD); + credential.setValue("password"); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("test"); + user.setCredentials(Collections.singletonList(credential)); + user.setEnabled(true); + + createUser(user, false); + + String actualRepresentation = assertAdminEvents.poll().getRepresentation(); + assertEquals( + JsonSerialization.writeValueAsString(user), + actualRepresentation + ); + } + + @Test + public void testUserProfileMetadata() { + String userId = createUser("user-metadata", "user-metadata@keycloak.org"); + UserRepresentation user = realm.users().get(userId).toRepresentation(true); + UserProfileMetadata metadata = user.getUserProfileMetadata(); + assertNotNull(metadata); + + for (String name : managedAttributes) { + assertNotNull(metadata.getAttributeMetadata(name)); + } + } + + @Test + public void testUsernameReadOnlyIfEmailAsUsernameEnabled() { + switchRegistrationEmailAsUsername(true); + getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false)); + String userId = createUser("user-metadata", "user-metadata@keycloak.org"); + UserRepresentation user = realm.users().get(userId).toRepresentation(true); + UserProfileMetadata metadata = user.getUserProfileMetadata(); + assertNotNull(metadata); + UserProfileAttributeMetadata username = metadata.getAttributeMetadata(UserModel.USERNAME); + assertNotNull(username); + assertTrue(username.isReadOnly()); + UserProfileAttributeMetadata email = metadata.getAttributeMetadata(UserModel.EMAIL); + assertNotNull(email); + assertFalse(email.isReadOnly()); + } + + @Test + public void testEmailNotReadOnlyIfEmailAsUsernameEnabledAndEditUsernameDisabled() { + switchRegistrationEmailAsUsername(true); + getCleanup().addCleanup(() -> switchRegistrationEmailAsUsername(false)); + RealmRepresentation rep = realm.toRepresentation(); + assertFalse(rep.isEditUsernameAllowed()); + String userId = createUser("user-metadata", "user-metadata@keycloak.org"); + UserRepresentation user = realm.users().get(userId).toRepresentation(true); + UserProfileMetadata metadata = user.getUserProfileMetadata(); + assertNotNull(metadata); + UserProfileAttributeMetadata username = metadata.getAttributeMetadata(UserModel.USERNAME); + assertNotNull(username); + assertTrue(username.isReadOnly()); + UserProfileAttributeMetadata email = metadata.getAttributeMetadata(UserModel.EMAIL); + assertNotNull(email); + assertFalse(email.isReadOnly()); + } + + @Test + public void testDefaultCharacterValidationOnUsername() { + List invalidNames = List.of("1user\\\\", "2user\\\\%", "3user\\\\*", "4user\\\\_"); + + for (String invalidName : invalidNames) { + try { + createUser(invalidName, "test@invalid.org"); + fail("Should fail because the username contains invalid characters"); + } catch (WebApplicationException bre) { + assertEquals(400, bre.getResponse().getStatus()); + ErrorRepresentation error = bre.getResponse().readEntity(ErrorRepresentation.class); + assertEquals("error-username-invalid-character", error.getErrorMessage()); + } + } + }*/ + + private UPAttribute createAttributeMetadata(String name) { + UPAttribute attribute = new UPAttribute(); + attribute.setName(name); + UPAttributePermissions permissions = new UPAttributePermissions(); + permissions.setEdit(new HashSet<>(Arrays.asList("user", "admin"))); + attribute.setPermissions(permissions); + this.managedAttributes.add(name); + return attribute; + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/forms2/LoginTest.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/forms2/LoginTest.java new file mode 100644 index 000000000000..446948ae86cc --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/forms2/LoginTest.java @@ -0,0 +1,37 @@ +package org.keycloak.testsuite.forms2; + +import com.microsoft.playwright.Locator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.KeycloakTest; + +import java.util.List; + +public class LoginTest extends KeycloakTest { + + @Test + public void loginSuccess() { + String keycloakUrl = keycloak.getBaseUrl() + "/admin"; + page.navigate(keycloakUrl); + + Locator usernameInput = page.locator("input[name=\"username\"]"); + usernameInput.fill("admin"); + + Locator passwordInput = page.locator("input[name=\"password\"]"); + passwordInput.fill("admin"); + + Locator submitButton = page.locator("input[name=\"login\"]"); + submitButton.click(); + Assertions.assertTrue(page.url().contains("admin/master/console/")); + } + + @Test + public void sampleTest(){ + } + + @Override + public void addTestRealms(List testRealms) { + + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/forms2/SimpleTest.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/forms2/SimpleTest.java new file mode 100644 index 000000000000..f9855f9f4a1a --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/forms2/SimpleTest.java @@ -0,0 +1,21 @@ +package org.keycloak.testsuite.forms2; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.KeycloakTest; + +import java.util.List; + +public class SimpleTest extends KeycloakTest { + + @Test + public void simpleTest() { + Assertions.assertTrue(true); + } + + @Override + public void addTestRealms(List testRealms) { + + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/ContainerKeycloakLifecycle.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/ContainerKeycloakLifecycle.java new file mode 100644 index 000000000000..c3b3f5f768e6 --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/ContainerKeycloakLifecycle.java @@ -0,0 +1,40 @@ +package org.keycloak.testsuite.server; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.utility.DockerImageName; + +public class ContainerKeycloakLifecycle implements KeycloakLifecycle { + + @Container + GenericContainer keycloak; + @Override + public void start() { + keycloak = new GenericContainer(DockerImageName.parse("quay.io/keycloak/keycloak:latest")) + .withExposedPorts(8080) + .withCommand("start-dev") + .withEnv("KEYCLOAK_ADMIN", "admin") + .withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin"); + keycloak.start(); + } + + @Override + public void stop() { + keycloak.stop(); + } + + @Override + public int getPort() { + return keycloak.getFirstMappedPort(); + } + + @Override + public String getBaseUrl() { + return "http://" + keycloak.getHost() + ":" + keycloak.getFirstMappedPort(); + } + + @Override + public boolean isRunning(){ + return false; + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/EmbeddedKeycloakLifecycle.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/EmbeddedKeycloakLifecycle.java new file mode 100644 index 000000000000..90ad1c856f43 --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/EmbeddedKeycloakLifecycle.java @@ -0,0 +1,99 @@ +package org.keycloak.testsuite.server; + +import org.jboss.logging.Logger; +import org.keycloak.Keycloak; +import org.keycloak.common.Version; + +import java.util.Arrays; +import java.util.List; + +/** + * Run embedded Keycloak-on-quarkus server before the test-class and stop it after each test-class + * + * @author Marek Posolda + */ +public final class EmbeddedKeycloakLifecycle implements KeycloakLifecycle { + + private static final Logger logger = Logger.getLogger(EmbeddedKeycloakLifecycle.class); + private static final String KEYCLOAK_VERSION = Version.VERSION; + + // TODO: Read from pom if needed (For instance like org.keycloak.common.Version) + private static final String MY_VERSION = "1.0-SNAPSHOT"; + + private static final int HTTP_PORT = 8180; + private static final int HTTPS_PORT = 8543; + + private static Keycloak keycloak; + + private static EmbeddedKeycloakLifecycle instance; + + private static boolean isRunning = false; + + private EmbeddedKeycloakLifecycle() { + } + + public static EmbeddedKeycloakLifecycle getInstance() { + if (instance == null) { + instance = new EmbeddedKeycloakLifecycle(); + } + return instance; + } + + @Override + public void start() { + logger.info("Starting embedded Keycloak server"); + + try { + keycloak = Keycloak.builder() + //.setHomeDir(configuration.getProvidersPath()) + .setVersion(KEYCLOAK_VERSION) + .start(getArgs()); + isRunning = true; + } catch (Exception e) { + throw new RuntimeException(e); + } + logger.info("Started embedded Keycloak server"); + } + + private List getArgs() { + System.setProperty("quarkus.http.test-port", String.valueOf(HTTP_PORT)); + System.setProperty("quarkus.http.test-ssl-port", String.valueOf(HTTPS_PORT)); + + return Arrays.asList("-v", + "start-dev", + "--db=dev-mem", + "--cache=local"); + } + + @Override + public void stop() { + if (keycloak != null) { + logger.info("Going to stop embedded Keycloak server"); + + try { + keycloak.stop(); + } catch (Exception e) { + throw new RuntimeException("Failed to stop the server", e); + } finally { + keycloak = null; + } + logger.info("Keycloak server stopped"); + isRunning = false; + } + } + + @Override + public int getPort() { + return HTTP_PORT; + } + + @Override + public String getBaseUrl() { + return "http://localhost:" + HTTP_PORT; + } + + @Override + public boolean isRunning() { + return isRunning; + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/KeycloakLifecycle.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/KeycloakLifecycle.java new file mode 100644 index 000000000000..c80457093822 --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/KeycloakLifecycle.java @@ -0,0 +1,14 @@ +package org.keycloak.testsuite.server; + +public interface KeycloakLifecycle { + + void start(); + + void stop(); + + int getPort(); + + String getBaseUrl(); + + boolean isRunning(); +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/RemoteKeycloakLifecycle.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/RemoteKeycloakLifecycle.java new file mode 100644 index 000000000000..2f3b3f03fa0e --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/server/RemoteKeycloakLifecycle.java @@ -0,0 +1,32 @@ +package org.keycloak.testsuite.server; + +import org.jboss.logging.Logger; + +public class RemoteKeycloakLifecycle implements KeycloakLifecycle { + + private static final Logger logger = Logger.getLogger(RemoteKeycloakLifecycle.class); + @Override + public void start() { + logger.infof("Ignored start of Keycloak server as it is externally managed"); + } + + @Override + public void stop() { + logger.infof("Ignored stop of Keycloak server as it is externally managed"); + } + + @Override + public int getPort() { + return 0; + } + + @Override + public String getBaseUrl() { + return ""; + } + + @Override + public boolean isRunning() { + return false; + } +} diff --git a/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java new file mode 100644 index 000000000000..41dd59e741ef --- /dev/null +++ b/testsuite/integration-playwright/src/test/java/org/keycloak/testsuite/welcomepage/WelcomePageTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017 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.welcomepage; + +import com.microsoft.playwright.Locator; +import org.junit.jupiter.api.*; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.KeycloakTest; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class WelcomePageTest extends KeycloakTest { + + @BeforeAll + public static void restartKeycloak() { + keycloak.stop(); + keycloak.start(); + } + + @Test + @Order(1) + public void test_6_CheckProductNameOnWelcomePage() { + page.navigate(keycloak.getBaseUrl()); + Assertions.assertTrue("Welcome to Keycloak".equals(page.title())); + } + @Test + @Order(2) + public void test_1_LocalAccessNoAdmin() { + page.navigate(keycloak.getBaseUrl()); + Locator passwordInput = page.locator("input[name=\"passwordConfirmation\"]"); + Assertions.assertTrue(passwordInput.inputValue().isEmpty(), "Welcome page did not ask to create a new admin user."); + } + + @Test + @Order(3) + public void test_2_RemoteAccessNoAdmin() throws Exception { + page.navigate(getPublicServerUrl().toString()); + Locator text = page.getByText("Local access required"); + Assertions.assertTrue(text.isVisible(), "Remote access should have not been allowed."); + } + + @Test + @Order(4) + public void test_3_LocalAccessWithAdmin() throws Exception { + page.navigate(keycloak.getBaseUrl()); + Locator usernameInput = page.locator("input[name=\"username\"]"); + usernameInput.fill("admin"); + + Locator passwordInput = page.locator("input[name=\"password\"]"); + passwordInput.fill("admin"); + + Locator passwordConfirmationInput = page.locator("input[name=\"passwordConfirmation\"]"); + passwordConfirmationInput.fill("admin"); + + Locator submitButton = page.locator("button[type=\"submit\"]"); + submitButton.click(); + Assertions.assertTrue(page.getByText("User created").isVisible()); + } + + /** + * Attempt to resolve the floating IP address. This is where EAP/WildFly + * will be accessible. See "-Djboss.bind.address=0.0.0.0". + * + * @return + * @throws Exception + */ + private String getFloatingIpAddress() throws Exception { + Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface ni : Collections.list(netInterfaces)) { + Enumeration inetAddresses = ni.getInetAddresses(); + for (InetAddress a : Collections.list(inetAddresses)) { + if (!a.isLoopbackAddress() && a.isSiteLocalAddress()) { + return a.getHostAddress(); + } + } + } + return null; + } + + private URL getPublicServerUrl() throws Exception { + String floatingIp = getFloatingIpAddress(); + if (floatingIp == null) { + throw new RuntimeException("Could not determine floating IP address."); + } + return new URL("http", floatingIp, keycloak.getPort(), ""); + } + + @Override + public void addTestRealms(List testRealms) { + + } +} diff --git a/testsuite/integration-playwright/src/test/resources/realm/testrealm.json b/testsuite/integration-playwright/src/test/resources/realm/testrealm.json new file mode 100644 index 000000000000..9f7dcbf17ab5 --- /dev/null +++ b/testsuite/integration-playwright/src/test/resources/realm/testrealm.json @@ -0,0 +1,215 @@ +{ + "id": "test", + "realm": "test", + "enabled": true, + "sslRequired": "external", + "defaultRoles": [ "user" ], + "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" ] + } + } + ], + "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" + }, + { + "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" + } + } + ] + } + ], + "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" + ] + } + } + ], + "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" + ] + } + } + } + ] + } + }, + + "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" + } + ], + "eventsListeners": ["jboss-logging"] +} diff --git a/testsuite/pom.xml b/testsuite/pom.xml index b8f8a84e8bf0..752848fdafdd 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -52,6 +52,7 @@ db-allocator-plugin integration-arquillian + integration-playwright model utils