Move UserTest.java to the new testsuite

Part of: #34494

Signed-off-by: Lukas Hanusovsky <lhanusov@redhat.com>
This commit is contained in:
Lukas Hanusovsky 2025-05-19 12:59:14 +02:00 committed by Stian Thorgersen
parent 5c930c1f73
commit 788e981917
38 changed files with 5800 additions and 3990 deletions

View File

@ -1,6 +1,10 @@
package org.keycloak.testframework.realm;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.utils.HmacOTP;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.Arrays;
@ -88,6 +92,23 @@ public class UserConfigBuilder {
return this;
}
public UserConfigBuilder federatedLink(String identityProvider, String federatedUserId, String federatedUsername) {
FederatedIdentityRepresentation federatedIdentity = new FederatedIdentityRepresentation();
federatedIdentity.setUserId(federatedUserId);
federatedIdentity.setUserName(federatedUsername);
federatedIdentity.setIdentityProvider(identityProvider);
rep.setFederatedIdentities(Collections.combine(rep.getFederatedIdentities(), federatedIdentity));
return this;
}
public UserConfigBuilder totpSecret(String totpSecret) {
rep.setCredentials(Collections.combine(rep.getCredentials(), ModelToRepresentation.toRepresentation(
OTPCredentialModel.createTOTP(totpSecret, 6, 30, HmacOTP.HMAC_SHA1))));
rep.setTotp(true);
return this;
}
/**
* Best practice is to use other convenience methods when configuring a user, but while the framework is under
* active development there may not be a way to perform all updates required. In these cases this method allows

View File

@ -134,7 +134,7 @@ public class OAuthClientTest {
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(authzResponse.getCode());
oauth.logoutForm().idTokenHint(accessTokenResponse.getIdToken()).open();
oauth.loginForm().open();
Assertions.assertTrue(loginPage.isActivePage());
loginPage.assertCurrent();
}
public static class OAuthUserConfig implements UserConfig {

View File

@ -2,7 +2,9 @@ package org.keycloak.test.examples;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.ui.annotations.InjectPage;
import org.keycloak.testframework.ui.annotations.InjectWebDriver;
import org.keycloak.testframework.ui.page.LoginPage;
@ -13,6 +15,8 @@ import org.openqa.selenium.htmlunit.HtmlUnitDriver;
@KeycloakIntegrationTest
public class PagesTest {
@InjectRealm(ref = "master", attachTo = "master")
ManagedRealm masterRealm;
@InjectWebDriver
WebDriver webDriver;
@ -25,13 +29,16 @@ public class PagesTest {
@Test
public void testLoginFromWelcome() {
masterRealm.admin().users().searchByUsername("admin", true)
.stream().findFirst().ifPresent(admin ->
masterRealm.admin().users().delete(admin.getId()));
welcomePage.navigateTo();
if (welcomePage.isActivePage()) {
welcomePage.fillRegistration("admin", "admin");
welcomePage.submit();
welcomePage.clickOpenAdminConsole();
}
welcomePage.assertCurrent();
welcomePage.fillRegistration("admin", "admin");
welcomePage.submit();
welcomePage.clickOpenAdminConsole();
if (webDriver instanceof HtmlUnitDriver) {
String pageId = webDriver.findElement(By.xpath("//body")).getAttribute("data-page-id");
@ -40,7 +47,7 @@ public class PagesTest {
} else {
loginPage.waitForPage();
Assertions.assertTrue(loginPage.isActivePage());
loginPage.assertCurrent();
loginPage.fillLogin("admin", "admin");
loginPage.submit();

View File

@ -1,5 +1,6 @@
package org.keycloak.testframework.ui.page;
import org.junit.jupiter.api.Assertions;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@ -34,8 +35,11 @@ public abstract class AbstractPage {
}
}
public boolean isActivePage() {
private boolean isActivePage() {
return getExpectedPageId().equals(getCurrentPageId());
}
public void assertCurrent() {
Assertions.assertEquals(getExpectedPageId(), getCurrentPageId(), "Not on the expected page");
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.testframework.ui.page;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ErrorPage extends AbstractPage {
@FindBy(className = "instruction")
private WebElement errorMessage;
@FindBy(id = "backToApplication")
private WebElement backToApplicationLink;
public ErrorPage(WebDriver driver) {
super(driver);
}
public String getError() {
return errorMessage.getText();
}
public void clickBackToApplication() {
backToApplicationLink.click();
}
public String getBackToApplicationLink() {
if (backToApplicationLink == null) {
return null;
} else {
return backToApplicationLink.getAttribute("href");
}
}
@Override
public String getExpectedPageId() {
return "login-error";
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.testframework.ui.page;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class InfoPage extends AbstractPage {
@FindBy(className = "instruction")
private WebElement infoMessage;
public InfoPage(WebDriver driver) {
super(driver);
}
public String getInfo() {
return infoMessage.getText();
}
@Override
public String getExpectedPageId() {
return "login-info";
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.testframework.ui.page;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LoginPasswordUpdatePage extends AbstractPage {
@FindBy(id = "password-new")
private WebElement newPasswordInput;
@FindBy(id = "password-confirm")
private WebElement passwordConfirmInput;
@FindBy(css = "[type=\"submit\"]")
private WebElement submitButton;
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
private WebElement loginErrorMessage;
@FindBy(className = "kc-feedback-text")
private WebElement feedbackMessage;
@FindBy(name = "cancel-aia")
private WebElement cancelAIAButton;
public LoginPasswordUpdatePage(WebDriver driver) {
super(driver);
}
public void changePassword(String newPassword, String passwordConfirm) {
newPasswordInput.sendKeys(newPassword);
passwordConfirmInput.sendKeys(passwordConfirm);
submitButton.click();
}
public void cancel() {
cancelAIAButton.click();
}
public String getError() {
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
}
public String getFeedbackMessage() {
return feedbackMessage.getText();
}
public boolean isCancelDisplayed() {
return cancelAIAButton.isDisplayed();
}
@Override
public String getExpectedPageId() {
return "login-login-update-password";
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.testframework.ui.page;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
*
* @author hmlnarik
*/
public class ProceedPage extends AbstractPage {
@FindBy(className = "instruction")
private WebElement infoMessage;
@FindBy(linkText = "» Click here to proceed")
private WebElement proceedLink;
public ProceedPage(WebDriver driver) {
super(driver);
}
public String getInfo() {
return infoMessage.getText();
}
public void clickProceedLink() {
proceedLink.click();
}
@Override
public String getExpectedPageId() {
return "login-info";
}
}

View File

@ -215,7 +215,7 @@ public class ImpersonationTest {
public void testImpersonationWorksWhenAuthenticationSessionExists() throws Exception {
// Open the URL for the client (will redirect to Keycloak server AuthorizationEndpoint and create authenticationSession)
oauth.openLoginForm();
Assertions.assertTrue(loginPage.isActivePage());
loginPage.assertCurrent();
// Impersonate and get SSO cookie. Setup that cookie for webDriver
for (Cookie cookie : testSuccessfulImpersonation("realm-admin", managedRealm.getName())) {

View File

@ -0,0 +1,265 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.credential.CredentialModel;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.utils.StripSecretsUtils;
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.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.annotations.InjectAdminEvents;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.events.AdminEvents;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.tests.utils.runonserver.RunHelpers;
import org.keycloak.testframework.remote.runonserver.InjectRunOnServer;
import org.keycloak.testframework.remote.runonserver.RunOnServerClient;
import org.keycloak.testframework.ui.annotations.InjectPage;
import org.keycloak.testframework.ui.annotations.InjectWebDriver;
import org.keycloak.testframework.ui.page.LoginPage;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.testsuite.util.userprofile.UserProfileUtil;
import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.WebDriver;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AbstractUserTest {
@InjectRealm
ManagedRealm managedRealm;
@InjectAdminClient
Keycloak adminClient;
@InjectAdminEvents
AdminEvents adminEvents;
@InjectWebDriver
WebDriver driver;
@InjectRunOnServer
RunOnServerClient runOnServer;
@InjectPage
LoginPage loginPage;
protected Set<String> 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 {
UserProfileUtil.setUserProfileConfiguration(managedRealm.admin(), null);
UPConfig upConfig = managedRealm.admin().users().userProfile().getConfiguration();
for (String name : managedAttributes) {
upConfig.addOrReplaceAttribute(createAttributeMetadata(name));
}
UserProfileUtil.setUserProfileConfiguration(managedRealm.admin(), JsonSerialization.writeValueAsString(upConfig));
adminEvents.clear();
}
@AfterEach
public void after() {
managedRealm.admin().identityProviders().findAll()
.forEach(ip -> managedRealm.admin().identityProviders().get(ip.getAlias()).remove());
managedRealm.admin().groups().groups()
.forEach(g -> managedRealm.admin().groups().group(g.getId()).remove());
}
protected String createUser() {
return createUser("user1", "user1@localhost");
}
protected 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);
}
protected String createUser(UserRepresentation userRep) {
return createUser(userRep, true);
}
protected String createUser(UserRepresentation userRep, boolean assertAdminEvent) {
final String createdId;
try (Response response = managedRealm.admin().users().create(userRep)) {
createdId = ApiUtil.getCreatedId(response);
}
managedRealm.cleanup().add(r -> r.users().get(createdId).remove());
StripSecretsUtils.stripSecrets(null, userRep);
if (assertAdminEvent) {
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.userResourcePath(createdId), userRep,
ResourceType.USER);
}
return createdId;
}
protected void updateUser(UserResource user, UserRepresentation userRep) {
user.update(userRep);
List<CredentialRepresentation> credentials = userRep.getCredentials();
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.userResourcePath(userRep.getId()), StripSecretsUtils.stripSecrets(null, userRep), ResourceType.USER);
userRep.setCredentials(credentials);
}
protected UPAttribute createAttributeMetadata(String name) {
UPAttribute attribute = new UPAttribute();
attribute.setName(name);
attribute.setMultivalued(true);
UPAttributePermissions permissions = new UPAttributePermissions();
permissions.setEdit(Set.of("user", "admin"));
attribute.setPermissions(permissions);
this.managedAttributes.add(name);
return attribute;
}
protected CredentialModel fetchCredentials(String username) {
return runOnServer.fetch(RunHelpers.fetchCredentials(username));
}
protected List<String> createUsers() {
List<String> 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", Arrays.asList("common", "common2"));
ids.add(createUser(user));
}
return ids;
}
private void addAttribute(UserRepresentation user, String name, List<String> values) {
Map<String, List<String>> attributes = Optional.ofNullable(user.getAttributes()).orElse(new HashMap<>());
attributes.put(name, values);
managedAttributes.add(name);
user.setAttributes(attributes);
}
protected void deleteUser(String id) {
try (Response response = managedRealm.admin().users().delete(id)) {
assertEquals(204, response.getStatus());
}
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.userResourcePath(id), ResourceType.USER);
}
protected void addFederatedIdentity(String keycloakUserId, String identityProviderAlias1,
FederatedIdentityRepresentation link) {
Response response1 = managedRealm.admin().users().get(keycloakUserId).addFederatedIdentity(identityProviderAlias1, link);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE,
AdminEventPaths.userFederatedIdentityLink(keycloakUserId, identityProviderAlias1), link,
ResourceType.USER);
assertEquals(204, response1.getStatus());
}
protected void addSampleIdentityProvider() {
addSampleIdentityProvider("social-provider-id", 0);
}
protected void addSampleIdentityProvider(final String alias, final int expectedInitialIdpCount) {
List<IdentityProviderRepresentation> providers = managedRealm.admin().identityProviders().findAll();
Assertions.assertEquals(expectedInitialIdpCount, providers.size());
IdentityProviderRepresentation rep = new IdentityProviderRepresentation();
rep.setAlias(alias);
rep.setProviderId("oidc");
managedRealm.admin().identityProviders().create(rep);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.identityProviderPath(rep.getAlias()), rep, ResourceType.IDENTITY_PROVIDER);
}
protected String mapToSearchQuery(Map<String, String> search) {
return search.entrySet()
.stream()
.map(e -> String.format("%s:%s", e.getKey(), e.getValue()))
.collect(Collectors.joining(" "));
}
protected void switchEditUsernameAllowedOn(boolean enable) {
RealmRepresentation rep = managedRealm.admin().toRepresentation();
managedRealm.cleanup().add(r -> r.update(rep));
rep.setEditUsernameAllowed(enable);
managedRealm.admin().update(rep);
AdminEventAssertion.assertSuccess(adminEvents.poll()).operationType(OperationType.UPDATE).representation(rep).resourceType(ResourceType.REALM);
}
protected void switchRegistrationEmailAsUsername(boolean enable) {
RealmRepresentation rep = managedRealm.admin().toRepresentation();
managedRealm.cleanup().add(r -> r.update(rep));
rep.setRegistrationEmailAsUsername(enable);
managedRealm.admin().update(rep);
AdminEventAssertion.assertSuccess(adminEvents.poll()).operationType(OperationType.UPDATE).representation(rep).resourceType(ResourceType.REALM);
}
protected static <T> T loadJson(InputStream is, Class<T> type) {
try {
return JsonSerialization.readValue(is, type);
} catch (IOException e) {
throw new RuntimeException("Failed to parse json", e);
}
}
}

View File

