Tests
Part-of: Add support for not importing brokered user into Keycloak database Closes: #11334
This commit is contained in:
parent
a668c2cb2b
commit
d70735f64d
3 changed files with 924 additions and 29 deletions
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue