Skip to content
Open
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
Expand Up @@ -17,8 +17,10 @@

package org.keycloak.tests.organization.admin;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
Expand All @@ -31,27 +33,38 @@
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.OrganizationModel.IdentityProviderRedirectMode;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.realm.ClientBuilder;
import org.keycloak.testframework.realm.CredentialBuilder;
import org.keycloak.testframework.realm.IdentityProviderBuilder;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testframework.realm.RealmBuilder;
import org.keycloak.testframework.realm.RealmConfig;
import org.keycloak.testframework.realm.UserBuilder;
import org.keycloak.testframework.realm.UserConfig;
import org.keycloak.testframework.ui.page.LoginPage;
import org.keycloak.testframework.ui.page.LoginUpdateProfilePage;
import org.keycloak.testframework.ui.page.LoginUsernamePage;
import org.keycloak.testframework.util.ApiUtil;

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;

/**
* Base class for organization tests in the new test framework.
Expand Down Expand Up @@ -149,7 +162,7 @@ protected MemberRepresentation addMember(OrganizationResource organization) {
}

protected MemberRepresentation addMember(OrganizationResource organization, String email) {
return addMember(organization, null, email, null, null, true);
return addMember(organization, null, email, "FirstName", "LastName", true);
}

protected MemberRepresentation addMember(OrganizationResource organization, String email, String firstName, String lastName) {
Expand All @@ -163,6 +176,7 @@ protected MemberRepresentation addMember(OrganizationResource organization, Stri
expected.setEmail(email);
expected.setUsername(username == null ? expected.getEmail() : username);
expected.setEnabled(true);
expected.setEmailVerified(true);
expected.setFirstName(firstName);
expected.setLastName(lastName);
try (Response response = realm.admin().users().create(expected)) {
Expand Down Expand Up @@ -235,6 +249,92 @@ protected void setMapperConfig(String key, String value) {
orgScopeResource.getProtocolMappers().update(orgMapper.getId(), orgMapper);
}

/**
* Builds an IdP representation pointing to a real provider realm's OIDC endpoints.
*/
public static IdentityProviderRepresentation createRealOrgBroker(String alias, ManagedRealm providerRealm) {
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
idp.setAlias(alias);
idp.setProviderId("keycloak-oidc");
idp.setEnabled(true);
idp.setTrustEmail(true);
String providerBaseUrl = providerRealm.getBaseUrl();
idp.setConfig(new HashMap<>(Map.of(
"clientId", CLIENT_ID,
"clientSecret", CLIENT_SECRET,
"authorizationUrl", providerBaseUrl + "/protocol/openid-connect/auth",
"tokenUrl", providerBaseUrl + "/protocol/openid-connect/token",
"userInfoUrl", providerBaseUrl + "/protocol/openid-connect/userinfo",
"defaultScope", "email profile",
"syncMode", "IMPORT"
)));
return idp;
}
Comment on lines +255 to +272

/**
* Performs identity-first login through a broker: enters email on the consumer realm login page,
* follows the redirect to the provider realm, fills credentials, and optionally handles first-time
* login profile update. Registers cleanup for the federated user created in the consumer realm.
*/
protected void loginViaBroker(String email, String username, String password,
OAuthClient oauth, LoginUsernamePage loginUsernamePage, LoginPage loginPage,
ManagedRealm providerRealm) {
loginViaBroker(email, username, password, null, oauth, loginUsernamePage, loginPage, null, providerRealm);
}