@ -0,0 +1,256 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.BadRequestException;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KeycloakIntegrationTest(config = UserAttributesTest.UserAttributesServerConfig.class)
public class UserAttributesTest extends AbstractUserTest {
@Test
public void countByAttribute() {
createUsers();
Map<String, String> attributes = new HashMap<>();
attributes.put("test1", "test2");
assertThat(managedRealm.admin().users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(0));
attributes = new HashMap<>();
attributes.put("test", "test1");
assertThat(managedRealm.admin().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(managedRealm.admin().users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(1));
attributes = new HashMap<>();
attributes.put("attr", "common");
assertThat(managedRealm.admin().users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(9));
attributes = new HashMap<>();
attributes.put("attr", "common");
attributes.put(UserModel.EXACT, Boolean.FALSE.toString());
assertThat(managedRealm.admin().users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(9));
}
@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<String> vals = new ArrayList<>();
vals.add("value2user2");
vals.add("value2user2_2");
user2.getAttributes().put("attr2", vals);
String user2Id = createUser(user2);
user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
assertEquals(2, user1.getAttributes().size());
assertAttributeValue("value1user1", user1.getAttributes().get("attr1"));
assertAttributeValue("value2user1", user1.getAttributes().get("attr2"));
user2 = managedRealm.admin().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(managedRealm.admin().users().get(user1Id), user1);
user1 = managedRealm.admin().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(managedRealm.admin().users().get(user1Id), user1);
user1 = managedRealm.admin().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(managedRealm.admin().users().get(user1Id), user1);
user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
assertNotNull(user1.getAttributes());
assertEquals(2, user1.getAttributes().size());
// empty attributes should remove attributes
user1.setAttributes(Collections.emptyMap());
updateUser(managedRealm.admin().users().get(user1Id), user1);
user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
assertNull(user1.getAttributes());
Map<String, List<String>> attributes = new HashMap<>();
attributes.put("foo", List.of("foo"));
attributes.put("bar", List.of("bar"));
user1.setAttributes(attributes);
managedRealm.admin().users().get(user1Id).update(user1);
user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
assertEquals(2, user1.getAttributes().size());
user1.getAttributes().remove("foo");
managedRealm.admin().users().get(user1Id).update(user1);
user1 = managedRealm.admin().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 = managedRealm.admin().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(managedRealm.admin().users().get(user1Id), user1);
Assertions.fail("Not supposed to successfully update user");
} catch (BadRequestException expected) {
// Expected
Assertions.assertNull(adminEvents.poll());
ErrorRepresentation error = expected.getResponse().readEntity(ErrorRepresentation.class);
Assertions.assertEquals("updateReadOnlyAttributesRejectedMessage", error.getErrorMessage());
}
// The same test as before, but with the case-sensitivity used
try {
user1.getAttributes().remove(LDAPConstants.LDAP_ID);
user1.singleAttribute("LDap_Id", "baz");
updateUser(managedRealm.admin().users().get(user1Id), user1);
Assertions.fail("Not supposed to successfully update user");
} catch (BadRequestException bre) {
// Expected
Assertions.assertNull(adminEvents.poll());
}
// Attribute "deniedSomeAdmin" was denied for administrator
try {
user1.getAttributes().remove("LDap_Id");
user1.singleAttribute("deniedSomeAdmin", "baz");
updateUser(managedRealm.admin().users().get(user1Id), user1);
Assertions.fail("Not supposed to successfully update user");
} catch (BadRequestException bre) {
// Expected
Assertions.assertNull(adminEvents.poll());
}
// usercertificate and saml attribute are allowed by admin
user1.getAttributes().remove("deniedSomeAdmin");
updateUser(managedRealm.admin().users().get(user1Id), user1);
user1 = managedRealm.admin().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(UserAttributesTest.class.getResourceAsStream("testrealm-user-null-attr.json"), RealmRepresentation.class);
adminClient.realms().create(rep);
List<UserRepresentation> users = adminClient.realms().realm("test-user-null-attr").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 ] shouldn't be imported
assertThat(users.get(0).getAttributes().size(), equalTo(2));
}
@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 = managedRealm.admin().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(managedRealm.admin().users().get(userId), update);
UserRepresentation updated = managedRealm.admin().users().get(userId).toRepresentation();
assertThat(updated.getUsername(), equalTo(userName));
assertThat(updated.getAttributes().get("phoneNumber"), equalTo(List.of("123")));
assertThat(updated.getEmail(), equalTo(email));
}
private void assertAttributeValue(String expectedValue, List<String> attrValues) {
Assertions.assertEquals(1, attrValues.size());
Assertions.assertEquals(expectedValue, attrValues.get(0));
}
public static class UserAttributesServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder builder) {
builder.option("spi-user-profile-declarative-user-profile-admin-read-only-attributes", "deniedSomeAdmin");
return builder;
}
}
}

View File

