8000 Generate random passwords for imported users in forms and webauthn tests (26.2) by rmartinc · Pull Request #38980 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/actions/install-chrome/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ description: Download and install the compatible Chrome and Chromedriver

inputs:
version:
description: The version of Chrome and Chromedriver to install.
description: The version of Chrome and Chromedriver to install. By default none is installed.
required: false
default: 134.0.6998.165 # Ensures compatibility with the testsuite
default: default # E.g. 135.0.7049.84 (fixed version), default (chrome provided by GHA box)

runs:
using: composite
steps:

# Cannot use Chrome for Testing. Acts weirdly and would probably require some changes in the testsuite.
- id: install-chrome
name: Install Chrome
if: inputs.version != 'default'
shell: bash
run: |
sudo apt-get remove google-chrome-stable
Expand All @@ -22,6 +22,7 @@ runs:

- id: install-chromedriver
name: Install Chromedriver
if: inputs.version != 'default'
shell: bash
run: |
wget https://storage.googleapis.com/chrome-for-testing-public/${{ inputs.version }}/linux64/chromedriver-linux64.zip -O /tmp/chromedriver.zip --no-verbose
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/ci.yml
8BD0
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ jobs:
name: Integration test setup
uses: ./.github/actions/integration-test-setup

- uses: ./.github/actions/install-chrome

- name: Run base tests
run: |
TESTS=`testsuite/integration-arquillian/tests/base/testsuites/base-suite.sh ${{ matrix.group }}`
Expand Down Expand Up @@ -162,8 +160,6 @@ jobs:
- name: Build app servers
run: ./mvnw install -DskipTests -Pbuild-app-servers -f testsuite/integration-arquillian/servers/app-server/pom.xml

- uses: ./.github/actions/install-chrome