protected void loginViaBroker(String email, String username, String password,
String updateEmail, OAuthClient oauth, LoginUsernamePage loginUsernamePage,
LoginPage loginPage, LoginUpdateProfilePage loginUpdateProfilePage,
ManagedRealm providerRealm) {
oauth.openLoginForm();
loginUsernamePage.fillLoginWithUsernameOnly(email);
loginUsernamePage.submit();

assertTrue(Objects.requireNonNull(oauth.getDriver().getCurrentUrl()).contains("/realms/" + providerRealm.getName() + "/"),
"Should be on provider realm login page");

loginPage.fillLogin(username, password);
loginPage.submit();

if (updateEmail != null) {
String currentUrl = Objects.requireNonNull(oauth.getDriver().getCurrentUrl());
if (currentUrl.contains("/login-actions/") || currentUrl.contains("/broker/")) {
loginUpdateProfilePage.update("Firstname", "Lastname", updateEmail);
}
}

String searchEmail = updateEmail != null ? updateEmail : email;
List<UserRepresentation> users = realm.admin().users().searchByEmail(searchEmail, true);
assertEquals(1, users.size(), "Federated user should be created in consumer realm");

String userId = users.get(0).getId();
realm.cleanup().add(r -> {
try {
r.users().get(userId).remove();
} catch (NotFoundException ignored) {}
});
}

/**
* Performs broker registration and verifies the user becomes an organization member.
* Equivalent to the old AbstractOrganizationTest.assertBrokerRegistration().
*/
protected void assertBrokerRegistration(OrganizationResource organization,
String username, String email,
OAuthClient oauth, LoginUsernamePage loginUsernamePage, LoginPage loginPage,
LoginUpdateProfilePage loginUpdateProfilePage, ManagedRealm providerRealm) {
loginViaBroker(email, username, "password", email,
oauth, loginUsernamePage, loginPage, loginUpdateProfilePage, providerRealm);

assertIsMember(email, organization);
}

protected void assertIsMember(String userEmail, OrganizationResource organization) {
UserRepresentation account = getUserRepresentation(userEmail);
UserRepresentation member = organization.members().member(account.getId()).toRepresentation();
assertEquals(account.getId(), member.getId());
}

/**
* Creates an OIDC identity provider representation for the given organization name.
*/
Expand Down Expand Up @@ -288,6 +388,46 @@ public RealmBuilder configure(RealmBuilder realm) {
}
}

protected void createTestClients() {
if (realm.admin().clients().findByClientId("direct-grant").isEmpty()) {
realm.admin().clients().create(
ClientBuilder.create("direct-grant")
.secret("password")
.directAccessGrantsEnabled()
.redirectUris("*")
.build()
).close();
}
if (realm.admin().clients().findByClientId("broker-app").isEmpty()) {
ProtocolMapperRepresentation audienceMapper = new ProtocolMapperRepresentation();
audienceMapper.setName("audience-broker-app");
audienceMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
audienceMapper.setProtocolMapper(AudienceProtocolMapper.PROVIDER_ID);
audienceMapper.setConfig(Map.of(
AudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, "broker-app",
OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"
));
realm.admin().clients().create(
ClientBuilder.create("broker-app")
.secret("broker-app-secret")
.directAccessGrantsEnabled()
.redirectUris("*")
.protocolMappers(audienceMapper)
.build()
).close();
}
try {
RequiredActionProviderRepresentation verifyProfile = realm.admin().flows()
.getRequiredAction("VERIFY_PROFILE");
if (verifyProfile.isEnabled()) {
verifyProfile.setEnabled(false);
verifyProfile.setDefaultAction(false);
realm.admin().flows().updateRequiredAction("VERIFY_PROFILE", verifyProfile);
}
} catch (NotFoundException ignored) {
}
}

public static final String IDP_ALIAS = "org-identity-provider";
public static final String CLIENT_ID = "broker-app";
public static final String CLIENT_SECRET = "broker-secret";
Comment on lines 432 to 433
Expand All @@ -304,4 +444,15 @@ public RealmBuilder configure(RealmBuilder realm) {
);
}
}

public static class AliceUserConf implements UserConfig {
@Override
public UserBuilder configure(UserBuilder builder) {
return builder.username("alice")
.password("password")
.email("alice@neworg.org")
.emailVerified(true)
.name("Alice", "Org");
}
}
}
Loading