@ -0,0 +1,579 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.Base64;
import org.keycloak.credential.CredentialModel;
import org.keycloak.crypto.hash.Argon2Parameters;
import org.keycloak.crypto.hash.Argon2PasswordHashProviderFactory;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testframework.admin.AdminClientFactory;
import org.keycloak.testframework.annotations.InjectAdminClientFactory;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.tests.utils.Assert;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.util.RoleBuilder;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.endsWith;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@KeycloakIntegrationTest(config = UserCreateTest.UserCreateServerConf.class)
public class UserCreateTest extends AbstractUserTest {
@InjectRealm(lifecycle = LifeCycle.METHOD)
ManagedRealm managedRealm;
@InjectAdminClientFactory
AdminClientFactory clientFactory;
@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);
managedRealm.admin().users().get(userId).resetPassword(credTmp);
CredentialRepresentation credPerm = new CredentialRepresentation();
credPerm.setType(CredentialRepresentation.PASSWORD);
credPerm.setValue("perm");
credPerm.setTemporary(null);
managedRealm.admin().users().get(userId).resetPassword(credPerm);
UserRepresentation userRep = managedRealm.admin().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 = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
Assertions.assertNull(adminEvents.poll());
// 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 = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
assertNull(adminEvents.poll());
// Alternative way of showing underlying error message
try {
CreatedResponseUtil.getCreatedId(response);
Assertions.fail("Not expected getCreatedId to success");
} catch (WebApplicationException wae) {
MatcherAssert.assertThat(wae.getMessage(), endsWith("ErrorMessage: User exists with same email"));
}
}
}
@Test
public void createDuplicatedUsernameWithEmail() {
createUser("user1@local.com", "user1@local.org");
UserRepresentation user = new UserRepresentation();
user.setUsername("user1@local.org");
user.setEmail("user2@localhost");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
assertNull(adminEvents.poll());
ErrorRepresentation error = response.readEntity(ErrorRepresentation.class);
Assertions.assertEquals("User exists with same username", error.getErrorMessage());
}
}
@Test
public void createDuplicatedEmailWithUsername() {
createUser("user1@local.com", "user1@local.org");
UserRepresentation user = new UserRepresentation();
user.setUsername("user2");
user.setEmail("user1@local.com");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
assertNull(adminEvents.poll());
ErrorRepresentation error = response.readEntity(ErrorRepresentation.class);
Assertions.assertEquals("User exists with same email", error.getErrorMessage());
}
}
//KEYCLOAK-14611
@Test
public void createDuplicateEmailWithExistingDuplicates() {
//Allow duplicate emails
RealmRepresentation rep = managedRealm.admin().toRepresentation();
rep.setDuplicateEmailsAllowed(true);
managedRealm.admin().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);
managedRealm.admin().update(rep);
//Create a third user with the same email
user.setUsername("user3");
adminEvents.clear();
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
ErrorRepresentation error = response.readEntity(ErrorRepresentation.class);
Assertions.assertEquals("User exists with same username or email", error.getErrorMessage());
assertNull(adminEvents.poll());
}
}
@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(credentialHashed, "Expecting credential");
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);
Assertions.assertNotNull(deprecatedHashedPassword.getHashedSaltedValue());
Assertions.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(credentialHashed, "Expecting credential");
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 createUserWithTemporaryCredentials() {
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 = managedRealm.admin().users().get(userId).toRepresentation();
Assertions.assertEquals(1, userRep.getRequiredActions().size());
Assertions.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(credential, "Expecting credential");
PasswordCredentialModel pcm = PasswordCredentialModel.createFromCredentialModel(credential);
assertEquals(Argon2PasswordHashProviderFactory.ID, pcm.getPasswordCredentialData().getAlgorithm());
assertEquals(Argon2Parameters.DEFAULT_ITERATIONS, pcm.getPasswordCredentialData().getHashIterations());
assertNotEquals("ABCD", pcm.getPasswordSecretData().getValue());
assertEquals(CredentialRepresentation.PASSWORD, credential.getType());
}
@Test
public void createDuplicatedUser3() {
createUser();
UserRepresentation user = new UserRepresentation();
user.setUsername("User1");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void createDuplicatedUser4() {
createUser();
UserRepresentation user = new UserRepresentation();
user.setUsername("USER1");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void createDuplicatedUser5() {
createUser();
UserRepresentation user = new UserRepresentation();
user.setUsername("user2");
user.setEmail("User1@localhost");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void createDuplicatedUser6() {
createUser();
UserRepresentation user = new UserRepresentation();
user.setUsername("user2");
user.setEmail("user1@LOCALHOST");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void createDuplicatedUser7() {
createUser("user1", "USer1@Localhost");
UserRepresentation user = new UserRepresentation();
user.setUsername("user2");
user.setEmail("user1@localhost");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(409, response.getStatus());
Assertions.assertNull(adminEvents.poll());
}
}
// 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());
managedRealm.admin().components().add(dummyFederationProvider);
AdminEventAssertion.assertEvent(adminEvents.poll(), 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 = managedRealm.admin().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 = managedRealm.admin().users().create(user)) {
assertEquals(400, response.getStatus());
ErrorRepresentation error = response.readEntity(ErrorRepresentation.class);
Assertions.assertEquals("User name is missing", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void createUserWithEmailAsUsername() {
switchRegistrationEmailAsUsername(true);
switchEditUsernameAllowedOn(false);
String id = createUser();
UserResource user = managedRealm.admin().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 = managedRealm.admin().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 = managedRealm.admin().users().get(id);
userRep = user.toRepresentation();
assertEquals("user1", userRep.getUsername());
assertEquals("user1@localhost", userRep.getEmail());
deleteUser(id);
switchRegistrationEmailAsUsername(false);
switchEditUsernameAllowedOn(false);
id = createUser();
user = managedRealm.admin().users().get(id);
userRep = user.toRepresentation();
assertEquals("user1", userRep.getUsername());
assertEquals("user1@localhost", userRep.getEmail());
}
@Test
public void createUserWithEmptyUsername() {
UserRepresentation user = new UserRepresentation();
user.setUsername("");
user.setEmail("user2@localhost");
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(400, response.getStatus());
ErrorRepresentation error = response.readEntity(ErrorRepresentation.class);
Assertions.assertEquals("User name is missing", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void createUserWithInvalidPolicyPassword() {
RealmRepresentation rep = managedRealm.admin().toRepresentation();
String passwordPolicy = rep.getPasswordPolicy();
rep.setPasswordPolicy("length(8)");
managedRealm.admin().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));
adminEvents.clear();
try (Response response = managedRealm.admin().users().create(user)) {
assertEquals(400, response.getStatus());
OAuth2ErrorRepresentation error = response.readEntity(OAuth2ErrorRepresentation.class);
Assertions.assertEquals("invalidPasswordMinLengthMessage", error.getError());
Assertions.assertEquals("Invalid password: minimum length 8.", error.getErrorDescription());
rep.setPasswordPolicy(passwordPolicy);
Assertions.assertNull(adminEvents.poll());
managedRealm.admin().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 = managedRealm.admin().users().get(userId).toRepresentation();
assertNotNull(createdUser);
assertEquals(user.getCreatedTimestamp(), createdUser.getCreatedTimestamp());
}
@Test
public void failCreateUserUsingRegularUser() throws Exception {
managedRealm.admin().users().create(UserConfigBuilder.create().username("regular-user").password("password").email("regular@local").name("Regular", "User").build());
try (Keycloak localAdminClient = clientFactory.create()
.realm(managedRealm.getName()).username("regular-user").password("password")
.clientId(Constants.ADMIN_CLI_CLIENT_ID).build()) {
UserRepresentation invalidUser = new UserRepresentation();
invalidUser.setUsername("do-not-create-me");
Response response = localAdminClient.realm("default").users().create(invalidUser);
Assert.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
invalidUser.setGroups(Collections.emptyList());
response = localAdminClient.realm("default").users().create(invalidUser);
Assert.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
}
}
@Test
public void testCreateUserDoNotGrantRole() {
managedRealm.admin().roles().create(RoleBuilder.create().name("realm-role").build());
try {
UserRepresentation userRep = UserConfigBuilder.create().username("alice").password("password").roles("realm-role")
.build();
String userId = ApiUtil.getCreatedId(managedRealm.admin().users().create(userRep));
UserResource user = managedRealm.admin().users().get(userId);
List<RoleRepresentation> realmMappings = user.roles().getAll().getRealmMappings();
assertFalse(realmMappings.stream().map(RoleRepresentation::getName).anyMatch("realm-role"::equals));
} finally {
managedRealm.admin().roles().get("realm-role").remove();
}
}
@Test
public void testDefaultCharacterValidationOnUsername() {
List<String> invalidNames = List.of("1user\\\\", "2user\\\\%", "3user\\\\*", "4user\\\\_");
for (String invalidName : invalidNames) {
UserRepresentation invalidUser = UserConfigBuilder.create().username(invalidName).email("test@invalid.org").build();
Response response = managedRealm.admin().users().create(invalidUser);
Assert.assertEquals(400, response.getStatus());
Assert.assertEquals("error-username-invalid-character", response.readEntity(ErrorRepresentation.class).getErrorMessage());
}
}
public static class UserCreateServerConf implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder builder) {
return builder.dependency("org.keycloak.tests", "keycloak-tests-custom-providers");
}
}
}

View File

@ -0,0 +1,360 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.InjectUser;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
import org.keycloak.testframework.realm.ManagedUser;
import org.keycloak.testframework.realm.UserConfig;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@KeycloakIntegrationTest
public class UserCredentialTest extends AbstractUserTest {
@InjectOAuthClient
OAuthClient oauth;
@InjectUser(ref = "user-with-one-configured-otp", config = UserCredentialOtp1UserConf.class)
ManagedUser userOtp1;
@InjectUser(ref = "user-with-two-configured-otp", config = UserCredentialOtp2UserConf.class)
ManagedUser userOtp2;
@InjectUser(ref = "john-doh@localhost", config = UserCredentialJohnDohUserConf.class)
ManagedUser johnDoh;
@InjectUser(ref = "test-user@localhost", config = UserCredentialTestUserConf.class)
ManagedUser testUser;
@Test
public void resetUserPassword() {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@localhost").build();
String userId = createUser(userRep);
CredentialRepresentation cred = new CredentialRepresentation();
cred.setType(CredentialRepresentation.PASSWORD);
cred.setValue("paSSw0rd");
cred.setTemporary(false);
managedRealm.admin().users().get(userId).resetPassword(cred);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResetPasswordPath(userId), ResourceType.USER);
oauth.openLoginForm();
loginPage.assertCurrent();
loginPage.fillLogin("user1", "paSSw0rd");
loginPage.submit();
assertTrue(driver.getPageSource().contains("Happy days"));
AccountHelper.logout(managedRealm.admin(), "user1");
}
@Test
public void resetUserInvalidPassword() {
String userId = createUser("user1", "user1@localhost");
try {
CredentialRepresentation cred = new CredentialRepresentation();
cred.setType(CredentialRepresentation.PASSWORD);
cred.setValue(" ");
cred.setTemporary(false);
managedRealm.admin().users().get(userId).resetPassword(cred);
fail("Expected failure");
} catch (ClientErrorException e) {
assertEquals(400, e.getResponse().getStatus());
e.getResponse().close();
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void loginShouldFailAfterPasswordDeleted() {
String userName = "credential-tester";
String userPass = "s3cr37";
UserRepresentation userRep = UserConfigBuilder.create()
.username(userName).password(userPass).name("credential", "tester").email("credential@tester").build();
String userId = createUser(userRep);
oauth.openLoginForm();
loginPage.assertCurrent();
loginPage.fillLogin(userName, userPass);
loginPage.submit();
assertTrue(driver.getPageSource().contains("Happy days"), "Test user should be successfully logged in.");
AccountHelper.logout(managedRealm.admin(), userName);
Optional<CredentialRepresentation> passwordCredential =
managedRealm.admin().users().get(userId).credentials().stream()
.filter(c -> CredentialRepresentation.PASSWORD.equals(c.getType()))
.findFirst();
assertTrue(passwordCredential.isPresent(), "Test user should have a password credential set.");
managedRealm.admin().users().get(userId).removeCredential(passwordCredential.get().getId());
oauth.openLoginForm();
loginPage.assertCurrent();
loginPage.fillLogin(userName, userPass);
loginPage.submit();
assertTrue(driver.getCurrentUrl().contains(String.format("/realms/%s/login-actions/authenticate", managedRealm.getName())), "Test user should fail to log in after password was deleted.");
}
@Test
public void testUpdateCredentials() {
// both credentials have a null priority - stable ordering is not guaranteed between calls
// Get user user-with-one-configured-otp and assert he has no label linked to its OTP credential
UserResource user = userOtp1.admin();
CredentialRepresentation otpCred = user.credentials().stream().filter(cr -> "otp".equals(cr.getType()))
.findFirst().orElseThrow();
Assertions.assertNull(otpCred.getUserLabel());
// Set and check a new label
String newLabel = "the label";
user.setCredentialUserLabel(otpCred.getId(), newLabel);
Assertions.assertEquals(newLabel, user.credentials().stream().filter(cr -> cr.getId().equals(otpCred.getId()))
.findFirst().orElseThrow().getUserLabel());
}
@Test
public void testShouldFailToSetCredentialUserLabelWhenLabelIsEmpty() {
UserResource user = userOtp1.admin();
CredentialRepresentation otpCred = user.credentials().get(0);
BadRequestException ex = Assertions.assertThrows(BadRequestException.class, () -> {
user.setCredentialUserLabel(otpCred.getId(), " ");
});
Response response = ex.getResponse();
String body = response.readEntity(String.class);
Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
Assertions.assertTrue(body.contains("missingCredentialLabel"));
Assertions.assertTrue(body.contains("Credential label must not be empty"));
}
@Test
public void testShouldFailToSetCredentialUserLabelWhenLabelAlreadyExists() {
UserResource user = userOtp2.admin();
List<CredentialRepresentation> credentials = user.credentials().stream()
.filter(c -> c.getType().equals(OTPCredentialModel.TYPE))
.toList();
Assertions.assertEquals(2, credentials.size());
String firstId = credentials.get(0).getId();
String secondId = credentials.get(1).getId();
user.setCredentialUserLabel(firstId, "Device");
user.setCredentialUserLabel(secondId, "Second Device");
// Attempt to update second credential to use the same label as the first
ClientErrorException ex = Assertions.assertThrows(ClientErrorException.class, () -> {
user.setCredentialUserLabel(secondId, "Device");
});
Response response = ex.getResponse();
Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus());
String body = response.readEntity(String.class);
Assertions.assertNotNull(body);
Assertions.assertTrue(body.contains("Device already exists with the same name"));
}
@Test
public void testDeleteCredentials() {
UserResource user = johnDoh.admin();
List<CredentialRepresentation> creds = user.credentials();
Assertions.assertEquals(1, creds.size());
CredentialRepresentation credPasswd = creds.get(0);
Assertions.assertEquals("password", credPasswd.getType());
// Remove password
user.removeCredential(credPasswd.getId());
Assertions.assertEquals(0, user.credentials().size());
// Restore password
credPasswd.setValue("password");
user.resetPassword(credPasswd);
Assertions.assertEquals(1, user.credentials().size());
}
@Test
public void testCRUDCredentialsOfDifferentUser() {
// Get credential ID of the OTP credential of the user1
UserResource user1 = userOtp1.admin();
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(managedRealm.admin(), testUser.getUsername());
try {
user2.setCredentialUserLabel(otpCredential.getId(), "new-label");
Assertions.fail("Not expected to successfully update user label");
} catch (NotFoundException nfe) {
// Expected
}
try {
user2.moveCredentialToFirst(otpCredential.getId());
Assertions.fail("Not expected to successfully move credential");
} catch (NotFoundException nfe) {
// Expected
}
try {
user2.removeCredential(otpCredential.getId());
Assertions.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();
Assertions.assertTrue(ObjectUtil.isEqualOrBothNull(otpCredential.getUserLabel(), otpCredentialLoaded.getUserLabel()));
Assertions.assertTrue(ObjectUtil.isEqualOrBothNull(otpCredential.getPriority(), otpCredentialLoaded.getPriority()));
}
@Test
public void testGetAndMoveCredentials() {
UserResource user = userOtp2.admin();
List<CredentialRepresentation> creds = user.credentials();
List<String> 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<String> 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());
}
@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 = adminEvents.poll().getRepresentation();
assertEquals(
JsonSerialization.writeValueAsString(user),
actualRepresentation
);
}
private void assertSameIds(List<String> expectedIds, List<CredentialRepresentation> actual) {
Assertions.assertEquals(expectedIds.size(), actual.size());
for (int i = 0; i < expectedIds.size(); i++) {
Assertions.assertEquals(expectedIds.get(i), actual.get(i).getId());
}
}
private static class UserCredentialJohnDohUserConf implements UserConfig {
@Override
public UserConfigBuilder configure(UserConfigBuilder builder) {
builder.username("john-doh@localhost");
builder.password("password");
builder.name("John", "Doh");
builder.email("john-doh@localhost");
builder.emailVerified();
return builder;
}
}
private static class UserCredentialTestUserConf implements UserConfig {
@Override
public UserConfigBuilder configure(UserConfigBuilder builder) {
builder.username("test-user@localhost");
builder.password("password");
builder.name("Tom", "Brady");
builder.email("test-user@localhost");
builder.emailVerified();
return builder;
}
}
private static class UserCredentialOtp1UserConf implements UserConfig {
@Override
public UserConfigBuilder configure(UserConfigBuilder builder) {
builder.username("user-with-one-configured-otp");
builder.password("password");
builder.name("Otp", "1");
builder.email("otp1@redhat.com");
builder.emailVerified();
builder.totpSecret("DJmQfC73VGFhw7D4QJ8A");
return builder;
}
}
private static class UserCredentialOtp2UserConf implements UserConfig {
@Override
public UserConfigBuilder configure(UserConfigBuilder builder) {
builder.username("user-with-two-configured-otp");
builder.password("password");
builder.name("Otp", "2");
builder.email("otp2@redhat.com");
builder.emailVerified();
builder.totpSecret("DJmQfC73VGFhw7D4QJ8A");
builder.totpSecret("ABCQfC73VGFhw7D4QJ8A");
return builder;
}
}
}

View File

@ -0,0 +1,30 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.tests.utils.admin.ApiUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
@KeycloakIntegrationTest
public class UserDeleteTest extends AbstractUserTest {
@Test
public void delete() {
String userId = ApiUtil.getCreatedId(managedRealm.admin().users().create(UserConfigBuilder.create().username("user1").email("user1@localhost.com").build()));
AdminEventAssertion.assertSuccess(adminEvents.poll());
deleteUser(userId);
}
@Test
public void deleteNonExistent() {
try (Response response = managedRealm.admin().users().delete("does-not-exist")) {
assertEquals(404, response.getStatus());
}
Assertions.assertNull(adminEvents.poll());
}
}

View File

@ -0,0 +1,901 @@
package org.keycloak.tests.admin.user;
import jakarta.mail.internet.MimeMessage;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.core.Response;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.TokenVerifier;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.VerificationException;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.SystemClientUtil;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.annotations.InjectKeycloakUrls;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.mail.MailServer;
import org.keycloak.testframework.mail.annotations.InjectMailServer;
import org.keycloak.testframework.realm.ClientConfig;
import org.keycloak.testframework.realm.ClientConfigBuilder;
import org.keycloak.testframework.realm.ManagedClient;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.testframework.remote.timeoffset.InjectTimeOffSet;
import org.keycloak.testframework.remote.timeoffset.TimeOffSet;
import org.keycloak.testframework.server.KeycloakUrls;
import org.keycloak.testframework.ui.annotations.InjectPage;
import org.keycloak.testframework.ui.page.ErrorPage;
import org.keycloak.testframework.ui.page.InfoPage;
import org.keycloak.testframework.ui.page.LoginPasswordUpdatePage;
import org.keycloak.testframework.ui.page.ProceedPage;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.tests.utils.MailUtils;
import org.openqa.selenium.By;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@KeycloakIntegrationTest
public class UserEmailTest extends AbstractUserTest {
@InjectRealm(lifecycle = LifeCycle.METHOD)
ManagedRealm managedRealm;
@InjectClient(config = UserEmailTestAppClientConf.class)
ManagedClient client;
@InjectMailServer
MailServer mailServer;
@InjectKeycloakUrls
KeycloakUrls keycloakUrls;
@InjectTimeOffSet
TimeOffSet timeOffSet;
@InjectPage
LoginPasswordUpdatePage passwordUpdatePage;
@InjectPage
InfoPage infoPage;
@InjectPage
ProceedPage proceedPage;
@InjectPage
ErrorPage errorPage;
@Test
public void sendResetPasswordEmail() {
UserRepresentation userRep = new UserRepresentation();
userRep.setUsername("user1");
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> 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);
Assertions.assertEquals("User email missing", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
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);
Assertions.assertEquals("User is disabled", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
try {
userRep.setEnabled(true);
updateUser(user, userRep);
user.executeActionsEmail(Arrays.asList(
UserModel.RequiredAction.UPDATE_PASSWORD.name(),
"invalid\"<img src=\"alert(0)\">")
);
fail("Expected failure");
} catch (ClientErrorException e) {
assertEquals(400, e.getResponse().getStatus());
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
Assertions.assertEquals("Provided invalid required actions", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
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);
Assertions.assertEquals("Client doesn't exist", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void sendResetPasswordEmailSuccess() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.getReceivedMessages()[0];
MailUtils.EmailBody body = MailUtils.getBody(message);
assertTrue(body.getText().contains("Update Password"));
assertTrue(body.getText().contains("your Default account"));
assertTrue(body.getText().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");
assertThat(driver.getCurrentUrl(), Matchers.containsString("client_id=" + Constants.ACCOUNT_MANAGEMENT_CLIENT_ID));
assertEquals("Your account has been updated.", infoPage.getInfo());
driver.navigate().to(link);
errorPage.assertCurrent();
}
@Test
public void sendResetPasswordEmailSuccessWithAccountClientDisabled() throws IOException {
ClientRepresentation clientRepresentation = managedRealm.admin().clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0);
clientRepresentation.setEnabled(false);
managedRealm.admin().clients().get(clientRepresentation.getId()).update(clientRepresentation);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.clientResourcePath(clientRepresentation.getId()), clientRepresentation, ResourceType.CLIENT);
UserRepresentation userRep = new UserRepresentation();
userRep.setEnabled(true);
userRep.setUsername("user1");
userRep.setEmail("user1@test.com");
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.getReceivedMessages()[0];
MailUtils.EmailBody body = MailUtils.getBody(message);
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");
assertThat(driver.getCurrentUrl(), Matchers.containsString("client_id=" + SystemClientUtil.SYSTEM_CLIENT_ID));
clientRepresentation.setEnabled(true);
managedRealm.admin().clients().get(clientRepresentation.getId()).update(clientRepresentation);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.clientResourcePath(clientRepresentation.getId()), clientRepresentation, ResourceType.CLIENT);
}
@Test
public void testEmailLinkBasedOnRealmFrontEndUrl() throws Exception {
try {
updateRealmFrontEndUrl(adminClient.realm("master"), keycloakUrls.getBase());
String expectedFrontEndUrl = "https://mytestrealm";
updateRealmFrontEndUrl(adminClient.realm(managedRealm.getName()), expectedFrontEndUrl);
UserRepresentation userRep = new UserRepresentation();
userRep.setEnabled(true);
userRep.setUsername("user1");
userRep.setEmail("user1@test.com");
String id = createUser(userRep, false);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.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(managedRealm.getName()), null);
}
}
@Test
public void sendResetPasswordEmailWithCustomLifespan() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
final int lifespan = (int) TimeUnit.HOURS.toSeconds(5);
user.executeActionsEmail(actions, lifespan);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.getReceivedMessages()[0];
MailUtils.EmailBody body = MailUtils.getBody(message);
assertTrue(body.getText().contains("Update Password"));
assertTrue(body.getText().contains("your Default account"));
assertTrue(body.getText().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)));
assertEquals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID, accessToken.getIssuedFor());
} 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.", infoPage.getInfo());
driver.navigate().to(link);
errorPage.assertCurrent();
}
@Test
public void sendResetPasswordEmailSuccessTwoLinks() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
user.executeActionsEmail(actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(2, mailServer.getReceivedMessages().length);
int i = 1;
for (MimeMessage message : mailServer.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.", infoPage.getInfo());
}
for (MimeMessage message : mailServer.getReceivedMessages()) {
String link = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(link);
errorPage.assertCurrent();
}
}
@Test
public void sendResetPasswordEmailSuccessTwoLinksReverse() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
user.executeActionsEmail(actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(2, mailServer.getReceivedMessages().length);
int i = 1;
for (int j = mailServer.getReceivedMessages().length - 1; j >= 0; j--) {
MimeMessage message = mailServer.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.", infoPage.getInfo());
}
for (MimeMessage message : mailServer.getReceivedMessages()) {
String link = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(link);
errorPage.assertCurrent();
}
}
@Test
public void sendResetPasswordEmailSuccessLinkOpenDoesNotExpireWhenOpenedOnly() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.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.", infoPage.getInfo());
}
@Test
public void sendResetPasswordEmailSuccessTokenShortLifespan() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
final AtomicInteger originalValue = new AtomicInteger();
RealmRepresentation realmRep = managedRealm.admin().toRepresentation();
originalValue.set(realmRep.getActionTokenGeneratedByAdminLifespan());
realmRep.setActionTokenGeneratedByAdminLifespan(60);
managedRealm.admin().update(realmRep);
try {
UserResource user = managedRealm.admin().users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.getReceivedMessages()[0];
String link = MailUtils.getPasswordResetEmailLink(message);
timeOffSet.set(70);
driver.navigate().to(link);
errorPage.assertCurrent();
assertEquals("Action expired.", errorPage.getError());
} finally {
timeOffSet.set(0);
realmRep.setActionTokenGeneratedByAdminLifespan(originalValue.get());
managedRealm.admin().update(realmRep);
}
}
@Test
public void sendResetPasswordEmailSuccessWithRecycledAuthSession() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
List<String> 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 = managedRealm.admin().clients().create(client);
String createdId = ApiUtil.getCreatedId(response);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT);
user.executeActionsEmail("myclient2", "http://myclient.com/home.html", actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.getReceivedMessages()[0];
String link = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(link);
}
user.executeActionsEmail(actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(2, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.getReceivedMessages()[mailServer.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.", infoPage.getInfo());
driver.navigate().to(link);
errorPage.assertCurrent();
}
@Test
public void sendResetPasswordEmailWithRedirect() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().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 = managedRealm.admin().clients().create(client);
String createdId = ApiUtil.getCreatedId(response);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT);
List<String> 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);
Assertions.assertEquals("Invalid redirect uri.", error.getErrorMessage());
}
user.executeActionsEmail("myclient", "http://myclient.com/home.html", actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.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.
Assertions.assertTrue(pageSource.contains("http://myclient.com/home.html"));
driver.navigate().to(link);
errorPage.assertCurrent();
}
@Test
public void sendResetPasswordEmailWithRedirectAndCustomLifespan() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().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 = managedRealm.admin().clients().create(client);
String createdId = ApiUtil.getCreatedId(response);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.clientResourcePath(createdId), client, ResourceType.CLIENT);
List<String> 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);
Assertions.assertEquals("Invalid redirect uri.", error.getErrorMessage());
}
user.executeActionsEmail("myclient", "http://myclient.com/home.html", lifespan, actions);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/execute-actions-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.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.getExp() - accessToken.getIat());
} 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.
Assertions.assertTrue(pageSource.contains("http://myclient.com/home.html"));
driver.navigate().to(link);
errorPage.assertCurrent();
}
@Test
public void sendVerifyEmail() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
try {
user.sendVerifyEmail();
fail("Expected failure");
} catch (ClientErrorException e) {
assertEquals(400, e.getResponse().getStatus());
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
Assertions.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);
Assertions.assertEquals("User is disabled", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
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);
Assertions.assertEquals("Client doesn't exist", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
user.sendVerifyEmail();
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/send-verify-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
String link = MailUtils.getPasswordResetEmailLink(mailServer.getReceivedMessages()[0]);
driver.navigate().to(link);
proceedPage.assertCurrent();
assertThat(proceedPage.getInfo(), Matchers.containsString("Confirm validity of e-mail address"));
proceedPage.clickProceedLink();
Assertions.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 = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
String clientId = client.getClientId();
String redirectUri = keycloakUrls.getBase() + "/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);
Assertions.assertEquals("Invalid redirect uri.", error.getErrorMessage());
}
user.sendVerifyEmail(clientId, redirectUri);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/send-verify-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.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();
Assertions.assertTrue(pageSource.contains(redirectUri));
}
@Test
public void sendVerifyEmailWithRedirectAndCustomLifespan() throws IOException {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").name("User", "One").email("user1@test.com").build();
String id = createUser(userRep);
UserResource user = managedRealm.admin().users().get(id);
final int lifespan = (int) TimeUnit.DAYS.toSeconds(1);
String redirectUri = keycloakUrls.getBase() + "/auth/some-page";
try {
// test that an invalid redirect uri is rejected.
user.sendVerifyEmail(client.getClientId(), "http://unregistered-uri.com/", lifespan);
fail("Expected failure");
} catch (ClientErrorException e) {
assertEquals(400, e.getResponse().getStatus());
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
Assertions.assertEquals("Invalid redirect uri.", error.getErrorMessage());
}
user.sendVerifyEmail(client.getClientId(), redirectUri, lifespan);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.ACTION, AdminEventPaths.userResourcePath(id) + "/send-verify-email", ResourceType.USER);
Assertions.assertEquals(1, mailServer.getReceivedMessages().length);
MimeMessage message = mailServer.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();
Assertions.assertTrue(pageSource.contains(redirectUri));
}
private void updateRealmFrontEndUrl(RealmResource realm, String url) throws Exception {
RealmRepresentation master = realm.toRepresentation();
Map<String, String> attributes = Optional.ofNullable(master.getAttributes()).orElse(new HashMap<>());
if (url == null) {
attributes.remove("frontendUrl");
} else {
attributes.put("frontendUrl", url);
}
realm.update(master);
}
private static class UserEmailTestAppClientConf implements ClientConfig {
public ClientConfigBuilder configure(ClientConfigBuilder builder) {
builder.clientId("test-app-email");
builder.secret("password");
builder.baseUrl("http://localhost:8080/auth/");
builder.redirectUris("http://localhost:8080/auth/*");
builder.adminUrl("http://localhost:8080/auth/admin");
return builder;
}
}
}