- name: Run adapter tests
run: |
TESTS="org.keycloak.testsuite.adapter.**"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -51,6 +52,7 @@ public final class TestContext {
private Keycloak adminClient;
private KeycloakTestingClient testingClient;
private List<RealmRepresentation> testRealmReps = new ArrayList<>();
private Map<String, String> userPasswords = new HashMap<>();

// Track if particular test was initialized. What exactly means "initialized" is test dependent (Eg. some user in @Before method was created, so we can set initialized to true
// to avoid creating user when @Before method is executed for 2nd time)
Expand Down Expand Up @@ -179,6 +181,14 @@ public void addTestRealmsToTestRealmReps(List<RealmRepresentation> testRealmReps
this.testRealmReps.addAll(testRealmReps);
}

public Map<String, String> getUserPasswords() {
return userPasswords;
}

public void setUserPasswords(Map<String, String> userPasswords) {
this.userPasswords = userPasswords;
}

public boolean isInitialized() {
return initialized;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@
public @interface IgnoreBrowserDriver {

/**
* Define for which WebDriver the test method/class should be ignored
* Define for which WebDrivers the test method/class should be ignored
* @return
*/
Class<? extends WebDriver> value();
Class<? extends WebDriver>[] value();

/**
* Define whether the value should be negated
*
* Usable in cases when we want to execute test method/class with all WebDrivers except the one specified in value()
* Usable in cases when we want to execute test method/class with all WebDrivers except the ones specified in value()
* @return
*/
boolean negate() default false;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private ExecutionDecision decideIgnoring(AnnotatedElement element) {
final WebDriver webDriver = driver.get();

Predicate<IgnoreBrowserDriver> shouldBeIgnored = (item) -> {
return webDriver != null && (isDriverInstanceOf(webDriver, item.value()) ^ item.negate());
return webDriver != null && (isPresent(webDriver, item.value()) ^ item.negate());
};

return Arrays.stream(element.getAnnotationsByType(IgnoreBrowserDriver.class))
Expand All @@ -78,6 +78,13 @@ private ExecutionDecision decideIgnoring(AnnotatedElement element) {
.orElse(ExecutionDecision.execute());
}

private boolean isPresent(WebDriver webDriver, Class<? extends WebDriver>[] items) {
if (items == null) {
return false;
}
return Arrays.stream(items).filter(item -> isDriverInstanceOf(webDriver, item)).findAny().isPresent();
}

@Override
public int precedence() {
return Integer.MIN_VALUE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public class RegisterPage extends LanguageComboboxAwarePage
@FindBy(linkText = "« Back to Login")
private WebElement backToLoginLink;

public void register(String firstName, String lastName, String email, String username, String password) {
register(firstName, lastName, email, username, password, password, null, null, null);
}

public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
register(firstName, lastName, email, username, password, passwordConfirm, null, null, null);
}
Expand Down Expand Up @@ -146,6 +150,10 @@ public void register(String firstName, String lastName, String email, String use
UIUtils.clickLink(submitButton);
}

public void registerWithEmailAsUsername(String firstName, String lastName, String email, String password) {
registerWithEmailAsUsername(firstName, lastName, email, password, password);
}

public void registerWithEmailAsUsername(String firstName, String lastName, String email, String password, String passwordConfirm) {
firstNameInput.clear();
if (firstName != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.pages;

import org.keycloak.testsuite.util.UIUtils;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

Expand All @@ -30,15 +31,16 @@ public class TermsAndConditionsPage extends AbstractPage {
@FindBy(id = "kc-decline")
private WebElement cancelButton;

@Override
public boolean isCurrent() {
return PageUtils.getPageTitle(driver).equals("Terms and Conditions");
}

public void acceptTerms() {
submitButton.click();
UIUtils.clickLink(submitButton);
}
public void declineTerms() {
cancelButton.click();
UIUtils.clickLink(cancelButton);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,18 @@ public void beforeAuthTest() {


public void createTestUserWithAdminClient() {
createTestUserWithAdminClient(true);
createTestUserWithAdminClient(true, PASSWORD);
}

public void createTestUserWithAdminClient(boolean setRealmRoles) {
createTestUserWithAdminClient(setRealmRoles, PASSWORD);
}

public void createTestUserWithAdminClient(boolean setRealmRoles, String password) {
ApiUtil.removeUserByUsername(testRealmResource(), "test");

log.debug("creating test user");
String id = createUserAndResetPasswordWithAdminClient(testRealmResource(), testUser, PASSWORD);
String id = createUserAndResetPasswordWithAdminClient(testRealmResource(), testUser, password);
testUser.setId(id);

if (setRealmRoles) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 2025 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* 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;

import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;

/**
* <p>Abstract class that re-generates all imported user passwords with a random one.
* This way all the passwords are random and cannot be rejected by security
* configurations in browsers (chrome for example). The passwords are stored in
* a map inside the test context to be retrieved using the username.</p>
*
* @author rmartinc
*/
abstract public class AbstractChangeImportedUserPasswordsTest extends AbstractTestRealmKeycloakTest {

private Map<String, String> userPasswords = null;

@Before
@Override
public void beforeAbstractKeycloakTest() throws Exception {
super.beforeAbstractKeycloakTest();
if (userPasswords == null) {
userPasswords = testContext.getUserPasswords();
}
}

@Override
public void configureTestRealm(RealmRepresentation testRealm) {
if (userPasswords == null) {
userPasswords = testContext.getUserPasswords();
}
userPasswords.clear();
List<UserRepresentation> users = testRealm.getUsers();
for (UserRepresentation user : users) {
List<CredentialRepresentation> credentials = user.getCredentials();
if (credentials != null) {
for (CredentialRepresentation cred : credentials) {
if (CredentialRepresentation.PASSWORD.equals(cred.getType())) {
// re-generate the password for the user using a random one
cred.setValue(generatePassword(user.getUsername()));
}
}
}
}
testContext.setUserPasswords(userPasswords);
}

protected String changePassword(String username) {
UserResource userRes = ApiUtil.findUserByUsernameId(testRealm(), username);
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(PasswordCredentialModel.TYPE);
credential.setTemporary(Boolean.FALSE);
credential.setValue(generatePassword());
userRes.resetPassword(credential);
userPasswords.put(username, credential.getValue());
return credential.getValue();
}

protected void changePasswords(String... usernames) {
if (usernames != null) {
for (String username: usernames) {
changePassword(username);
}
}
}

protected void generatePasswords(String... usernames) {
if (usernames != null) {
for (String username: usernames) {
generatePassword(username);
}
}
}

protected String generatePassword(String username) {
final String password = generatePassword();
userPasswords.put(username, password);
return password;
}

protected String getPassword(String username) {
final String password = userPasswords.get(username);
Assert.assertNotNull("password not generated for user " + username, password);
return password;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.common.util.Time;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.idm.ClientRepresentation;
Expand Down Expand Up @@ -96,7 +98,6 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import org.keycloak.models.UserModel;
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
Expand Down Expand Up @@ -785,4 +786,11 @@ public static <T> void eventuallyEquals(String message, T expected, Supplier<T>
}
}

protected static String generatePassword() {
return generatePassword(64);
}

protected static String generatePassword(int length) {
return SecretGenerator.getInstance().randomString(length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;

import java.lang.reflect.Field;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AbstractChangeImportedUserPasswordsTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
Expand Down Expand Up @@ -49,7 +49,7 @@
/**
* @author Stan Silvert
*/
public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKeycloakTest {
public abstract class AbstractAppInitiatedActionTest extends AbstractChangeImportedUserPasswordsTest {

protected static final String SUCCESS = "success";
protected static final String CANCELLED = "cancelled";
Expand Down
Loading
0