Part-of: Add support for not importing brokered user into Keycloak database

Closes: #11334
This commit is contained in:
Hynek Mlnarik 2023-10-11 13:55:28 +02:00 committed by Hynek Mlnařík
parent a668c2cb2b
commit d70735f64d
3 changed files with 924 additions and 29 deletions

View file

@ -37,6 +37,7 @@ import jakarta.ws.rs.core.Response;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -48,7 +49,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configurePostBrokerLoginWithOTP;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.disablePostBrokerLoginFlow;
@ -97,6 +100,8 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
*/
@Test
public void testAccountManagementLinkIdentity() {
assumeFalse("Account linking does not apply to transient sessions", isUsingTransientSessions());
createUser("consumer");
TestAppHelper testAppHelper = new TestAppHelper(oauth, loginPage, appPage);
@ -171,6 +176,8 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
*/
@Test
public void testRetrieveToken() throws Exception {
assumeFalse("There is no user to update once the user has logged in using transient sessions", isUsingTransientSessions());
updateExecutions(AbstractBrokerTest::enableRequirePassword);
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
IdentityProviderRepresentation idpRep = identityProviderResource.toRepresentation();
@ -220,13 +227,15 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
// KEYCLOAK-3267
@Test
public void loginWithExistingUserWithBruteForceEnabled() {
assumeFalse("Brute force protection does not apply to transient sessions", isUsingTransientSessions());
adminClient.realm(bc.consumerRealmName()).update(RealmBuilder.create().bruteForceProtected(true).failureFactor(2).build());
loginWithExistingUser();
Assert.assertTrue(AccountHelper.updatePassword(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin(), "password"));
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
oauth.clientId("broker-app");
@ -307,13 +316,15 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
*/
@Test
public void testDisabledUser() {
assumeFalse("There is no user to update after user logout when using transient sessions", isUsingTransientSessions());
loginUser();
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
UserRepresentation userRep = realm.users().search(bc.getUserLogin()).get(0);
UserRepresentation userRep = getConsumerUserRepresentation(bc.getUserLogin());
UserResource user = realm.users().get(userRep.getId());
userRep.setEnabled(false);
@ -366,8 +377,7 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
logInAsUserInIDPForFirstTime();
UserResource consumerUserResource = adminClient.realm(bc.consumerRealmName()).users().get(
adminClient.realm(bc.consumerRealmName()).users().search(bc.getUserLogin()).get(0).getId());
UserResource consumerUserResource = adminClient.realm(bc.consumerRealmName()).users().get(getConsumerUserRepresentation(bc.getUserLogin()).getId());
Set<String> currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
@ -375,12 +385,17 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
userResource.roles().realmLevel().add(Collections.singletonList(userRole));
logInAsUserInIDP();
if (isUsingTransientSessions()) {
// Transient sessions never update user, the rest of the test applies to persistent users only
return;
} else {
logInAsUserInIDP();
}
currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
@ -392,12 +407,14 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
assertThat(currentRoles, not(hasItems(ROLE_USER)));
}
logoutFromRealm(getConsumerRoot(), bc.consumerRealmName());
logoutFromConsumerRealm();
logoutFromRealm(getProviderRoot(), bc.providerRealmName());
}
@Test
public void differentMappersCanHaveDifferentSyncModes() {
assumeFalse("Sync mode does not apply to transient sessions as the mappers are applied only once and there is nothing to update", isUsingTransientSessions());
createRolesForRealm(bc.providerRealmName());
createRolesForRealm(bc.consumerRealmName());
@ -466,6 +483,8 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
*/
@Test
public void testPostBrokerLoginFlowWithOTP() {
assumeFalse("Password / OTP setup does not apply to transient sessions as there is no persistent user to log in twice", isUsingTransientSessions());
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(bc.getIDPAlias()));
@ -506,6 +525,8 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
// KEYCLOAK-12986
@Test
public void testPostBrokerLoginFlowWithOTP_bruteForceEnabled() {
assumeFalse("Brute force protection does not apply to transient sessions", isUsingTransientSessions());
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(bc.getIDPAlias()));
@ -594,7 +615,7 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
updateAccountInformationPage.assertCurrent();
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
logoutFromConsumerRealm();
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
@ -633,6 +654,11 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
loginPage.clickSocial(bc.getIDPAlias());
loginPage.login("test-user", "password");
if (isUsingTransientSessions()) {
assertThat(getConsumerUserRepresentation("test-user"), notNullValue());
// Updating password and the rest of the test is irrelevant for transient sessions
return;
}
Assert.assertTrue(AccountHelper.updatePassword(adminClient.realm(bc.consumerRealmName()), "test-user", "new-password"));
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), "test-user");

View file

@ -3,24 +3,37 @@ package org.keycloak.testsuite.broker;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory;
import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.pages.ConsentPage;
import org.keycloak.testsuite.util.AccountHelper;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.models.utils.DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
@ -62,35 +75,75 @@ public abstract class AbstractBrokerTest extends AbstractInitializedBaseBrokerTe
log.debug("Updating info on updateAccount page");
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), bc.getUserEmail(), "Firstname", "Lastname");
UserRepresentation userRep = AccountHelper.getUserRepresentation(
if (isUsingTransientSessions()) {
UsersResource consumerUsers = adminClient.realm(bc.consumerRealmName()).users();
List<UserRepresentation> userCount = consumerUsers.list();
assertThat("There must be at no users", userCount, empty());
} else {
UserRepresentation userRep = AccountHelper.getUserRepresentation(
adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
userRep.setFirstName("Firstname");
userRep.setLastName("Lastname");
userRep.setFirstName("Firstname");
userRep.setLastName("Lastname");
AccountHelper.updateUser(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin(), userRep);
AccountHelper.updateUser(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin(), userRep);
UsersResource consumerUsers = adminClient.realm(bc.consumerRealmName()).users();
UsersResource consumerUsers = adminClient.realm(bc.consumerRealmName()).users();
int userCount = consumerUsers.count();
Assert.assertTrue("There must be at least one user", userCount > 0);
List<UserRepresentation> users = consumerUsers.search("", 0, userCount);
boolean isUserFound = false;
for (UserRepresentation user : users) {
if (user.getUsername().equals(bc.getUserLogin()) && user.getEmail().equals(bc.getUserEmail())) {
isUserFound = true;
break;
}
int userCount = consumerUsers.count();
Assert.assertTrue("There must be at least one user", userCount > 0);
}
boolean isUserFound = getConsumerUserRepresentations()
.anyMatch(user -> user.getUsername().equals(bc.getUserLogin()) && user.getEmail().equals(bc.getUserEmail()));
Assert.assertTrue("There must be user " + bc.getUserLogin() + " in realm " + bc.consumerRealmName(),
isUserFound);
}
protected boolean isUsingTransientSessions() {
return "true".equals(identityProviderResource.toRepresentation().getConfig().getOrDefault(IdentityProviderModel.DO_NOT_STORE_USERS, "false"));
}
protected Stream<UserResource> getConsumerUserResources() {
return getConsumerUserRepresentations()
.map(UserRepresentation::getId)
.map(adminClient.realm(bc.consumerRealmName()).users()::get);
}
protected UserRepresentation getConsumerUserRepresentation(String userName) {
Objects.requireNonNull(userName);
Iterator<UserRepresentation> it = getConsumerUserRepresentations()
.peek(userRep -> log.debugf("UserRep: %s .. %s", userRep.getId(), userRep.getUsername()))
.filter(userRep -> userName.equals(userRep.getUsername()))
.iterator();
assertTrue("At least one user expected with username " + userName, it.hasNext());
UserRepresentation res = it.next();
assertFalse("At most one user expected with username " + userName, it.hasNext());
return res;
}
protected Stream<UserRepresentation> getConsumerUserRepresentations() {
String consumerClientBrokerAppId = adminClient.realm(bc.consumerRealmName()).clients().findByClientId("broker-app").get(0).getId();
List<UserSessionRepresentation> brokeredSessions = adminClient.realm(bc.consumerRealmName()).clients().get(consumerClientBrokerAppId).getUserSessions(0, 10);
final List<UserRepresentation> persistentUsers = adminClient.realm(bc.consumerRealmName()).users().list();
final Set<String> persistentUsersId = persistentUsers.stream().map(UserRepresentation::getId).collect(Collectors.toSet());
return Stream.concat(persistentUsers.stream(),
brokeredSessions.stream()
.map(userSession -> userSession.getUserId())
.filter(id -> ! persistentUsersId.contains(id))
.map(adminClient.realm(bc.consumerRealmName()).users()::get)
.map(UserResource::toRepresentation)
);
}
@Test
public void loginWithExistingUser() {
Integer userCountBefore = adminClient.realm(bc.consumerRealmName()).users().count();
testLogInAsUserInIDP();
Integer userCount = adminClient.realm(bc.consumerRealmName()).users().count();
@ -98,10 +151,15 @@ public abstract class AbstractBrokerTest extends AbstractInitializedBaseBrokerTe
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
if (isUsingTransientSessions()) {
// Assert that there has been no persistent user created
assertThat(userCount, is(userCountBefore));
} else {
logInWithBroker(bc);
assertTrue(driver.getCurrentUrl().contains(getConsumerRoot() + "/auth/realms/master/app/"));
assertEquals(userCount, adminClient.realm(bc.consumerRealmName()).users().count());
assertThat(driver.getCurrentUrl(), containsString(getConsumerRoot() + "/auth/realms/master/app/"));
assertEquals(userCount, adminClient.realm(bc.consumerRealmName()).users().count());
}
}
@ -114,7 +172,7 @@ public abstract class AbstractBrokerTest extends AbstractInitializedBaseBrokerTe
Assert.assertTrue("Should be logged in", driver.getTitle().endsWith("AUTH_RESPONSE"));
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
oauth.clientId("broker-app");
@ -124,6 +182,14 @@ public abstract class AbstractBrokerTest extends AbstractInitializedBaseBrokerTe
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/protocol/openid-connect/"));
}
protected void logoutFromConsumerRealm() {
if (isUsingTransientSessions()) {
getConsumerUserResources().forEach(UserResource::logout);
} else {
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
}
}
protected void createRolesForRealm(String realm) {
RoleRepresentation managerRole = new RoleRepresentation(ROLE_MANAGER,null, false);
RoleRepresentation friendlyManagerRole = new RoleRepresentation(ROLE_FRIENDLY_MANAGER,null, false);

View file

@ -0,0 +1,803 @@
package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.WaitUtils;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assume.assumeFalse;
import static org.keycloak.models.utils.TimeBasedOTP.DEFAULT_INTERVAL_SECONDS;
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configurePostBrokerLoginWithOTP;
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS;
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME;
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim;
import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot;
/**
* Final class as it's not intended to be overriden. Feel free to remove "final" if you really know what you are doing.
*/
public final class KcOidcBrokerTransientSessionsTest extends AbstractAdvancedBrokerTest {
private final static String USER_ATTRIBUTE_NAME = "user-attribute";
private final static String USER_ATTRIBUTE_VALUE = "attribute-value";
private final static String CLAIM_FILTER_REGEXP = ".*-value";
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return BROKER_CONFIG_INSTANCE;
}
@Before
public void setUpTotp() {
totp = new TimeBasedOTP();
}
@Override
protected Iterable<IdentityProviderMapperRepresentation> createIdentityProviderMappers(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation attrMapper1 = new IdentityProviderMapperRepresentation();
attrMapper1.setName("manager-role-mapper");
attrMapper1.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
attrMapper1.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_MANAGER)
.put("role", ROLE_MANAGER)
.build());
IdentityProviderMapperRepresentation attrMapper2 = new IdentityProviderMapperRepresentation();
attrMapper2.setName("user-role-mapper");
attrMapper2.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
attrMapper2.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_USER)
.put("role", ROLE_USER)
.build());
return Lists.newArrayList(attrMapper1, attrMapper2);
}
@Override
protected void createAdditionalMapperWithCustomSyncMode(IdentityProviderMapperSyncMode syncMode) {
IdentityProviderMapperRepresentation friendlyManagerMapper = new IdentityProviderMapperRepresentation();
friendlyManagerMapper.setName("friendly-manager-role-mapper");
friendlyManagerMapper.setIdentityProviderMapper(ExternalKeycloakRoleToRoleMapper.PROVIDER_ID);
friendlyManagerMapper.setConfig(ImmutableMap.<String,String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, syncMode.toString())
.put("external.role", ROLE_FRIENDLY_MANAGER)
.put("role", ROLE_FRIENDLY_MANAGER)
.build());
friendlyManagerMapper.setIdentityProviderAlias(bc.getIDPAlias());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
IdentityProviderResource idpResource = realm.identityProviders().get(bc.getIDPAlias());
idpResource.addMapper(friendlyManagerMapper).close();
}
@Test
public void mapperDoesNothingForLegacyMode() {
createRolesForRealm(bc.providerRealmName());
createRolesForRealm(bc.consumerRealmName());
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.LEGACY);
RoleRepresentation managerRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_MANAGER).toRepresentation();
RoleRepresentation userRole = adminClient.realm(bc.providerRealmName()).roles().get(ROLE_USER).toRepresentation();
UserResource userResource = adminClient.realm(bc.providerRealmName()).users().get(userId);
userResource.roles().realmLevel().add(Collections.singletonList(managerRole));
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInAsUserInIDPForFirstTime();
String consumerClientBrokerAppId = adminClient.realm(bc.consumerRealmName()).clients().findByClientId("broker-app").get(0).getId();
String transientUserId = adminClient.realm(bc.consumerRealmName()).clients().get(consumerClientBrokerAppId).getUserSessions(0, 10).get(0).getUserId();
assertThat(adminClient.realm(bc.consumerRealmName()).users().list(), empty());
UserResource consumerUserResource = adminClient.realm(bc.consumerRealmName()).users().get(transientUserId);
Set<String> currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
userResource.roles().realmLevel().add(Collections.singletonList(userRole));
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
if (! isUsingTransientSessions()) {
logInAsUserInIDP();
currentRoles = consumerUserResource.roles().realmLevel().listAll().stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toSet());
assertThat(currentRoles, hasItems(ROLE_MANAGER));
assertThat(currentRoles, not(hasItems(ROLE_USER)));
logoutFromConsumerRealm();
logoutFromRealm(getProviderRoot(), bc.providerRealmName());
}
}
@Test
public void loginFetchingUserFromUserEndpoint() {
loginFetchingUserFromUserEndpoint(false);
}
private void loginFetchingUserFromUserEndpoint(boolean loginIsDenied) {
RealmResource realm = realmsResouce().realm(bc.providerRealmName());
ClientsResource clients = realm.clients();
ClientRepresentation brokerApp = clients.findByClientId("brokerapp").get(0);
try {
IdentityProviderResource identityProviderResource = realmsResouce().realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias());
IdentityProviderRepresentation idp = identityProviderResource.toRepresentation();
idp.getConfig().put(OIDCIdentityProviderConfig.JWKS_URL, getProviderRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/certs");
identityProviderResource.update(idp);
brokerApp.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, Algorithm.RS256);
brokerApp.getAttributes().put("validateSignature", Boolean.TRUE.toString());
clients.get(brokerApp.getId()).update(brokerApp);
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, loginIsDenied? "We are sorry..." : "update account information", false);
if (loginIsDenied) {
return;
}
updateAccountInformationPage.assertCurrent();
Assert.assertTrue("We must be on correct realm right now",
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
log.debug("Updating info on updateAccount page");
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), bc.getUserEmail(), "Firstname", "Lastname");
List<UserRepresentation> consumerUsers = getConsumerUserRepresentations().collect(Collectors.toList());
int userCount = consumerUsers.size();
Assert.assertTrue("There must be at least one user", userCount > 0);
boolean isUserFound = false;
for (UserRepresentation user : consumerUsers) {
if (user.getUsername().equals(bc.getUserLogin()) && user.getEmail().equals(bc.getUserEmail())) {
isUserFound = true;
break;
}
}
Assert.assertTrue("There must be user " + bc.getUserLogin() + " in realm " + bc.consumerRealmName(),
isUserFound);
} finally {
brokerApp.getAttributes().put(OIDCConfigAttributes.USER_INFO_RESPONSE_SIGNATURE_ALG, null);
brokerApp.getAttributes().put("validateSignature", Boolean.FALSE.toString());
clients.get(brokerApp.getId()).update(brokerApp);
}
}
/**
* Refers to in old test suite: org.keycloak.testsuite.broker.OIDCBrokerUserPropertyTest
*/
@Test
public void loginFetchingUserFromUserEndpointWithClaimMapper() {
RealmResource realm = realmsResouce().realm(bc.providerRealmName());
ClientsResource clients = realm.clients();
ClientRepresentation brokerApp = clients.findByClientId("brokerapp").get(0);
IdentityProviderResource identityProviderResource = getIdentityProviderResource();
clients.get(brokerApp.getId()).getProtocolMappers().createMapper(createHardcodedClaim("hard-coded", "hard-coded", "hard-coded", "String", true, true, true)).close();
IdentityProviderMapperRepresentation hardCodedSessionNoteMapper = new IdentityProviderMapperRepresentation();
hardCodedSessionNoteMapper.setName("hard-coded");
hardCodedSessionNoteMapper.setIdentityProviderAlias(bc.getIDPAlias());
hardCodedSessionNoteMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID);
hardCodedSessionNoteMapper.setConfig(ImmutableMap.<String, String>builder()
.put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString())
.put(UserAttributeMapper.USER_ATTRIBUTE, "hard-coded")
.put(UserAttributeMapper.CLAIM, "hard-coded")
.build());
identityProviderResource.addMapper(hardCodedSessionNoteMapper).close();
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
loginFetchingUserFromUserEndpoint();
UserRepresentation user = getFederatedIdentity();
Assert.assertEquals(1, user.getAttributes().size());
Assert.assertEquals("hard-coded", user.getAttributes().get("hard-coded").get(0));
}
/**
* Refers to in old test suite: PostBrokerFlowTest#testBrokerReauthentication_samlBrokerWithOTPRequired
*/
@Test
public void testReauthenticationSamlBrokerWithOTPRequired() throws Exception {
assumeFalse("OTP does not apply to transient sessions (there is no second login)", isUsingTransientSessions());
KcSamlBrokerConfiguration samlBrokerConfig = KcSamlBrokerConfiguration.INSTANCE;
ClientRepresentation samlClient = samlBrokerConfig.createProviderClients().get(0);
IdentityProviderRepresentation samlBroker = samlBrokerConfig.setUpIdentityProvider();
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
try {
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
adminClient.realm(bc.providerRealmName()).clients().create(samlClient);
consumerRealm.identityProviders().create(samlBroker);
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(samlBrokerConfig.getIDPAlias()));
logInWithBroker(samlBrokerConfig);
totpPage.assertCurrent();
String totpSecret = totpPage.getTotpSecret();
totpPage.configure(totp.generateTOTP(totpSecret));
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
setOtpTimeOffset(DEFAULT_INTERVAL_SECONDS, totp);
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "account already exists", false);
idpConfirmLinkPage.assertCurrent();
idpConfirmLinkPage.clickLinkAccount();
loginPage.clickSocial(samlBrokerConfig.getIDPAlias());
waitForPage(driver, "sign in to", true);
log.debug("Logging in");
loginTotpPage.login(totp.generateTOTP(totpSecret));
assertNumFederatedIdentities(consumerRealm.users().search(samlBrokerConfig.getUserLogin()).get(0).getId(), 2);
} finally {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
removeUserByUsername(consumerRealm, "consumer");
}
}
/**
* Refers to in old test suite: PostBrokerFlowTest#testBrokerReauthentication_oidcBrokerWithOTPRequired
*/
@Test
public void testReauthenticationOIDCBrokerWithOTPRequired() throws Exception {
assumeFalse("Account linking does not apply to transient sessions", isUsingTransientSessions());
KcSamlBrokerConfiguration samlBrokerConfig = KcSamlBrokerConfiguration.INSTANCE;
ClientRepresentation samlClient = samlBrokerConfig.createProviderClients().get(0);
IdentityProviderRepresentation samlBroker = samlBrokerConfig.setUpIdentityProvider();
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
try {
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
adminClient.realm(bc.providerRealmName()).clients().create(samlClient);
consumerRealm.identityProviders().create(samlBroker);
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(samlBrokerConfig);
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(bc.getIDPAlias()));
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "account already exists", false);
idpConfirmLinkPage.assertCurrent();
idpConfirmLinkPage.clickLinkAccount();
loginPage.clickSocial(samlBrokerConfig.getIDPAlias());
totpPage.assertCurrent();
String totpSecret = totpPage.getTotpSecret();
totpPage.configure(totp.generateTOTP(totpSecret));
logoutFromConsumerRealm();
assertNumFederatedIdentities(consumerRealm.users().search(samlBrokerConfig.getUserLogin()).get(0).getId(), 2);
} finally {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
removeUserByUsername(consumerRealm, "consumer");
}
}
/**
* Refers to in old test suite: PostBrokerFlowTest#testBrokerReauthentication_bothBrokerWithOTPRequired
*/
@Test
public void testReauthenticationBothBrokersWithOTPRequired() throws Exception {
assumeFalse("Account linking does not apply to transient sessions", isUsingTransientSessions());
final RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
final RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
try (RealmAttributeUpdater rauConsumer = new RealmAttributeUpdater(consumerRealm).setOtpPolicyCodeReusable(true).update();
RealmAttributeUpdater rauProvider = new RealmAttributeUpdater(providerRealm).setOtpPolicyCodeReusable(true).update()) {
KcSamlBrokerConfiguration samlBrokerConfig = KcSamlBrokerConfiguration.INSTANCE;
ClientRepresentation samlClient = samlBrokerConfig.createProviderClients().get(0);
IdentityProviderRepresentation samlBroker = samlBrokerConfig.setUpIdentityProvider();
try {
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
providerRealm.clients().create(samlClient);
consumerRealm.identityProviders().create(samlBroker);
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(samlBrokerConfig.getIDPAlias()));
logInWithBroker(samlBrokerConfig);
totpPage.assertCurrent();
String totpSecret = totpPage.getTotpSecret();
totpPage.configure(totp.generateTOTP(totpSecret));
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(bc.getIDPAlias()));
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
waitForPage(driver, "account already exists", false);
idpConfirmLinkPage.assertCurrent();
idpConfirmLinkPage.clickLinkAccount();
loginPage.clickSocial(samlBrokerConfig.getIDPAlias());
loginTotpPage.assertCurrent();
loginTotpPage.login(totp.generateTOTP(totpSecret));
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);
loginTotpPage.assertCurrent();
loginTotpPage.login(totp.generateTOTP(totpSecret));
assertNumFederatedIdentities(consumerRealm.users().search(samlBrokerConfig.getUserLogin()).get(0).getId(), 2);
} finally {
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
removeUserByUsername(consumerRealm, "consumer");
}
}
}
@Test
public void testInvalidIssuedFor() {
loginUser();
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
log.debug("Clicking social " + bc.getIDPAlias());
loginPage.clickSocial(bc.getIDPAlias());
waitForPage(driver, "sign in to", true);
RealmResource realm = adminClient.realm(bc.providerRealmName());
ClientRepresentation rep = realm.clients().findByClientId(BrokerTestConstants.CLIENT_ID).get(0);
ClientResource clientResource = realm.clients().get(rep.getId());
ProtocolMapperRepresentation hardCodedAzp = createHardcodedClaim("hard", "azp", "invalid-azp", ProviderConfigProperty.STRING_TYPE, true, true, true);
clientResource.getProtocolMappers().createMapper(hardCodedAzp);
log.debug("Logging in");
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
errorPage.assertCurrent();
}
@Test
public void testInvalidAudience() {
loginUser();
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
log.debug("Clicking social " + bc.getIDPAlias());
loginPage.clickSocial(bc.getIDPAlias());
waitForPage(driver, "sign in to", true);
RealmResource realm = adminClient.realm(bc.providerRealmName());
ClientRepresentation rep = realm.clients().findByClientId(BrokerTestConstants.CLIENT_ID).get(0);
ClientResource clientResource = realm.clients().get(rep.getId());
ProtocolMapperRepresentation hardCodedAzp = createHardcodedClaim("hard", "aud", "invalid-aud", ProviderConfigProperty.LIST_TYPE, true, true, true);
clientResource.getProtocolMappers().createMapper(hardCodedAzp);
log.debug("Logging in");
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
errorPage.assertCurrent();
}
@Test
public void testIdPNotFound() {
final String notExistingIdP = "not-exists";
final String realmName = realmsResouce().realm(bc.providerRealmName()).toRepresentation().getRealm();
assertThat(realmName, notNullValue());
final String LINK = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + realmName + "/broker/" + notExistingIdP + "/endpoint";
driver.navigate().to(LINK);
errorPage.assertCurrent();
assertThat(errorPage.getError(), is("Page not found"));
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
SimpleHttp.Response simple = SimpleHttp.doGet(LINK, client).asResponse();
assertThat(simple, notNullValue());
assertThat(simple.getStatus(), is(Response.Status.NOT_FOUND.getStatusCode()));
OAuth2ErrorRepresentation error = simple.asJson(OAuth2ErrorRepresentation.class);
assertThat(error, notNullValue());
assertThat(error.getError(), is("Identity Provider [" + notExistingIdP + "] not found."));
} catch (IOException ex) {
Assert.fail("Cannot create HTTP client. Details: " + ex.getMessage());
}
}
@Test
public void testIdPForceSyncUserAttributes() {
checkUpdatedUserAttributesIdP(true, false);
}
@Test
public void testIdPForceSyncTrustEmailUserAttributes() {
checkUpdatedUserAttributesIdP(true, true);
}
@Test
public void testIdPNotForceSyncUserAttributes() {
checkUpdatedUserAttributesIdP(false, false);
}
@Test
public void testIdPNotForceSyncTrustEmailUserAttributes() {
checkUpdatedUserAttributesIdP(false, true);
}
@Test
public void loginWithClaimFilter() {
IdentityProviderResource identityProviderResource = getIdentityProviderResource();
IdentityProviderRepresentation identityProvider = identityProviderResource.toRepresentation();
updateIdPClaimFilter(identityProvider, identityProviderResource, true, USER_ATTRIBUTE_NAME, USER_ATTRIBUTE_VALUE);
WaitUtils.waitForPageToLoad();
loginFetchingUserFromUserEndpoint();
UserRepresentation user = getFederatedIdentity();
Assert.assertNotNull(user);
}
@Test
public void loginWithClaimRegexpFilter() {
IdentityProviderResource identityProviderResource = getIdentityProviderResource();
IdentityProviderRepresentation identityProvider = identityProviderResource.toRepresentation();
updateIdPClaimFilter(identityProvider, identityProviderResource, true, USER_ATTRIBUTE_NAME, CLAIM_FILTER_REGEXP);
WaitUtils.waitForPageToLoad();
loginFetchingUserFromUserEndpoint();
UserRepresentation user = getFederatedIdentity();
Assert.assertNotNull(user);
}
@Test
public void denyLoginWithClaimFilter() {
IdentityProviderResource identityProviderResource = getIdentityProviderResource();
IdentityProviderRepresentation identityProvider = identityProviderResource.toRepresentation();
updateIdPClaimFilter(identityProvider, identityProviderResource, true, "hardcoded-missing-claim", "hardcoded-missing-claim-value");
WaitUtils.waitForPageToLoad();
loginFetchingUserFromUserEndpoint(true);
Assert.assertEquals("The ID token issued by the identity provider does not match the configured essential claim. Please contact your administrator.",
loginPage.getInstruction());
List<UserRepresentation> users = realmsResouce().realm(bc.consumerRealmName()).users().search(bc.getUserLogin());
assertThat(users, Matchers.empty());
}
protected void postInitializeUser(UserRepresentation user) {
user.setAttributes(ImmutableMap.<String, List<String>> builder()
.put(USER_ATTRIBUTE_NAME, ImmutableList.<String> builder().add(USER_ATTRIBUTE_VALUE).build())
.build());
}
private void updateIdPClaimFilter(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource, boolean filteredByClaim, String claimFilterName, String claimFilterValue) {
assertThat(idProvider, Matchers.notNullValue());
assertThat(idProviderResource, Matchers.notNullValue());
assertThat(claimFilterName, Matchers.notNullValue());
assertThat(claimFilterValue, Matchers.notNullValue());
if (idProvider.getConfig().getOrDefault(IdentityProviderModel.FILTERED_BY_CLAIMS, "false").equals(Boolean.toString(filteredByClaim)) &&
idProvider.getConfig().getOrDefault(IdentityProviderModel.CLAIM_FILTER_NAME, "").equals(claimFilterName) &&
idProvider.getConfig().getOrDefault(IdentityProviderModel.CLAIM_FILTER_VALUE, "").equals(claimFilterValue)
) {
return;
}
idProvider.getConfig().put(IdentityProviderModel.FILTERED_BY_CLAIMS, Boolean.toString(filteredByClaim));
idProvider.getConfig().put(IdentityProviderModel.CLAIM_FILTER_NAME, claimFilterName);
idProvider.getConfig().put(IdentityProviderModel.CLAIM_FILTER_VALUE, claimFilterValue);
idProviderResource.update(idProvider);
idProvider = idProviderResource.toRepresentation();
assertThat("Cannot get Identity Provider", idProvider, Matchers.notNullValue());
assertThat("Filtered by claim didn't change", idProvider.getConfig().get(IdentityProviderModel.FILTERED_BY_CLAIMS), Matchers.equalTo(Boolean.toString(filteredByClaim)));
assertThat("Claim name didn't change", idProvider.getConfig().get(IdentityProviderModel.CLAIM_FILTER_NAME), Matchers.equalTo(claimFilterName));
assertThat("Claim value didn't change", idProvider.getConfig().get(IdentityProviderModel.CLAIM_FILTER_VALUE), Matchers.equalTo(claimFilterValue));
}
private void checkUpdatedUserAttributesIdP(boolean isForceSync, boolean isTrustEmail) {
assumeFalse("Updating user attributes is not supported when using transient sessions", isUsingTransientSessions());
final String IDP_NAME = getBrokerConfiguration().getIDPAlias();
final String USERNAME = "demo-user";
final String PASSWORD = "demo-pwd";
final String NEW_USERNAME = "demo-user-new";
final String FIRST_NAME = "John";
final String LAST_NAME = "Doe";
final String EMAIL = "mail@example.com";
final String NEW_FIRST_NAME = "Jack";
final String NEW_LAST_NAME = "Doee";
final String NEW_EMAIL = "mail123@example.com";
RealmResource providerRealmResource = realmsResouce().realm(bc.providerRealmName());
allowUserEdit(providerRealmResource);
UsersResource providerUsersResource = providerRealmResource.users();
String providerUserID = createUser(bc.providerRealmName(), USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL,
user -> user.setEmailVerified(true));
UserResource providerUserResource = providerUsersResource.get(providerUserID);
try {
IdentityProviderResource consumerIdentityResource = getIdentityProviderResource();
IdentityProviderRepresentation idProvider = consumerIdentityResource.toRepresentation();
updateIdPSyncMode(idProvider, consumerIdentityResource,
isForceSync ? IdentityProviderSyncMode.FORCE : IdentityProviderSyncMode.IMPORT, isTrustEmail);
// login to create the user in the consumer realm
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
WaitUtils.waitForPageToLoad();
assertThat(driver.getTitle(), Matchers.containsString("Sign in to " + bc.consumerRealmName()));
logInWithIdp(IDP_NAME, USERNAME, PASSWORD);
UserRepresentation userRepresentation = AccountHelper.getUserRepresentation(adminClient.realm(bc.providerRealmName()), USERNAME);
assertThat(userRepresentation.getUsername(), Matchers.equalTo(USERNAME));
assertThat(userRepresentation.getEmail(), Matchers.equalTo(EMAIL));
assertThat(userRepresentation.getFirstName(), Matchers.equalTo(FIRST_NAME));
assertThat(userRepresentation.getLastName(), Matchers.equalTo(LAST_NAME));
RealmResource consumerRealmResource = realmsResouce().realm(bc.consumerRealmName());
UserRepresentation consumerUser = getConsumerUserRepresentation(USERNAME);
assertThat(consumerUser, Matchers.notNullValue());
String consumerUserID = consumerUser.getId();
UserResource consumerUserResource = consumerRealmResource.users().get(consumerUserID);
checkFederatedIdentityLink(consumerUserResource, providerUserID, USERNAME);
assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.equalTo(isTrustEmail));
logoutFromConsumerRealm();
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), USERNAME);
// set email verified to true on the consumer resource
consumerUser = consumerUserResource.toRepresentation();
consumerUser.setEmailVerified(true);
consumerUserResource.update(consumerUser);
consumerUserResource = consumerRealmResource.users().get(consumerUserID);
assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.is(true));
// modify provider user with the new values
UserRepresentation providerUser = providerUserResource.toRepresentation();
providerUser.setUsername(NEW_USERNAME);
providerUser.setFirstName(NEW_FIRST_NAME);
providerUser.setLastName(NEW_LAST_NAME);
providerUser.setEmail(NEW_EMAIL);
providerUser.setEmailVerified(true);
providerUserResource.update(providerUser);
// login again to force sync if force mode
oauth.clientId("broker-app");
loginPage.open(bc.consumerRealmName());
WaitUtils.waitForPageToLoad();
assertThat(driver.getTitle(), Matchers.containsString("Sign in to " + bc.consumerRealmName()));
logInWithIdp(IDP_NAME, NEW_USERNAME, PASSWORD);
userRepresentation = getConsumerUserRepresentation(USERNAME);
// consumer username stays the same, even when sync mode is force
assertThat(userRepresentation.getUsername(), Matchers.equalTo(USERNAME));
// other consumer attributes are updated, when sync mode is force
assertThat(userRepresentation.getEmail(), Matchers.equalTo(isForceSync ? NEW_EMAIL : EMAIL));
assertThat(userRepresentation.getFirstName(), Matchers.equalTo(isForceSync ? NEW_FIRST_NAME : FIRST_NAME));
assertThat(userRepresentation.getLastName(), Matchers.equalTo(isForceSync ? NEW_LAST_NAME : LAST_NAME));
consumerUserResource = consumerRealmResource.users().get(consumerUserID);
checkFederatedIdentityLink(consumerUserResource, providerUserID, isForceSync ? NEW_USERNAME : USERNAME);
// the email verified should be reverted to false if force-sync and not trust-email
assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.equalTo(!isForceSync || isTrustEmail));
} finally {
providerUsersResource.delete(providerUserID);
}
}
private void allowUserEdit(RealmResource realmResource) {
RealmRepresentation realm = realmResource.toRepresentation();
realm.setEditUsernameAllowed(true);
realmResource.update(realm);
}
private void checkFederatedIdentityLink(UserResource userResource, String userID, String username) {
if (isUsingTransientSessions()) {
return;
}
List<FederatedIdentityRepresentation> federatedIdentities = userResource.getFederatedIdentity();
assertThat(federatedIdentities, Matchers.hasSize(1));
FederatedIdentityRepresentation federatedIdentity = federatedIdentities.get(0);
assertThat(federatedIdentity.getIdentityProvider(), Matchers.equalTo(IDP_OIDC_ALIAS));
assertThat(federatedIdentity.getUserId(), Matchers.equalTo(userID));
assertThat(federatedIdentity.getUserName(), Matchers.equalTo(username));
}
private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource,
IdentityProviderSyncMode syncMode, boolean trustEmail) {
assertThat(idProvider, Matchers.notNullValue());
assertThat(idProviderResource, Matchers.notNullValue());
assertThat(syncMode, Matchers.notNullValue());
if (idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE).equals(syncMode.name())
&& idProvider.isTrustEmail() == trustEmail) {
return;
}
idProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, syncMode.name());
idProvider.setTrustEmail(trustEmail);
idProviderResource.update(idProvider);
idProvider = idProviderResource.toRepresentation();
assertThat("Cannot get Identity Provider", idProvider, Matchers.notNullValue());
assertThat("Sync mode didn't change", idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE), Matchers.equalTo(syncMode.name()));
assertThat("TrustEmail didn't change", idProvider.isTrustEmail(), Matchers.equalTo(trustEmail));
}
private UserRepresentation getFederatedIdentity() {
return getConsumerUserRepresentation(bc.getUserLogin());
}
private IdentityProviderResource getIdentityProviderResource() {
return realmsResouce().realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias());
}
private static final CustomKcOidcBrokerConfiguration BROKER_CONFIG_INSTANCE = new CustomKcOidcBrokerConfiguration();
static class CustomKcOidcBrokerConfiguration extends KcOidcBrokerConfiguration {
@Override
public List<ClientRepresentation> createProviderClients() {
List<ClientRepresentation> clients = super.createProviderClients();
ClientRepresentation client = clients.get(0);
ProtocolMapperRepresentation userAttrMapper = new ProtocolMapperRepresentation();
userAttrMapper.setName(USER_ATTRIBUTE_NAME);
userAttrMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
userAttrMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
Map<String, String> userAttrMapperConfig = userAttrMapper.getConfig();
userAttrMapperConfig.put(ProtocolMapperUtils.USER_ATTRIBUTE, USER_ATTRIBUTE_NAME);
userAttrMapperConfig.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, USER_ATTRIBUTE_NAME);
userAttrMapperConfig.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE);
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "false");
userAttrMapperConfig.put(ProtocolMapperUtils.AGGREGATE_ATTRS, "false");
List<ProtocolMapperRepresentation> mappers = new ArrayList<>(client.getProtocolMappers());
mappers.add(userAttrMapper);
client.setProtocolMappers(mappers);
return clients;
}
@Override
protected void applyDefaultConfiguration(Map<String, String> config, IdentityProviderSyncMode syncMode) {
super.applyDefaultConfiguration(config, syncMode);
config.put(IdentityProviderModel.DO_NOT_STORE_USERS, "true");
}
}
}