View File

@ -0,0 +1,157 @@
package org.keycloak.tests.admin.user;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.tests.utils.Assert;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.testsuite.federation.UserMapStorageFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
@KeycloakIntegrationTest(config = UserFedarationTest.UserFederationServerConfig.class)
public class UserFedarationTest extends AbstractUserTest {
@Test
public void getFederatedIdentities() {
// Add sample identity provider
addSampleIdentityProvider();
// Add sample user
String id = createUser();
UserResource user = managedRealm.admin().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
List<FederatedIdentityRepresentation> 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");
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.userFederatedIdentityLink(id, "social-provider-id"), ResourceType.USER);
assertEquals(0, user.getFederatedIdentity().size());
removeSampleIdentityProvider();
}
@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));
String memProviderId = ApiUtil.getCreatedId(managedRealm.admin().components().add(memProvider));
managedRealm.cleanup().add(realm -> realm.components().component(memProviderId).remove());
AdminEventAssertion.assertEvent(adminEvents.poll(), 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 = ApiUtil.getCreatedId(managedRealm.admin().users().create(userRepresentation));
Assert.assertFalse(StorageId.isLocalStorage(userId));
UserResource user = ApiUtil.findUserByUsernameId(managedRealm.admin(), username);
List<CredentialRepresentation> credentials = user.credentials();
Assertions.assertNotNull(credentials);
Assertions.assertEquals(1, credentials.size());
Assertions.assertEquals("label", credentials.get(0).getUserLabel());
// Update federated credential user label
user.setCredentialUserLabel(credentials.get(0).getId(), "updatedLabel");
credentials = user.credentials();
Assertions.assertNotNull(credentials);
Assertions.assertEquals(1, credentials.size());
Assertions.assertEquals("updatedLabel", credentials.get(0).getUserLabel());
}
@Test
public void createFederatedIdentities() {
String identityProviderAlias = "social-provider-id";
String username = "federated-identities";
String federatedUserId = "federated-user-id";
addSampleIdentityProvider();
UserRepresentation build = UserConfigBuilder.create()
.username(username)
.federatedLink(identityProviderAlias, federatedUserId, username)
.build();
//when
String userId = createUser(build, false);
List<FederatedIdentityRepresentation> obtainedFederatedIdentities = managedRealm.admin().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());
}
private void removeSampleIdentityProvider() {
IdentityProviderResource resource = managedRealm.admin().identityProviders().get("social-provider-id");
Assertions.assertNotNull(resource);
resource.remove();
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.identityProviderPath("social-provider-id"), ResourceType.IDENTITY_PROVIDER);
}
public static class UserFederationServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.dependency("org.keycloak.tests", "keycloak-tests-custom-providers");
}
}
}

View File

@ -0,0 +1,249 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.realm.GroupConfigBuilder;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.keycloak.tests.utils.Assert.assertNames;
@KeycloakIntegrationTest
public class UserGroupTest extends AbstractUserTest {
@Test
public void testGetGroupsForUserFullRepresentation() {
String userName = "averagejoe";
String groupName = "groupWithAttribute";
Map<String, List<String>> attributes = new HashMap<String, List<String>>();
attributes.put("attribute1", Arrays.asList("attribute1","attribute2"));
UserRepresentation userRepresentation = UserConfigBuilder.create()
.username(userName).name("average", "joe").password("password")
.email("joe@average.com").emailVerified().build();
GroupRepresentation groupRepresentation = GroupConfigBuilder.create().name(groupName).setAttributes(attributes).build();
String userId = createUser(userRepresentation);
String groupId = createGroup(groupRepresentation).getId();
UserResource user = managedRealm.admin().users().get(userId);
user.joinGroup(groupId);
List<GroupRepresentation> userGroups = user.groups(0, 100, false);
assertFalse(userGroups.isEmpty());
assertTrue(userGroups.get(0).getAttributes().containsKey("attribute1"));
}
@Test
public void testGetSearchedGroupsForUserFullRepresentation() {
String userName = "averagejoe";
String groupName1 = "group1WithAttribute";
String groupName2 = "group2WithAttribute";
Map<String, List<String>> attributes1 = new HashMap<String, List<String>>();
attributes1.put("attribute1", Arrays.asList("attribute1"));
Map<String, List<String>> attributes2 = new HashMap<String, List<String>>();
attributes2.put("attribute2", Arrays.asList("attribute2"));
UserRepresentation userRepresentation = UserConfigBuilder.create()
.username(userName).name("average", "joe").password("password")
.email("joe@average.com").emailVerified().build();
GroupRepresentation groupRepresentation = GroupConfigBuilder.create().name(groupName1).setAttributes(attributes1).build();
GroupRepresentation groupRepresentation2 = GroupConfigBuilder.create().name(groupName2).setAttributes(attributes2).build();
String userId = createUser(userRepresentation);
String group1Id = createGroup(groupRepresentation).getId();
String group2Id = createGroup(groupRepresentation2).getId();
UserResource user = managedRealm.admin().users().get(userId);
user.joinGroup(group1Id);
user.joinGroup(group2Id);
List<GroupRepresentation> 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(UserConfigBuilder.create().username("user-a").build());
for (int i = 1; i <= 10; i++) {
GroupRepresentation group = new GroupRepresentation();
group.setName("group-" + i);
String groupId = createGroup(group).getId();
managedRealm.admin().users().get(userId).joinGroup(groupId);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.userGroupPath(userId, groupId), group, ResourceType.GROUP_MEMBERSHIP);
}
List<GroupRepresentation> groups = managedRealm.admin().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(UserConfigBuilder.create().username("user-b").build());
for (int i = 1; i <= 10; i++) {
GroupRepresentation group = new GroupRepresentation();
group.setName("group-" + i);
String groupId = createGroup(group).getId();
managedRealm.admin().users().get(userId).joinGroup(groupId);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.userGroupPath(userId, groupId), group, ResourceType.GROUP_MEMBERSHIP);
}
List<GroupRepresentation> groups = managedRealm.admin().users().get(userId).groups("-3", 0, 10);
assertThat(managedRealm.admin().users().get(userId).groupsCount("-3").get("count"), is(1L));
assertEquals(1, groups.size());
assertNames(groups, "group-3");
List<GroupRepresentation> groups2 = managedRealm.admin().users().get(userId).groups("1", 0, 10);
assertThat(managedRealm.admin().users().get(userId).groupsCount("1").get("count"), is(2L));
assertEquals(2, groups2.size());
assertNames(groups2, "group-1", "group-10");
List<GroupRepresentation> groups3 = managedRealm.admin().users().get(userId).groups("1", 2, 10);
assertEquals(0, groups3.size());
List<GroupRepresentation> groups4 = managedRealm.admin().users().get(userId).groups("gr", 2, 10);
assertThat(managedRealm.admin().users().get(userId).groupsCount("gr").get("count"), is(10L));
assertEquals(8, groups4.size());
List<GroupRepresentation> groups5 = managedRealm.admin().users().get(userId).groups("Gr", 2, 10);
assertEquals(8, groups5.size());
}
@Test
public void createUserWithGroups() {
String username = "user-with-groups";
String groupToBeAdded = "test-group";
createGroup(GroupConfigBuilder.create().name(groupToBeAdded).build());
UserRepresentation build = UserConfigBuilder.create()
.username(username)
.groups(groupToBeAdded)
.build();
//when
String userId = createUser(build);
List<GroupRepresentation> obtainedGroups = managedRealm.admin().users().get(userId).groups();
//then
assertEquals(1, obtainedGroups.size());
assertEquals(groupToBeAdded, obtainedGroups.get(0).getName());
}
/**
* Test for #9482
*/
@Test
public void joinParentGroupAfterSubGroup() {
String username = "user-with-sub-and-parent-group";
String parentGroupName = "parent-group";
String subGroupName = "sub-group";
UserRepresentation userRepresentation = UserConfigBuilder.create().username(username).build();
GroupRepresentation subGroupRep = GroupConfigBuilder.create().name(subGroupName).build();
GroupRepresentation parentGroupRep = GroupConfigBuilder.create().name(parentGroupName).subGroups(subGroupRep).build();
String userId = createUser(userRepresentation);
String subGroupId = createGroup(subGroupRep).getId();
String parentGroupId = createGroup(parentGroupRep).getId();
UserResource user = managedRealm.admin().users().get(userId);
//when
user.joinGroup(subGroupId);
List<GroupRepresentation> obtainedGroups = managedRealm.admin().users().get(userId).groups();
//then
assertEquals(1, obtainedGroups.size());
assertEquals(subGroupName, obtainedGroups.get(0).getName());
//when
user.joinGroup(parentGroupId);
obtainedGroups = managedRealm.admin().users().get(userId).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 = UserConfigBuilder.create().username(username).build();
GroupRepresentation subGroupRep = GroupConfigBuilder.create().name(subGroupName).build();
GroupRepresentation parentGroupRep = GroupConfigBuilder.create().name(parentGroupName).subGroups(subGroupRep).build();
String userId = createUser(userRepresentation);
String subGroupId = createGroup(subGroupRep).getId();
String parentGroupId = createGroup(parentGroupRep).getId();
UserResource user = managedRealm.admin().users().get(userId);
//when
user.joinGroup(parentGroupId);
List<GroupRepresentation> obtainedGroups = managedRealm.admin().users().get(userId).groups();
//then
assertEquals(1, obtainedGroups.size());
assertEquals(parentGroupName, obtainedGroups.get(0).getName());
//when
user.joinGroup(subGroupId);
obtainedGroups = managedRealm.admin().users().get(userId).groups();
//then
assertEquals(2, obtainedGroups.size());
assertEquals(parentGroupName, obtainedGroups.get(0).getName());
assertEquals(subGroupName, obtainedGroups.get(1).getName());
}
private GroupRepresentation createGroup(GroupRepresentation group) {
final String groupId;
try (Response response = managedRealm.admin().groups().add(group)) {
groupId = ApiUtil.getCreatedId(response);
}
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.groupPath(groupId), group, ResourceType.GROUP);
group.setId(groupId);
return group;
}
}

