8000 Migrate basic broker infrastructure to new testsuite by sguilhen · Pull Request #51859 · 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.keycloak.tests.broker;

import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testsuite.util.AccountHelper;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
* Protocol-agnostic broker login scenarios. Concrete OIDC/SAML tests get these {@code @Test} methods by
* extending a protocol-specific subclass that supplies the realms and broker configuration.
*/
public abstract class AbstractBrokerLoginTest extends AbstractBrokerTest {
Comment thread
sguilhen marked this conversation as resolved.
Resolved

@Test
public void testLogInAsUserInIDP() {
loginThroughIdpAndLogout();
}

// Shared brokered login + single-logout round-trip, used both as its own test and as the setup step for
// testLoginWithExistingUser(), so neither test invokes the other's @Test method as a subroutine.
protected void loginThroughIdpAndLogout() {
loginUser();
singleLogout();
}
Comment thread
sguilhen marked this conversation as resolved.
Resolved

protected void loginUser() {
logInAsUserInIDP();

updateAccountInformation();

ManagedRealm consumerRealm = getConsumerRealm();
UserRepresentation userRep = AccountHelper.getUserRepresentation(
consumerRealm.admin(), getUserLogin());
Assertions.assertNotNull(userRep, "There must be user " + getUserLogin() + " in consumer realm");
// The review-profile page filled in these names via the UI; assert they were actually persisted
// rather than overwriting them here, so a broken UI flow is caught instead of masked.
Assertions.assertEquals("Firstname", userRep.getFirstName(),
"First name should have been persisted by the review-profile page");
Assertions.assertEquals("Lastname", userRep.getLastName(),
"Last name should have been persisted by the review-profile page");

int userCount = consumerRealm.admin().users().count();
Assertions.assertTrue(userCount > 0, "There must be at least one user");

// userRep was fetched above via an exact username search, so assert its email directly instead of
// scanning the (paginated) users().list(), which could miss the user beyond the first page.
Assertions.assertEquals(getUserEmail(), userRep.getEmail(),
"There must be user " + getUserLogin() + " with the expected email in consumer realm");
}

protected void singleLogout() {
ManagedRealm consumerRealm = getConsumerRealm();
ManagedRealm providerRealm = getProviderRealm();

oauth.openLoginForm();
Assertions.assertTrue(oauth.parseLoginResponse().isSuccess(), "Should be logged in");

AccountHelper.logout(consumerRealm.admin(), getUserLogin());
AccountHelper.logout(providerRealm.admin(), getUserLogin());

oauth.openLoginForm();
loginPage.assertCurrent();
}

@Test
public void testLoginWithExistingUser() {
ManagedRealm consumerRealm = getConsumerRealm();

int userCountBefore = consumerRealm.admin().users().count();

loginThroughIdpAndLogout();

int userCount = consumerRealm.admin().users().count();
Assertions.assertEquals(userCountBefore + 1, userCount,
"First broker login should have created exactly one user in the consumer realm");
Comment thread
sguilhen marked this conversation as resolved.
Resolved
Comment thread
sguilhen marked this conversation as resolved.
Resolved

oauth.openLoginForm();

logInWithBroker();

loginPage.fillLogin(getUserLogin(), getUserPassword());
loginPage.submit();

Assertions.assertTrue(oauth.parseLoginResponse().isSuccess());
Assertions.assertEquals(userCount, consumerRealm.admin().users().count());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package org.keycloak.tests.broker;

import java.util.List;
import java.util.Set;

import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
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.IdpReviewUserProfilePage;
Comment thread
sguilhen marked this conversation as resolved.
Resolved
import org.keycloak.testframework.ui.page.LoginPage;
import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;
import org.keycloak.testsuite.util.AccountHelper;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.openqa.selenium.TimeoutException;

/**
* Protocol-agnostic base for the broker tests. Holds the injected clients/pages that are identical across
* every broker test and the shared login/first-broker-login helpers. The realms differ per test (each
* concrete test injects its own provider/consumer realm with a protocol-specific config), so they are left
* as abstract accessors; everything else is inherited directly as protected fields.
*/
public abstract class AbstractBrokerTest {

protected static final String PROVIDER_REALM = "provider";
protected static final String CONSUMER_REALM = "consumer";
protected static final String USER_LOGIN = "testuser";
protected static final String USER_EMAIL = "user@localhost.com";
protected static final String USER_PASSWORD = "password";
protected static final String CONSUMER_BROKER_APP_CLIENT_ID = "broker-app";
protected static final String CONSUMER_BROKER_APP_SECRET = "broker-app-secret";

// Drive the login flows against the migrated broker-app client (as the legacy suite did) rather than the
// framework default test-app; the supplier creates it in the consumer realm from BrokerAppClientConfig.
@InjectOAuthClient(realmRef = "consumer", config = BrokerAppClientConfig.class)
protected OAuthClient oauth;

@InjectWebDriver
protected ManagedWebDriver webDriver;

@InjectPage
protected LoginPage loginPage;

@InjectPage
protected IdpReviewUserProfilePage updateProfilePage;

protected abstract ManagedRealm getProviderRealm();

protected abstract ManagedRealm getConsumerRealm();
Comment thread
sguilhen marked this conversation as resolved.
Resolved

protected abstract String getIdpAlias();

// The provider user is created without a first/last name so the consumer's first-broker-login
// review-profile page appears. The new-testsuite provider realm enables the VERIFY_PROFILE required
// action by default, which would otherwise stop the incomplete user at the provider; disable it so
// the incomplete profile reaches the consumer (the legacy suite did not enable VERIFY_PROFILE).
@BeforeEach
void relaxProviderProfileVerification() {
for (RequiredActionProviderRepresentation action : getProviderRealm().admin().flows().getRequiredActions()) {
if (UserModel.RequiredAction.VERIFY_PROFILE.name().equals(action.getAlias())) {
action.setEnabled(false);
getProviderRealm().admin().flows().updateRequiredAction(action.getAlias(), action);
}
}
}

protected String getUserLogin() {
return USER_LOGIN;
}

protected String getUserPassword() {
return USER_PASSWORD;
}

protected String getUserEmail() {
return USER_EMAIL;
}

protected void logInWithBroker() {
loginPage.clickSocial(getIdpAlias());
}

protected void logInWithIdp() {
logInWithBroker();
}

protected void logInAsUserInIDP() {
oauth.openLoginForm();
logInWithBroker();
logInAsUserInIDPForFirstTime();
}

protected void logInAsUserInIDPForFirstTime() {
loginPage.fillLogin(getUserLogin(), getUserPassword());
loginPage.submit();
}

// Drives the consumer's first-broker-login review-profile page, which must appear the first time a
// user logs in through a broker because the imported user is missing its first and last name. Fails
// the test if the page does not show, mirroring the legacy assertCurrent() on the update-profile page.
protected void updateAccountInformation() {
Assertions.assertTrue(profilePageAppeared(),
"The first-broker-login review-profile page was expected but did not appear");
updateProfilePage.update("Firstname", "Lastname");
}

// Tolerant variant for flows that intentionally skip the review-profile page (e.g. when
// update-profile-on-first-login is turned off). Fills the page only if it actually appears.
protected void updateAccountInformationIfPresent() {
if (profilePageAppeared()) {
updateProfilePage.update("Firstname", "Lastname");
}
}

private boolean profilePageAppeared() {
Set<String> profilePageIds = Set.of("login-login-update-profile", "login-idp-review-user-profile");
try {
webDriver.waiting().until(d -> {
String currentPageId = webDriver.page().getCurrentPageId();
if (currentPageId != null && profilePageIds.contains(currentPageId)) {
return true;
}
return null;
});
return true;
} catch (TimeoutException e) {
return false;
}
Comment thread
sguilhen marked this conversation as resolved.
Resolved
}
Comment thread
sguilhen marked this conversation as resolved.
Resolved

protected UserRepresentation createUser(String username, String email) {
ManagedRealm consumerRealm = getConsumerRealm();
UserRepresentation user = new UserRepresentation();
user.setUsername(username);
user.setEmail(email);
user.setEmailVerified(true);
user.setEnabled(true);
consumerRealm.admin().users().create(user).close();
return user;
}

// Mid-test helper used by singleLogout() to verify login state after logout.
protected void logoutFromConsumerRealm() {
AccountHelper.logout(getConsumerRealm().admin(), getUserLogin());
}

protected void assertNumFederatedIdentities(String username, int expected) {
ManagedRealm consumerRealm = getConsumerRealm();
List<UserRepresentation> users = consumerRealm.admin().users().search(username, true);
Assertions.assertEquals(1, users.size(), "Expected exactly one user with username " + username);
List<FederatedIdentityRepresentation> fedIdentities =
consumerRealm.admin().users().get(users.get(0).getId()).getFederatedIdentity();
Assertions.assertEquals(expected, fedIdentities.size());
}
}
Loading
Loading
0