Running mappers during account linking

Closes #11195

Co-authored-by: mposolda <mposolda@gmail.com>
Co-authored-by: toddkazakov
This commit is contained in:
Pedro Igor 2023-06-13 10:29:43 -03:00 committed by Marek Posolda
parent 41e253c054
commit aff6cc1cbd
2 changed files with 91 additions and 33 deletions

View file

@ -950,7 +950,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
} }
} else { } else {
this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, newModel); this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, newModel);
federatedUser = authenticatedUser;
} }
updateFederatedIdentity(context, federatedUser);
context.getIdp().authenticationFinished(authSession, context); context.getIdp().authenticationFinished(authSession, context);
AuthenticationManager.setClientScopesInSession(authSession); AuthenticationManager.setClientScopesInSession(authSession);

View file

@ -25,24 +25,27 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.broker.provider.HardcodedAttributeMapper;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.ActionURIUtils; import org.keycloak.testsuite.ActionURIUtils;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants; import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import org.keycloak.testsuite.broker.BrokerTestTools; import org.keycloak.testsuite.broker.BrokerTestTools;
@ -61,7 +64,9 @@ import java.net.URL;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT; import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS; import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
@ -82,6 +87,9 @@ public class ClientInitiatedAccountLinkTest extends AbstractServletsAdapterTest
public static final String CHILD_IDP = "child"; public static final String CHILD_IDP = "child";
public static final String PARENT_IDP = "parent-idp"; public static final String PARENT_IDP = "parent-idp";
public static final String PARENT_USERNAME = "parent"; public static final String PARENT_USERNAME = "parent";
private static final String HARDCODED_ATTRIBUTE_MAPPER_NAME = "my_hardcoded_mapper";
private static final String USER_ATTRIBUTE = "hardcoded_attribute";
private static final String USER_ATTRIBUTE_VALUE = "hardcoded_value";
@Page @Page
protected LoginUpdateProfilePage loginUpdateProfilePage; protected LoginUpdateProfilePage loginUpdateProfilePage;
@ -383,26 +391,7 @@ public class ClientInitiatedAccountLinkTest extends AbstractServletsAdapterTest
@Test @Test
public void testAccountLink() throws Exception { public void testAccountLink() throws Exception {
RealmResource realm = adminClient.realms().realm(CHILD_IDP); linkAccount();
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty());
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
.path("link");
String linkUrl = linkBuilder.clone()
.queryParam("realm", CHILD_IDP)
.queryParam("provider", PARENT_IDP).build().toString();
System.out.println("linkUrl: " + linkUrl);
navigateTo(linkUrl);
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
Assert.assertTrue(driver.getPageSource().contains(PARENT_IDP));
loginPage.login("child", "password");
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
loginPage.login(PARENT_USERNAME, "password");
System.out.println("After linking: " + driver.getCurrentUrl());
System.out.println(driver.getPageSource());
Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate()));
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, "client-linking", "password"); OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(CHILD_IDP, "child", "password", null, "client-linking", "password");
Assert.assertNotNull(response.getAccessToken()); Assert.assertNotNull(response.getAccessToken());
@ -411,19 +400,14 @@ public class ClientInitiatedAccountLinkTest extends AbstractServletsAdapterTest
String firstToken = getToken(response, httpClient); String firstToken = getToken(response, httpClient);
Assert.assertNotNull(firstToken); Assert.assertNotNull(firstToken);
navigateToAccountLinkPage();
navigateTo(linkUrl);
Assert.assertTrue(driver.getPageSource().contains("Account Linked")); Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
String nextToken = getToken(response, httpClient); String nextToken = getToken(response, httpClient);
Assert.assertNotNull(nextToken); Assert.assertNotNull(nextToken);
Assert.assertNotEquals(firstToken, nextToken); Assert.assertNotEquals(firstToken, nextToken);
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertFalse(links.isEmpty()); Assert.assertFalse(links.isEmpty());
realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP); realm.users().get(childUserId).removeFederatedIdentity(PARENT_IDP);
@ -431,8 +415,6 @@ public class ClientInitiatedAccountLinkTest extends AbstractServletsAdapterTest
Assert.assertTrue(links.isEmpty()); Assert.assertTrue(links.isEmpty());
logoutAll(); logoutAll();
} }
// TODO remove this once DYNAMIC_SCOPES feature is enabled by default // TODO remove this once DYNAMIC_SCOPES feature is enabled by default
@ -590,6 +572,78 @@ public class ClientInitiatedAccountLinkTest extends AbstractServletsAdapterTest
WaitUtils.waitForPageToLoad(); WaitUtils.waitForPageToLoad();
} }
@Test
public void testAccountLinkWithHardcodedMapper() throws Exception {
driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.DAYS);
addHardcodedAttributeMapper();
linkAccount();
assertHardcodedAttributeHasBeenAssigned();
removeHardcodedAttribute();
removeHardcodedAttributeMapper();
logoutAll();
}
private void addHardcodedAttributeMapper() {
IdentityProviderResource provider = adminClient.realms().realm(CHILD_IDP).identityProviders().get(PARENT_IDP);
IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation();
mapper.setIdentityProviderAlias(PARENT_IDP);
mapper.setName(HARDCODED_ATTRIBUTE_MAPPER_NAME);
mapper.setIdentityProviderMapper("hardcoded-attribute-idp-mapper");
mapper.setConfig(Map.of(
IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.FORCE.toString(),
HardcodedAttributeMapper.ATTRIBUTE, USER_ATTRIBUTE,
HardcodedAttributeMapper.ATTRIBUTE_VALUE, USER_ATTRIBUTE_VALUE)
);
mapper.setIdentityProviderAlias(PARENT_IDP);
provider.addMapper(mapper).close();
}
private void removeHardcodedAttributeMapper() {
IdentityProviderResource provider = adminClient.realms().realm(CHILD_IDP).identityProviders().get(PARENT_IDP);
Optional<IdentityProviderMapperRepresentation> mapper = provider.getMappers().stream()
.filter(m -> m.getName().equals(HARDCODED_ATTRIBUTE_MAPPER_NAME)).findFirst();
Assert.assertFalse(mapper.isEmpty());
provider.delete(mapper.get().getId());
}
private void assertHardcodedAttributeHasBeenAssigned() {
UserRepresentation user = adminClient.realm(CHILD_IDP).users().get(childUserId).toRepresentation();
Assert.assertEquals(USER_ATTRIBUTE_VALUE, user.firstAttribute(USER_ATTRIBUTE));
}
private void removeHardcodedAttribute() {
UserRepresentation user = adminClient.realm(CHILD_IDP).users().get(childUserId).toRepresentation();
user.getAttributes().remove(USER_ATTRIBUTE);
adminClient.realm(CHILD_IDP).users().get(user.getId()).update(user);
}
private void linkAccount() {
RealmResource realm = adminClient.realms().realm(CHILD_IDP);
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
Assert.assertTrue(links.isEmpty());
navigateToAccountLinkPage();
Assert.assertTrue(loginPage.isCurrent(CHILD_IDP));
Assert.assertTrue(driver.getPageSource().contains(PARENT_IDP));
loginPage.login("child", "password");
Assert.assertTrue(loginPage.isCurrent(PARENT_IDP));
loginPage.login(PARENT_USERNAME, "password");
System.out.println("After linking: " + driver.getCurrentUrl());
System.out.println(driver.getPageSource());
Assert.assertTrue(driver.getPageSource().contains("Account Linked"));
}
private void navigateToAccountLinkPage() {
UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString())
.path("link");
String linkUrl = linkBuilder.clone()
.queryParam("realm", CHILD_IDP)
.queryParam("provider", PARENT_IDP).build().toString();
System.out.println("linkUrl: " + linkUrl);
navigateTo(linkUrl);
}
} }