View File

@ -0,0 +1,178 @@
/*
* 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.tests.admin.user;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserProfileMetadata;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPAttributePermissions;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.UserConfigBuilder;
import java.util.List;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@KeycloakIntegrationTest
public class UserProfileTest extends AbstractUserTest {
@InjectRealm(lifecycle = LifeCycle.METHOD)
ManagedRealm managedRealm;
@Test
public void testUsernameReadOnlyIfEmailAsUsernameEnabled() {
switchRegistrationEmailAsUsername(true);
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = managedRealm.admin().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);
RealmRepresentation rep = managedRealm.admin().toRepresentation();
assertFalse(rep.isEditUsernameAllowed());
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = managedRealm.admin().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 testUserProfileMetadata() {
String userId = createUser("user-metadata", "user-metadata@keycloak.org");
UserRepresentation user = managedRealm.admin().users().get(userId).toRepresentation(true);
UserProfileMetadata metadata = user.getUserProfileMetadata();
assertNotNull(metadata);
for (String name : managedAttributes) {
assertNotNull(metadata.getAttributeMetadata(name));
}
}
@Test
public void testSearchBasedOnUserProfileSettings() {
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);
UPConfig upConfig = managedRealm.admin().users().userProfile().getConfiguration();
upConfig.getAttribute(UserModel.FIRST_NAME).setPermissions(new UPAttributePermissions());
managedRealm.admin().users().userProfile().update(upConfig);
List<UserRepresentation> users = managedRealm.admin().users().list();
assertThat(users, hasSize(1));
user = users.get(0);
assertThat(user.getFirstName(), is(nullValue()));
}
@Test
public void defaultMaxResults() {
UserProfileResource upResource = managedRealm.admin().users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
upConfig.addOrReplaceAttribute(createAttributeMetadata("aName"));
upConfig.getAttribute("aName").setPermissions(new UPAttributePermissions(Set.of("user", "admin"), Set.of("user", "admin")));
upResource.update(upConfig);
try {
UsersResource users = managedRealm.admin().users();
for (int i = 0; i < 110; i++) {
users.create(UserConfigBuilder.create().username("test2-" + i).attribute("aName", "aValue").build()).close();
}
List<UserRepresentation> result = users.search("test2", null, null);
assertEquals(100, result.size());
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.notNullValue());
assertThat(user.getAttributes().keySet(), hasSize(1));
assertThat(user.getAttributes(), Matchers.hasEntry(is("aName"), Matchers.contains("aValue")));
}
assertEquals(105, users.search("test2", 0, 105).size());
assertEquals(110, users.search("test2", 0, 1000).size());
} finally {
upConfig.removeAttribute("aName");
upResource.update(upConfig);
}
}
@Test
public void defaultMaxResultsBrief() {
UserProfileResource upResource = managedRealm.admin().users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
upConfig.addOrReplaceAttribute(createAttributeMetadata("aName"));
upConfig.getAttribute("aName").setPermissions(new UPAttributePermissions());
upResource.update(upConfig);
try {
UsersResource users = managedRealm.admin().users();
for (int i = 0; i < 110; i++) {
users.create(UserConfigBuilder.create().username("test-" + i).attribute("aName", "aValue").build()).close();
}
List<UserRepresentation> result = users.search("test", null, null, true);
assertEquals(100, result.size());
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), nullValue());
}
} finally {
upConfig.removeAttribute("aName");
upResource.update(upConfig);
}
}
}

View File

@ -0,0 +1,76 @@
package org.keycloak.tests.admin.user;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KeycloakIntegrationTest
public class UserRequiredActionsTest extends AbstractUserTest {
@Test
public void addRequiredAction() {
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
assertTrue(user.toRepresentation().getRequiredActions().isEmpty());
UserRepresentation userRep = user.toRepresentation();
userRep.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
updateUser(user, userRep);
assertEquals(1, user.toRepresentation().getRequiredActions().size());
assertEquals(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), user.toRepresentation().getRequiredActions().get(0));
}
@Test
public void removeRequiredAction() {
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
assertTrue(user.toRepresentation().getRequiredActions().isEmpty());
UserRepresentation userRep = user.toRepresentation();
userRep.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
updateUser(user, userRep);
user = managedRealm.admin().users().get(id);
userRep = user.toRepresentation();
userRep.getRequiredActions().clear();
updateUser(user, userRep);
assertTrue(user.toRepresentation().getRequiredActions().isEmpty());
}
@Test
public void testDefaultRequiredActionAdded() {
// Add UPDATE_PASSWORD as default required action
RequiredActionProviderRepresentation updatePasswordReqAction = managedRealm.admin().flows().getRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
updatePasswordReqAction.setDefaultAction(true);
managedRealm.admin().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePasswordReqAction);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(UserModel.RequiredAction.UPDATE_PASSWORD.toString()), updatePasswordReqAction, ResourceType.REQUIRED_ACTION);
// Create user
String userId = createUser("user1", "user1@localhost");
UserRepresentation userRep = managedRealm.admin().users().get(userId).toRepresentation();
Assertions.assertEquals(1, userRep.getRequiredActions().size());
Assertions.assertEquals(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), userRep.getRequiredActions().get(0));
// Remove UPDATE_PASSWORD default action
updatePasswordReqAction = managedRealm.admin().flows().getRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
updatePasswordReqAction.setDefaultAction(false);
managedRealm.admin().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePasswordReqAction);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(UserModel.RequiredAction.UPDATE_PASSWORD.toString()), updatePasswordReqAction, ResourceType.REQUIRED_ACTION);
}
}

View File

@ -0,0 +1,254 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.realm.ClientConfigBuilder;
import org.keycloak.testframework.realm.GroupConfigBuilder;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.testsuite.events.TestEventsListenerProviderFactory;
import org.keycloak.testsuite.util.RoleBuilder;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.keycloak.tests.utils.Assert.assertNames;
@KeycloakIntegrationTest
public class UserRoleTest extends AbstractUserTest {
@InjectRealm(lifecycle = LifeCycle.METHOD)
ManagedRealm managedRealm;
@Test
public void roleMappings() {
RealmResource realm = managedRealm.admin();
// Enable events
RealmRepresentation realmRep = addTestEventListener(managedRealm.admin().toRepresentation());
managedRealm.admin().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(ClientConfigBuilder.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(UserConfigBuilder.create().username("myuser").build())) {
userId = ApiUtil.getCreatedId(response);
}
// Admin events for creating role, client or user tested already in other places
adminEvents.clear();
RoleMappingResource roles = realm.users().get(userId).roles();
assertNames(roles.realmLevel().listAll(), Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName());
assertNames(roles.realmLevel().listEffective(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName());
// Add realm roles
List<RoleRepresentation> l = new LinkedList<>();
l.add(realm.roles().get("realm-role").toRepresentation());
l.add(realm.roles().get("realm-composite").toRepresentation());
roles.realmLevel().add(l);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.userRealmRoleMappingsPath(userId), l, ResourceType.REALM_ROLE_MAPPING);
// Add client roles
List<RoleRepresentation> list = Collections.singletonList(realm.clients().get(clientUuid).roles().get("client-role").toRepresentation());
roles.clientLevel(clientUuid).add(list);
AdminEventAssertion.assertEvent(adminEvents.poll(), 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);
AdminEventAssertion.assertEvent(adminEvents.poll(), 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 + "-" + managedRealm.getName());
assertNames(roles.realmLevel().listAvailable(), "realm-child", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(roles.realmLevel().listEffective(), "realm-role", "realm-composite", "realm-child", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName());
// List realm effective role with full representation
List<RoleRepresentation> 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<RoleRepresentation> 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 + "-" + managedRealm.getName());
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));
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.userRealmRoleMappingsPath(userId), Collections.singletonList(realmRoleRep), ResourceType.REALM_ROLE_MAPPING);
assertNames(roles.realmLevel().listAll(), "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName());
// Remove client role
RoleRepresentation clientRoleRep = realm.clients().get(clientUuid).roles().get("client-role").toRepresentation();
roles.clientLevel(clientUuid).remove(Collections.singletonList(clientRoleRep));
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.DELETE, AdminEventPaths.userClientRoleMappingsPath(userId, clientUuid), Collections.singletonList(clientRoleRep), ResourceType.CLIENT_ROLE_MAPPING);
assertNames(roles.clientLevel(clientUuid).listAll(), "client-composite");
}
/**
* Test for KEYCLOAK-10603.
*/
@Test
public void rolesCanBeAssignedEvenWhenTheyAreAlreadyIndirectlyAssigned() {
RealmResource realm = managedRealm.admin();
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(ClientConfigBuilder.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 = GroupConfigBuilder.create().name("mygroup").build();
response = realm.groups().add(group);
String groupId = ApiUtil.getCreatedId(response);
response.close();
response = realm.users().create(UserConfigBuilder.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 + "-" + managedRealm.getName());
assertNames(userRoles.realmLevel().listAvailable(), "realm-child", "realm-role-in-group", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION,
Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName());
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 + "-" + managedRealm.getName());
assertNames(userRoles.realmLevel().listAvailable(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group",
"offline_access", Constants.AUTHZ_UMA_AUTHORIZATION,
Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + managedRealm.getName());
// 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 + "-" + managedRealm.getName());
assertEquals(1, all.getClientMappings().size());
assertNames(all.getClientMappings().get("myclient").getMappings(), "client-composite", "client-child",
"client-role-in-group");
}
private RoleRepresentation getRoleByName(String name, List<RoleRepresentation> roles) {
for(RoleRepresentation role : roles) {
if(role.getName().equalsIgnoreCase(name)) {
return role;
}
}
return null;
}
private RealmRepresentation addTestEventListener(RealmRepresentation rep) {
if (rep.getEventsListeners() == null) {
rep.setEventsListeners(new LinkedList<String>());
}
if (!rep.getEventsListeners().contains(TestEventsListenerProviderFactory.PROVIDER_ID)) {
rep.getEventsListeners().add(TestEventsListenerProviderFactory.PROVIDER_ID);
}
return rep;
}
}

View File

@ -0,0 +1,789 @@
package org.keycloak.tests.admin.user;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.userprofile.DefaultAttributes;
import org.keycloak.userprofile.validator.UsernameProhibitedCharactersValidator;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KeycloakIntegrationTest
public class UserSearchTest extends AbstractUserTest {
@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(managedRealm.admin().users().count(null, null, null, "@enabledfilter.com", null, null, null, null), is(3));
// count users that are enabled and have username enabled1
assertThat(managedRealm.admin().users().count(null, null, null, "@enabledfilter.com", null, "enabled1", enabled, null),is(1));
// count users that are disabled
assertThat(managedRealm.admin().users().count(null, null, null, "@enabledfilter.com", null, null, disabled, null), is(1));
// count users that are enabled
assertThat(managedRealm.admin().users().count(null, null, null, "@enabledfilter.com", null, null, enabled, null), is(2));
}
@Test
public void searchByEmail() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().users().search(null, null, null, "user1@localhost", null, null);
assertEquals(1, users.size());
users = managedRealm.admin().users().search(null, null, null, "@localhost", null, null);
assertEquals(9, users.size());
}
@Test
public void searchByEmailExactMatch() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().users().searchByEmail("user1@localhost", true);
assertEquals(1, users.size());
users = managedRealm.admin().users().search("@localhost", true);
assertEquals(0, users.size());
}
@Test
public void searchByUsername() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().users().search("username1", null, null, null, null, null);
assertEquals(1, users.size());
users = managedRealm.admin().users().search("user", null, null, null, null, null);
assertEquals(9, users.size());
}
@Test
public void searchByAttribute() {
createUsers();
Map<String, String> attributes = new HashMap<>();
attributes.put("test", "test1");
List<UserRepresentation> users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(attributes));
assertEquals(1, users.size());
attributes.clear();
attributes.put("attr", "common");
users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(attributes));
assertEquals(9, users.size());
attributes.clear();
attributes.put("x", "common");
users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(attributes));
assertEquals(0, users.size());
}
@Test
public void searchByMultipleAttributes() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(Map.of("username", "user", "test", "test1", "attr", "common", "test1", "test1")));
assertThat(users, hasSize(1));
//custom user attribute should not use wildcard search by default
users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(Map.of("username", "user", "test", "est", "attr", "mm", "test1", "test1")));
assertThat(users, hasSize(0));
//custom user attribute should use wildcard
users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(Map.of("username", "user", "test", "est", "attr", "mm", "test1", "test1")), false);
assertThat(users, hasSize(1));
//with exact=true the user shouldn't be returned
users = managedRealm.admin().users().searchByAttributes(mapToSearchQuery(Map.of("test", "est", "attr", "mm", "test1", "test1")), Boolean.TRUE);
assertThat(users, hasSize(0));
}
@Test
public void searchByAttributesWithPagination() {
createUsers();
Map<String, String> attributes = new HashMap<>();
attributes.put("attr", "Common");
for (int i = 1; i < 10; i++) {
List<UserRepresentation> users = managedRealm.admin().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);
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").password("password").name("user1FirstName", "user1LastName")
.email("user1@example.com").emailVerified().attribute("attr", longValue).build();
String userId = createUser(userRep);
UserRepresentation user1 = managedRealm.admin().users().get(userId).toRepresentation();
Assertions.assertNotNull(user1);
assertThat(user1.getAttributes().get("attr").get(0), equalTo(longValue));
UserRepresentation userRep2 = UserConfigBuilder.create()
.username("user2").password("password").name("user2FirstName", "user2LastName")
.email("user2@example.com").emailVerified().attribute("attr", longValue + "a").build();
Response response = managedRealm.admin().users().create(userRep2);
assertThat(response.getStatus(), equalTo(400));
assertThat(response.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";
UserRepresentation userRep = UserConfigBuilder.create()
.username("user1").password("password").name("user1FirstName", "user1LastName")
.email("user1@example.com").emailVerified()
.attribute("test1", longValue, "v2").attribute("test2", "v2").build();
UserRepresentation userRep2 = UserConfigBuilder.create()
.username("user2").password("password").name("user2FirstName", "user2LastName")
.email("user2@example.com").emailVerified()
.attribute("test1", longValue, "v2").attribute("test2", longValue2).build();
UserRepresentation userRep3 = UserConfigBuilder.create()
.username("user3").password("password").name("user3FirstName", "user3LastName")
.email("user3@example.com").emailVerified()
.attribute("test2", longValue, "v3").attribute("test4", "v4").build();
createUser(userRep);
createUser(userRep2);
createUser(userRep3);
assertThat(managedRealm.admin().users().searchByAttributes(mapToSearchQuery(Map.of("test1", longValue))).stream().map(UserRepresentation::getUsername).collect(Collectors.toList()),
containsInAnyOrder("user1", "user2"));
assertThat(managedRealm.admin().users().searchByAttributes(mapToSearchQuery(Map.of("test1", longValue, "test2", longValue2))).stream().map(UserRepresentation::getUsername).collect(Collectors.toList()),
contains("user2"));
//case-insensitive search
assertThat(managedRealm.admin().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<UserRepresentation> users = managedRealm.admin().users().search("username1", true);
assertEquals(1, users.size());
users = managedRealm.admin().users().searchByUsername("username1", true);
assertEquals(1, users.size());
users = managedRealm.admin().users().search("user", true);
assertEquals(0, users.size());
}
@Test
public void searchByFirstNameExact() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().users().searchByFirstName("First1", true);
assertEquals(1, users.size());
}
@Test
public void searchByLastNameExact() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().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<UserRepresentation> users = managedRealm.admin().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<UserRepresentation> users = managedRealm.admin().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<UserRepresentation> enabledUsers = managedRealm.admin().users().search(null, null, null, null, null, null, true, false);
assertEquals(1, enabledUsers.size());
List<UserRepresentation> enabledUsersWithFilter = managedRealm.admin().users().search(userCommonName, null, null, null, null, null, true, true);
assertEquals(1, enabledUsersWithFilter.size());
assertEquals(user1.getUsername(), enabledUsersWithFilter.get(0).getUsername());
List<UserRepresentation> disabledUsers = managedRealm.admin().users().search(userCommonName, null, null, null, null, null, false, false);
assertEquals(1, disabledUsers.size());
assertEquals(user2.getUsername(), disabledUsers.get(0).getUsername());
List<UserRepresentation> allUsers = managedRealm.admin().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<UserRepresentation> searchFirstNameAndDisabled = managedRealm.admin().users().search(null, "First", null, null, null, null, false, true);
assertEquals(1, searchFirstNameAndDisabled.size());
assertEquals(user.getUsername(), searchFirstNameAndDisabled.get(0).getUsername());
List<UserRepresentation> searchLastNameAndEnabled = managedRealm.admin().users().search(null, null, "Last", null, null, null, true, false);
assertEquals(0, searchLastNameAndEnabled.size());
List<UserRepresentation> searchEmailAndDisabled = managedRealm.admin().users().search(null, null, null, "user2@localhost", 0, 50, false, true);
assertEquals(1, searchEmailAndDisabled.size());
assertEquals(user.getUsername(), searchEmailAndDisabled.get(0).getUsername());
List<UserRepresentation> searchInvalidSizeAndDisabled = managedRealm.admin().users().search(null, null, null, null, 10, 20, null, false);
assertEquals(0, searchInvalidSizeAndDisabled.size());
}
@Test
public void searchWithFilterAndEnabledAttribute() {
createUser();
UserRepresentation user = new UserRepresentation();
user.setUsername("user3");
user.setFirstName("user3First");
user.setLastName("user3Last");
user.setEmail("user3@localhost");
user.setRequiredActions(Collections.emptyList());
user.setEnabled(false);
createUser(user);
List<UserRepresentation> searchFilterUserNameAndDisabled = managedRealm.admin().users().search("user3", false, 0, 5);
assertEquals(1, searchFilterUserNameAndDisabled.size());
assertEquals(user.getUsername(), searchFilterUserNameAndDisabled.get(0).getUsername());
List<UserRepresentation> searchFilterMailAndDisabled = managedRealm.admin().users().search("user3@localhost", false, 0, 5);
assertEquals(1, searchFilterMailAndDisabled.size());
assertEquals(user.getUsername(), searchFilterMailAndDisabled.get(0).getUsername());
List<UserRepresentation> searchFilterLastNameAndEnabled = managedRealm.admin().users().search("user3Last", true, 0, 5);
assertEquals(0, searchFilterLastNameAndEnabled.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<UserRepresentation> searchForAllUsers =
managedRealm.admin().users().search(null, null, null, null, null, null, null, null, null, null, null);
assertEquals(4, searchForAllUsers.size());
List<UserRepresentation> searchByIdpAlias =
managedRealm.admin().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<UserRepresentation> searchByIdpUserId =
managedRealm.admin().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<UserRepresentation> searchByIdpAliasAndUserId =
managedRealm.admin().users().search(null, null, null, null, null, identityProviderAlias1, idp1User1UserId, null, null,
null,
null);
assertEquals(1, searchByIdpAliasAndUserId.size());
assertEquals(idp1User1Username, searchByIdpAliasAndUserId.get(0).getUsername());
}
@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<UserRepresentation> searchByIdpAliasAndEnabled =
managedRealm.admin().users().search(null, null, null, null, null, identityProviderAlias, null, null, null, true, null);
assertEquals(1, searchByIdpAliasAndEnabled.size());
assertEquals(enabledUsername, searchByIdpAliasAndEnabled.get(0).getUsername());
List<UserRepresentation> searchByIdpAliasAndDisabled =
managedRealm.admin().users().search(null, null, null, null, null, identityProviderAlias, null, null, null, false,
null);
assertEquals(1, searchByIdpAliasAndDisabled.size());
assertEquals(disabledUsername, searchByIdpAliasAndDisabled.get(0).getUsername());
List<UserRepresentation> searchByIdpAliasWithoutEnabledFlag =
managedRealm.admin().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() {
List<String> userIds = createUsers();
String expectedUserId = userIds.get(0);
List<UserRepresentation> users = managedRealm.admin().users().search("id:" + expectedUserId, null, null);
assertEquals(1, users.size());
assertEquals(expectedUserId, users.get(0).getId());
users = managedRealm.admin().users().search("id: " + expectedUserId + " ", null, null);
assertEquals(1, users.size());
assertEquals(expectedUserId, users.get(0).getId());
// Should allow searching for multiple users
String expectedUserId2 = userIds.get(1);
List<UserRepresentation> multipleUsers = managedRealm.admin().users().search(String.format("id:%s %s", expectedUserId, expectedUserId2), 0 , 10);;
assertThat(multipleUsers, hasSize(2));
assertThat(multipleUsers.get(0).getId(), is(expectedUserId));
assertThat(multipleUsers.get(1).getId(), is(expectedUserId2));
// Should take arbitrary amount of spaces in between ids
List<UserRepresentation> multipleUsers2 = managedRealm.admin().users().search(String.format("id: %s %s ", expectedUserId, expectedUserId2), 0 , 10);;
assertThat(multipleUsers2, hasSize(2));
assertThat(multipleUsers2.get(0).getId(), is(expectedUserId));
assertThat(multipleUsers2.get(1).getId(), is(expectedUserId2));
}
@Test
public void infixSearch() {
List<String> userIds = createUsers();
// Username search
List<UserRepresentation> users = managedRealm.admin().users().search("*1*", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search("*y*", null, null);
assertThat(users.size(), is(0));
users = managedRealm.admin().users().search("*name*", null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("**", null, null);
assertThat(users, hasSize(9));
// First/Last name search
users = managedRealm.admin().users().search("*first1*", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search("*last*", null, null);
assertThat(users, hasSize(9));
// Email search
users = managedRealm.admin().users().search("*@localhost*", null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("*1@local*", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
}
@Test
public void prefixSearch() {
List<String> userIds = createUsers();
// Username search
List<UserRepresentation> users = managedRealm.admin().users().search("user", null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("user*", null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("name", null, null);
assertThat(users, hasSize(0));
users = managedRealm.admin().users().search("name*", null, null);
assertThat(users, hasSize(0));
users = managedRealm.admin().users().search("username1", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search("username1*", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search(null, null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("", null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("*", null, null);
assertThat(users, hasSize(9));
// First/Last name search
users = managedRealm.admin().users().search("first1", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search("first1*", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search("last", null, null);
assertThat(users, hasSize(9));
users = managedRealm.admin().users().search("last*", null, null);
assertThat(users, hasSize(9));
// Email search
users = managedRealm.admin().users().search("user1@local", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().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<UserRepresentation> users = managedRealm.admin().users().search("u*name", null, null);
assertThat(users, hasSize(9));
}
@Test
public void wildcardSearch() {
UserProfileResource upResource = managedRealm.admin().users().userProfile();
UPConfig upConfig = upResource.getConfiguration();
Map<String, Object> prohibitedCharsOrigCfg = upConfig.getAttribute(UserModel.USERNAME).getValidations().get(UsernameProhibitedCharactersValidator.ID);
upConfig.getAttribute(UserModel.USERNAME).getValidations().remove(UsernameProhibitedCharactersValidator.ID);
upResource.update(upConfig);
adminEvents.clear();
try {
createUser("0user\\\\0", "email0@emal");
createUser("1user\\\\", "email1@emal");
createUser("2user\\\\%", "email2@emal");
createUser("3user\\\\*", "email3@emal");
createUser("4user\\\\_", "email4@emal");
assertThat(managedRealm.admin().users().search("*", null, null), hasSize(5));
assertThat(managedRealm.admin().users().search("*user\\", null, null), hasSize(5));
assertThat(managedRealm.admin().users().search("\"2user\\\\%\"", null, null), hasSize(1));
} finally {
upConfig.getAttribute(UserModel.USERNAME).addValidation(UsernameProhibitedCharactersValidator.ID, prohibitedCharsOrigCfg);
upResource.update(upConfig);
}
}
@Test
public void exactSearch() {
List<String> userIds = createUsers();
// Username search
List<UserRepresentation> users = managedRealm.admin().users().search("\"username1\"", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
users = managedRealm.admin().users().search("\"user\"", null, null);
assertThat(users, hasSize(0));
users = managedRealm.admin().users().search("\"\"", null, null);
assertThat(users, hasSize(0));
// First/Last name search
users = managedRealm.admin().users().search("\"first1\"", null, null);
assertThat(users, hasSize(1));
assertThat(userIds.get(0), equalTo(users.get(0).getId()));
// Email search
users = managedRealm.admin().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<UserRepresentation> users = managedRealm.admin().users().search(
null, null, null, "test_email@test.co",
0, 10, null, null, true
);
assertEquals(0, users.size());
users = managedRealm.admin().users().search(
null, null, null, "test_email@test.com",
0, 10, null, null, true
);
assertEquals(1, users.size());
users = managedRealm.admin().users().search(
null, null, "test_last", "test_email@test.com",
0, 10, null, null, true
);
assertEquals(0, users.size());
users = managedRealm.admin().users().search(
null, null, "test_last_name", "test_email@test.com",
0, 10, null, null, true
);
assertEquals(1, users.size());
users = managedRealm.admin().users().search(
null, "test_first", "test_last_name", "test_email@test.com",
0, 10, null, null, true
);
assertEquals(0, users.size());
users = managedRealm.admin().users().search(
null, "test_first_name", "test_last_name", "test_email@test.com",
0, 10, null, null, true
);
assertEquals(1, users.size());
users = managedRealm.admin().users().search(
"test_usernam", "test_first_name", "test_last_name", "test_email@test.com",
0, 10, null, null, true
);
assertEquals(0, users.size());
users = managedRealm.admin().users().search(
"test_username", "test_first_name", "test_last_name", "test_email@test.com",
0, 10, null, null, true
);
assertEquals(1, users.size());
users = managedRealm.admin().users().search(
null, null, "test_last_name", null,
0, 10, null, null, true
);
assertEquals(2, users.size());
users = managedRealm.admin().users().search(
null, "test_first_name", null, null,
0, 10, null, null, true
);
assertEquals(2, users.size());
}
@Test
public void countUsersNotServiceAccount() {
createUsers();
Integer count = managedRealm.admin().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"));
String clientId = ApiUtil.getCreatedId(managedRealm.admin().clients().create(client));
// KEYCLOAK-5660, should not consider service accounts
assertEquals(9, managedRealm.admin().users().count().intValue());
// client cleanup
managedRealm.admin().clients().get(clientId).remove();
}
@Test
public void searchPaginated() {
createUsers();
List<UserRepresentation> users = managedRealm.admin().users().search("username", 0, 1);
assertEquals(1, users.size());
assertEquals("username1", users.get(0).getUsername());
users = managedRealm.admin().users().search("username", 5, 2);
assertEquals(2, users.size());
assertEquals("username6", users.get(0).getUsername());
assertEquals("username7", users.get(1).getUsername());
users = managedRealm.admin().users().search("username", 7, 20);
assertEquals(2, users.size());
assertEquals("username8", users.get(0).getUsername());
assertEquals("username9", users.get(1).getUsername());
users = managedRealm.admin().users().search("username", 0, 20);
assertEquals(9, users.size());
}
}

View File

@ -0,0 +1,340 @@
package org.keycloak.tests.admin.user;
import io.restassured.internal.common.assertion.Assertion;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.crypto.hash.Argon2Parameters;
import org.keycloak.crypto.hash.Argon2PasswordHashProviderFactory;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.tests.utils.admin.AdminEventPaths;
import org.keycloak.tests.utils.admin.ApiUtil;
import org.keycloak.testsuite.util.AccountHelper;
import java.util.Arrays;
import java.util.Collections;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KeycloakIntegrationTest
public class UserUpdateTest extends AbstractUserTest {
@InjectRealm(lifecycle = LifeCycle.METHOD)
ManagedRealm managedRealm;
@InjectOAuthClient
OAuthClient oauth;
@Test
public void updateUserWithHashedCredentials() {
UserRepresentation userRep = UserConfigBuilder.create()
.username("user_hashed_creds").name("Hashed", "User").email("user_hashed_creds@localhost").build();
String userId = createUser(userRep);
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));
managedRealm.admin().users().get(userId).update(userRepresentation);
oauth.openLoginForm();
loginPage.assertCurrent();
loginPage.fillLogin("user_hashed_creds", "admin");
loginPage.submit();
assertTrue(driver.getPageSource().contains("Happy days"));
AccountHelper.logout(managedRealm.admin(), "user_hashed_creds");
}
@Test
public void updateUserWithNewUsername() {
switchEditUsernameAllowedOn(true);
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
UserRepresentation userRep = user.toRepresentation();
userRep.setUsername("user11");
updateUser(user, userRep);
userRep = managedRealm.admin().users().get(id).toRepresentation();
Assertions.assertEquals("user11", userRep.getUsername());
}
@Test
public void updateUserWithoutUsername() {
switchEditUsernameAllowedOn(true);
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
UserRepresentation rep = new UserRepresentation();
rep.setFirstName("Firstname");
user.update(rep);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.userResourcePath(id), rep, ResourceType.USER);
rep = new UserRepresentation();
rep.setLastName("Lastname");
user.update(rep);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.userResourcePath(id), rep, ResourceType.USER);
rep = managedRealm.admin().users().get(id).toRepresentation();
Assertions.assertEquals("user1", rep.getUsername());
Assertions.assertEquals("user1@localhost", rep.getEmail());
Assertions.assertEquals("Firstname", rep.getFirstName());
Assertions.assertEquals("Lastname", rep.getLastName());
}
@Test
public void updateUserWithEmailAsUsernameEditUsernameDisabled() {
switchRegistrationEmailAsUsername(true);
RealmRepresentation rep = managedRealm.admin().toRepresentation();
assertFalse(rep.isEditUsernameAllowed());
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
UserRepresentation userRep = user.toRepresentation();
Assertions.assertEquals("user1@localhost", userRep.getUsername());
userRep.setEmail("user11@localhost");
updateUser(user, userRep);
userRep = managedRealm.admin().users().get(id).toRepresentation();
Assertions.assertEquals("user11@localhost", userRep.getUsername());
Assertions.assertEquals("user11@localhost", userRep.getEmail());
}
@Test
public void updateUserWithEmailAsUsernameEditUsernameAllowed() {
switchRegistrationEmailAsUsername(true);
switchEditUsernameAllowedOn(true);
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
UserRepresentation userRep = user.toRepresentation();
Assertions.assertEquals("user1@localhost", userRep.getUsername());
userRep.setEmail("user11@localhost");
updateUser(user, userRep);
userRep = managedRealm.admin().users().get(id).toRepresentation();
Assertions.assertEquals("user11@localhost", userRep.getUsername());
Assertions.assertEquals("user11@localhost", userRep.getEmail());
}
@Test
public void updateUserWithExistingEmail() {
final String userId = createUser();
assertNotNull(userId);
assertNotNull(createUser("user2", "user2@localhost"));
UserResource user = managedRealm.admin().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);
Assertions.assertEquals("User exists with same email", error.getErrorMessage());
Assertions.assertNull(adminEvents.poll());
}
}
@Test
public void updateUserWithNewUsernameNotPossible() {
RealmRepresentation realmRep = managedRealm.admin().toRepresentation();
assertFalse(realmRep.isEditUsernameAllowed());
String id = createUser();
UserResource user = managedRealm.admin().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);
Assertions.assertEquals("error-user-attribute-read-only", error.getErrorMessage());
}
userRep = managedRealm.admin().users().get(id).toRepresentation();
Assertions.assertEquals("user1", userRep.getUsername());
}
@Test
public void updateUserWithNewUsernameAccessingViaOldUsername() {
switchEditUsernameAllowedOn(true);
createUser();
try {
UserResource user = managedRealm.admin().users().get("user1");
UserRepresentation userRep = user.toRepresentation();
userRep.setUsername("user1");
updateUser(user, userRep);
managedRealm.admin().users().get("user11").toRepresentation();
fail("Expected failure");
} catch (ClientErrorException e) {
Assertions.assertEquals(404, e.getResponse().getStatus());
Assertions.assertNull(adminEvents.poll());
} 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 = managedRealm.admin().users().get(createdId);
userRep = user.toRepresentation();
userRep.setUsername("user1");
user.update(userRep);
fail("Expected failure");
} catch (ClientErrorException e) {
Assertions.assertEquals(409, e.getResponse().getStatus());
Assertions.assertNull(adminEvents.poll());
} 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(credential, "Expecting credential");
assertEquals(Argon2PasswordHashProviderFactory.ID, credential.getPasswordCredentialData().getAlgorithm());
assertEquals(Argon2Parameters.DEFAULT_ITERATIONS, credential.getPasswordCredentialData().getHashIterations());
assertNotEquals("ABCD", credential.getPasswordSecretData().getValue());
Assertions.assertEquals(CredentialRepresentation.PASSWORD, credential.getType());
UserResource userResource = managedRealm.admin().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(updatedCredential, "Expecting credential");
assertEquals(Argon2PasswordHashProviderFactory.ID, updatedCredential.getPasswordCredentialData().getAlgorithm());
assertEquals(Argon2Parameters.DEFAULT_ITERATIONS, updatedCredential.getPasswordCredentialData().getHashIterations());
assertNotEquals("EFGH", updatedCredential.getPasswordSecretData().getValue());
Assertions.assertEquals(CredentialRepresentation.PASSWORD, updatedCredential.getType());
}
@Test
public void testAccessUserFromOtherRealm() {
RealmRepresentation firstRealm = new RealmRepresentation();
firstRealm.setRealm("first-realm");
adminClient.realms().create(firstRealm);
UserRepresentation firstUser = new UserRepresentation();
firstUser.setUsername("first");
firstUser.setEmail("first@first-realm.org");
firstUser.setId(ApiUtil.getCreatedId(adminClient.realm(firstRealm.getRealm()).users().create(firstUser)));
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();
adminClient.realm(firstRealm.getRealm()).remove();
}
}
private void enableBruteForce(boolean enable) {
RealmRepresentation rep = managedRealm.admin().toRepresentation();
managedRealm.cleanup().add(r -> r.update(rep));
rep.setBruteForceProtected(enable);
managedRealm.admin().update(rep);
AdminEventAssertion.assertSuccess(adminEvents.poll()).operationType(OperationType.UPDATE).representation(rep).resourceType(ResourceType.REALM);
}
}

View File

@ -48,5 +48,13 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-storage</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-storage-private</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,72 @@
/*
* 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.events;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerTransaction;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class TestEventsListenerProvider implements EventListenerProvider {
private static final BlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
private static final BlockingQueue<AdminEvent> adminEvents = new LinkedBlockingQueue<>();
private final EventListenerTransaction tx = new EventListenerTransaction((event, includeRepre) -> adminEvents.add(event), events::add);
public TestEventsListenerProvider(KeycloakSession session) {
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public void onEvent(Event event) {
tx.addEvent(event);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
tx.addAdminEvent(event, includeRepresentation);
}
@Override
public void close() {
}
public static Event poll() {
return events.poll();
}
public static AdminEvent pollAdminEvent() {
return adminEvents.poll();
}
public static void clear() {
events.clear();
}
public static void clearAdminEvents() {
adminEvents.clear();
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.events;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class TestEventsListenerProviderFactory implements EventListenerProviderFactory {
public static final String PROVIDER_ID = "event-queue";
@Override
public EventListenerProvider create(KeycloakSession session) {
return new TestEventsListenerProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View File

@ -0,0 +1,146 @@
/*
* 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.federation;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.models.*;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.storage.UserStoragePrivateUtil;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DummyUserFederationProvider implements UserStorageProvider,
UserLookupProvider,
UserRegistrationProvider,
CredentialInputValidator {
private final Map<String, UserModel> users;
private KeycloakSession session;
private ComponentModel component;
// Hardcoded password of test-user
public static final String HARDCODED_PASSWORD = "secret";
// Hardcoded otp code, which will be always considered valid for the test-user
public static final String HARDCODED_OTP = "123456";
public DummyUserFederationProvider(KeycloakSession session, ComponentModel component, Map<String, UserModel> users) {
this.users = users;
this.session = session;
this.component = component;
}
@Override
public UserModel addUser(RealmModel realm, String username) {
UserModel local = UserStoragePrivateUtil.userLocalStorage(session).addUser(realm, username);
local.setFederationLink(component.getId());
users.put(username, local);
return local;
}
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
return users.remove(user.getUsername()) != null;
}
@Override
public UserModel getUserById(RealmModel realm, String id) {
return null;
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
return users.get(username);
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
return null;
}
@Override
public void preRemove(RealmModel realm) {
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
}
public Set<String> getSupportedCredentialTypes() {
return new HashSet<>(Arrays.asList(PasswordCredentialModel.TYPE, OTPCredentialModel.TYPE));
}
@Override
public boolean supportsCredentialType(String credentialType) {
return getSupportedCredentialTypes().contains(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
if (!supportsCredentialType(credentialType)) return false;
if (user.getUsername().equals("test-user")) {
return true;
} else {
return false;
}
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) {
if (user.getUsername().equals("test-user")) {
if (PasswordCredentialModel.TYPE.equals(credentialInput.getType())) {
return HARDCODED_PASSWORD.equals(credentialInput.getChallengeResponse());
} else if (OTPCredentialModel.TYPE.equals(credentialInput.getType())) {
return HARDCODED_OTP.equals(credentialInput.getChallengeResponse());
}
}
return false;
}
@Override
public void close() {
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.federation;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.user.ImportSynchronization;
import org.keycloak.storage.user.SynchronizationResult;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DummyUserFederationProviderFactory implements UserStorageProviderFactory<DummyUserFederationProvider>, ImportSynchronization {
private static final Logger logger = Logger.getLogger(DummyUserFederationProviderFactory.class);
public static final String PROVIDER_NAME = "dummy";
private AtomicInteger fullSyncCounter = new AtomicInteger();
private AtomicInteger changedSyncCounter = new AtomicInteger();
private Map<String, UserModel> users = new HashMap<String, UserModel>();
@Override
public DummyUserFederationProvider create(KeycloakSession session, ComponentModel model) {
return new DummyUserFederationProvider(session, model, users);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return ProviderConfigurationBuilder.create()
.property().name("important.config")
.type(ProviderConfigProperty.STRING_TYPE)
.add().build();
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_NAME;
}
@Override
public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) {
logger.info("syncAllUsers invoked");
fullSyncCounter.incrementAndGet();
return SynchronizationResult.empty();
}
@Override
public SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) {
logger.info("syncChangedUsers invoked");
changedSyncCounter.incrementAndGet();
return SynchronizationResult.empty();
}
public int getFullSyncCounter() {
return fullSyncCounter.get();
}
public int getChangedSyncCounter() {
return changedSyncCounter.get();
}
}

View File

@ -0,0 +1,395 @@
/*
* 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.federation;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.models.*;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.credential.PasswordUserCredentialModel;
import org.keycloak.storage.*;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
import org.keycloak.storage.federated.UserGroupMembershipFederatedStorage;
import org.keycloak.storage.user.ImportedUserValidation;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater,
CredentialInputValidator, UserGroupMembershipFederatedStorage.Streams, UserQueryProvider, ImportedUserValidation {
private static final Logger log = Logger.getLogger(UserMapStorage.class);
protected final Map<String, String> userPasswords;
protected final ConcurrentMap<String, Set<String>> userGroups;
protected ComponentModel model;
protected KeycloakSession session;
protected EditMode editMode;
private transient Boolean importEnabled;
public static final AtomicInteger allocations = new AtomicInteger(0);
public static final AtomicInteger closings = new AtomicInteger(0);
public static final AtomicInteger realmRemovals = new AtomicInteger(0);
public static final AtomicInteger groupRemovals = new AtomicInteger(0);
public static final AtomicInteger roleRemovals = new AtomicInteger(0);
public UserMapStorage(KeycloakSession session, ComponentModel model, Map<String, String> userPasswords, ConcurrentMap<String, Set<String>> userGroups) {
this.session = session;
this.model = model;
this.userPasswords = userPasswords;
this.userGroups = userGroups;
allocations.incrementAndGet();
String editModeString = model.getConfig().getFirst(LDAPConstants.EDIT_MODE);
if (editModeString == null) {
this.editMode = EditMode.UNSYNCED;
} else {
this.editMode = EditMode.valueOf(editModeString);
}
}
private static String getUserIdInMap(RealmModel realm, String userId) {
return realm.getId() + "/" + userId;
}
@Override
public UserModel getUserById(RealmModel realm, String id) {
StorageId storageId = new StorageId(id);
final String username = storageId.getExternalId();
if (!userPasswords.containsKey(translateUserName(username))) {
return null;
}
return createUser(realm, username);
}
public Set<String> getUsernames() {
return userPasswords.keySet();
}
private UserModel createUser(RealmModel realm, String username) {
UserModel user;
if (isImportEnabled()) {
user = UserStoragePrivateUtil.userLocalStorage(session).addUser(realm, username);
user.setEnabled(true);
user.setFederationLink(model.getId());
} else {
user = new AbstractUserAdapterFederatedStorage.Streams(session, realm, model) {
@Override
public String getUsername() {
return username.toLowerCase();
}
@Override
public void setUsername(String innerUsername) {
if (! Objects.equals(innerUsername, username.toLowerCase())) {
throw new RuntimeException("Unsupported");
}
}
@Override
public void leaveGroup(GroupModel group) {
UserMapStorage.this.leaveGroup(realm, getUsername(), group);
}
@Override
public void joinGroup(GroupModel group) {
UserMapStorage.this.joinGroup(realm, getUsername(), group);
}
@Override
public String getFederationLink() {
return model.getId();
}
};
}
return user;
}
@Override
public boolean supportsCredentialType(String credentialType) {
return PasswordCredentialModel.TYPE.equals(credentialType);
}
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (editMode == EditMode.READ_ONLY) {
throw new ReadOnlyException("Federated storage is not writable");
}
if (!(input instanceof UserCredentialModel)) {
return false;
}
if (input.getType().equals(PasswordCredentialModel.TYPE)) {
userPasswords.put(translateUserName(user.getUsername()), input.getChallengeResponse());
return true;
} else {
return false;
}
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
@Override
public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
return Stream.empty();
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return PasswordCredentialModel.TYPE.equals(credentialType);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
// Test "instanceof PasswordUserCredentialModel" on purpose. We want to test that the backwards compatibility
if (!(input instanceof PasswordUserCredentialModel)) {
return false;
}
if (input.getType().equals(PasswordCredentialModel.TYPE)) {
String pw = userPasswords.get(translateUserName(user.getUsername()));
// Using "getValue" on purpose here, to test that backwards compatibility works as expected
return pw != null && pw.equals(((UserCredentialModel) input).getValue());
} else {
return false;
}
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
if (!userPasswords.containsKey(translateUserName(username))) {
return null;
}
return createUser(realm, username);
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
return null;
}
@Override
public UserModel addUser(RealmModel realm, String username) {
if (editMode == EditMode.READ_ONLY) {
throw new ReadOnlyException("Federated storage is not writable");
}
userPasswords.put(translateUserName(username), "");
return createUser(realm, username);
}
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString());
userPasswords.remove(translateUserName(user.getUsername()));
return true;
}
return userPasswords.remove(translateUserName(user.getUsername())) != null;
}
public boolean removeUserByName(String userName) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", userName, editMode.toString());
userPasswords.remove(translateUserName(userName));
return true;
}
return userPasswords.remove(translateUserName(userName)) != null;
}
public boolean isImportEnabled() {
if (importEnabled == null) {
String val = model.getConfig().getFirst(IMPORT_ENABLED);
if (val == null) {
importEnabled = true;
} else {
importEnabled = Boolean.valueOf(val);
}
}
return importEnabled;
}
public void setImportEnabled(boolean flag) {
importEnabled = flag;
model.getConfig().putSingle(IMPORT_ENABLED, Boolean.toString(flag));
}
@Override
public void preRemove(RealmModel realm) {
log.infof("preRemove: realm=%s", realm.getName());
realmRemovals.incrementAndGet();
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
log.infof("preRemove: realm=%s, group=%s", realm.getName(), group.getName());
groupRemovals.incrementAndGet();
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
log.infof("preRemove: realm=%s, role=%s", realm.getName(), role.getName());
roleRemovals.incrementAndGet();
}
@Override
public void close() {
closings.incrementAndGet();
}
@Override
public int getUsersCount(RealmModel realm) {
return userPasswords.size();
}
@Override
public Stream<UserModel> searchForUserStream(RealmModel realm, String search) {
String tSearch = translateUserName(search);
return userPasswords.keySet().stream()
.sorted()
.filter(userName -> translateUserName(userName).contains(tSearch))
.map(userName -> createUser(realm, userName));
}
@Override
public Stream<UserModel> searchForUserStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) {
String tSearch = translateUserName(search);
Stream<String> userStream = userPasswords.keySet().stream()
.sorted()
.filter(userName -> translateUserName(userName).contains(search));
return paginatedStream(userStream, firstResult, maxResults).map(userName -> createUser(realm, userName));
}
@Override
public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, String> params, Integer firstResult, Integer maxResults) {
Stream<String> userStream = userPasswords.keySet().stream()
.sorted();
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (value == null) {
continue;
}
switch (key) {
case UserModel.USERNAME:
case UserModel.SEARCH:
if (Boolean.valueOf(params.getOrDefault(UserModel.EXACT, Boolean.FALSE.toString()))) {
userStream = userStream.filter(s -> s.toLowerCase().equals(value.toLowerCase()));
} else {
userStream = userStream.filter(s -> s.toLowerCase().contains(value.toLowerCase()));
}
}
}
return paginatedStream(userStream, firstResult, maxResults).map(userName -> createUser(realm, userName));
}
@Override
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult, Integer maxResults) {
return getMembershipStream(realm, group, firstResult == null ? -1 : firstResult, maxResults == null ? -1 : maxResults)
.map(userName -> createUser(realm, userName));
}
@Override
public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realm, String attrName, String attrValue) {
if (isImportEnabled()) {
return UserStoragePrivateUtil.userLocalStorage(session).searchForUserByUserAttributeStream(realm, attrName, attrValue);
} else {
return UserStorageUtil.userFederatedStorage(session).getUsersByUserAttributeStream(realm, attrName, attrValue)
.map(userName -> createUser(realm, userName));
}
}
@Override
public Stream<GroupModel> getGroupsStream(RealmModel realm, String userId) {
Set<String> set = userGroups.get(getUserIdInMap(realm, userId));
if (set == null) {
return Stream.empty();
}
return set.stream().map(realm::getGroupById);
}
@Override
public void joinGroup(RealmModel realm, String userId, GroupModel group) {
Set<String> groups = userGroups.computeIfAbsent(getUserIdInMap(realm, userId), userName -> new ConcurrentSkipListSet());
groups.add(group.getId());
}
@Override
public void leaveGroup(RealmModel realm, String userId, GroupModel group) {
Set<String> set = userGroups.get(getUserIdInMap(realm, userId));
if (set != null) {
set.remove(group.getId());
}
}
@Override
public Stream<String> getMembershipStream(RealmModel realm, GroupModel group, Integer firstResult, Integer max) {
Stream<String> userStream = paginatedStream(userGroups.entrySet().stream(), firstResult, max)
.filter(me -> me.getValue().contains(group.getId()))
.map(Map.Entry::getKey)
.filter(realmUser -> realmUser.startsWith(realm.getId()))
.map(realmUser -> realmUser.substring(realmUser.indexOf("/") + 1));
return userStream;
}
@Override
public UserModel validate(RealmModel realm, UserModel local) {
final boolean userExists = userPasswords.containsKey(translateUserName(local.getUsername()));
if (! userExists) {
userGroups.remove(getUserIdInMap(realm, local.getUsername()));
}
return userExists ? local : null;
}
private static String translateUserName(String userName) {
return userName == null ? null : userName.toLowerCase();
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.federation;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.storage.UserStorageProviderFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserMapStorageFactory implements UserStorageProviderFactory<UserMapStorage> {
public static final String PROVIDER_ID = "user-password-map-arq";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty attr = new ProviderConfigProperty("attr", "attr",
"This is some attribute",
ProviderConfigProperty.STRING_TYPE, null);
configProperties.add(attr);
}
private final Map<String, String> userPasswords = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Set<String>> userGroups = new ConcurrentHashMap<>();
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public UserMapStorage create(KeycloakSession session, ComponentModel model) {
return new UserMapStorage(session, model, userPasswords, userGroups);
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
public void clear() {
userPasswords.clear();
userGroups.clear();
}
}

View File

@ -0,0 +1,35 @@
#
# 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.
#
#
# 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.
#
org.keycloak.testsuite.events.TestEventsListenerProviderFactory

View File

@ -0,0 +1,2 @@
org.keycloak.testsuite.federation.DummyUserFederationProviderFactory
org.keycloak.testsuite.federation.UserMapStorageFactory

View File

@ -44,6 +44,11 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.testframework</groupId>
<artifactId>keycloak-test-framework-remote-providers</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>

View File

@ -0,0 +1,108 @@
/*
* 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.tests.utils;
import jakarta.mail.Address;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class MailUtils {
private static Pattern mailPattern = Pattern.compile("http[^\\s\"]*");
public static String getLink(String body) {
Matcher matcher = mailPattern.matcher(body);
if (matcher.find()) {
return matcher.group();
}
throw new AssertionError("No link found in " + body);
}
public static String getPasswordResetEmailLink(MimeMessage message) throws IOException {
return getPasswordResetEmailLink(new EmailBody(message));
}
/**
*
* @param message email message
* @return first recipient of the email message
* @throws MessagingException
*/
public static String getRecipient(MimeMessage message) throws MessagingException {
Address[] recipients = message.getRecipients(MimeMessage.RecipientType.TO);
return recipients[0].toString();
}
public static String getPasswordResetEmailLink(EmailBody body) throws IOException {
final String textChangePwdUrl = getLink(body.getText());
String htmlChangePwdUrl = getLink(body.getHtml());
assertEquals(htmlChangePwdUrl, textChangePwdUrl);
return htmlChangePwdUrl;
}
public static EmailBody getBody(MimeMessage message) throws IOException {
return new EmailBody(message);
}
public static class EmailBody {
private String text;
private String html;
private EmailBody(MimeMessage message) throws IOException {
try {
Multipart multipart = (Multipart) message.getContent();
String textContentType = multipart.getBodyPart(0).getContentType();
assertEquals("text/plain; charset=UTF-8", textContentType);
text = (String) multipart.getBodyPart(0).getContent();
String htmlContentType = multipart.getBodyPart(1).getContentType();
assertEquals("text/html; charset=UTF-8", htmlContentType);
html = (String) multipart.getBodyPart(1).getContent();
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
public String getText() {
return text;
}
public String getHtml() {
return html;
}
}
}

View File

@ -0,0 +1,74 @@
package org.keycloak.tests.utils.runonserver;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testframework.remote.providers.runonserver.FetchOnServer;
import org.keycloak.testframework.remote.providers.runonserver.FetchOnServerWrapper;
import java.util.List;
import java.util.stream.Collectors;
/**
* Created by st on 26.01.17.
*/
public class RunHelpers {
public static FetchOnServerWrapper<RealmRepresentation> internalRealm() {
return new FetchOnServerWrapper() {
@Override
public FetchOnServer getRunOnServer() {
return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session, session.getContext().getRealm(), true);
}
@Override
public Class<RealmRepresentation> getResultClass() {
return RealmRepresentation.class;
}
};
}
public static FetchOnServerWrapper<ComponentRepresentation> internalComponent(String componentId) {
return new FetchOnServerWrapper() {
@Override
public FetchOnServer getRunOnServer() {
return (FetchOnServer) session -> ModelToRepresentation.toRepresentation(session, session.getContext().getRealm().getComponent(componentId), true);
}
@Override
public Class<ComponentRepresentation> getResultClass() {
return ComponentRepresentation.class;
}
};
}
public static FetchOnServerWrapper<CredentialModel> fetchCredentials(String username) {
return new FetchOnServerWrapper() {
@Override
public FetchOnServer getRunOnServer() {
return (FetchOnServer) session -> {
RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().getUserByUsername(realm, username);
List<CredentialModel> storedCredentialsByType = user.credentialManager().getStoredCredentialsByTypeStream(CredentialRepresentation.PASSWORD)
.collect(Collectors.toList());
return storedCredentialsByType.get(0);
};
}
@Override
public Class getResultClass() {
return CredentialModel.class;
}
};
}
}

View File

@ -185,19 +185,6 @@ public class UserBuilder {
return this;
}
public UserBuilder federatedLink(String identityProvider, String federatedUserId) {
if (rep.getFederatedIdentities() == null) {
rep.setFederatedIdentities(new LinkedList<>());
}
FederatedIdentityRepresentation federatedIdentity = new FederatedIdentityRepresentation();
federatedIdentity.setUserId(federatedUserId);
federatedIdentity.setUserName(rep.getUsername());
federatedIdentity.setIdentityProvider(identityProvider);
rep.getFederatedIdentities().add(federatedIdentity);
return this;
}
public UserRepresentation build() {
return rep;
}