Check manage-account-links role for client initiated account linking

Closes #41914

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
Giuseppe Graziano
2025-08-26 15:40:51 +02:00
committed by Marek Posolda
parent 4262480bc2
commit 6dc9d0d439
2 changed files with 83 additions and 1 deletions

View File

@@ -1001,7 +1001,9 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return redirectToErrorWhenLinkingFailed(authSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, idpDisplayName);
}
if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.MANAGE_ACCOUNT))) {
RoleModel manageAccountRole = this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.MANAGE_ACCOUNT);
RoleModel manageAccountLinkRole = this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.MANAGE_ACCOUNT_LINKS);
if (!authenticatedUser.hasRole(manageAccountRole) && !authenticatedUser.hasRole(manageAccountLinkRole)) {
return redirectToErrorPage(authSession, Response.Status.FORBIDDEN, Messages.INSUFFICIENT_PERMISSION);
}

View File

@@ -38,6 +38,7 @@ import org.keycloak.common.util.UriUtils;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
@@ -45,6 +46,7 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.messages.Messages;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.IdpLinkActionPage;
import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.oauth.OAuthClient;
@@ -319,6 +321,84 @@ public class KcOidcBrokerIdpLinkActionTest extends AbstractInitializedBaseBroker
}
@Test
public void testAccountLinkingWithDirectRoleManageAccount() throws Exception {
// Remove "manage-account" role from user
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
String user1Id = consumerRealm.users().search("user1").iterator().next().getId();
RoleRepresentation defaultRoles = consumerRealm.roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + bc.consumerRealmName()).toRepresentation();
consumerRealm.users().get(user1Id).roles().realmLevel().remove(Collections.singletonList(defaultRoles));
ClientRepresentation accountClient = ApiUtil.findClientResourceByClientId(consumerRealm, Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).toRepresentation();
RoleRepresentation manageAccount = consumerRealm.clients().get(accountClient.getId()).roles().get(AccountRoles.MANAGE_ACCOUNT).toRepresentation();
consumerRealm.users().get(user1Id).roles().clientLevel(accountClient.getId()).add(Collections.singletonList(manageAccount));
loginToConsumer();
// Redirect to link account on behalf of "broker-app" and login to the IDP
String kcAction = getKcActionParamForLinkIdp(bc.getIDPAlias());
oauth.loginForm().kcAction(kcAction).open();
confirmIdpLinking();
// Login to provider
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
events.clear();
grantPage.assertCurrent();
grantPage.accept();
appPage.assertCurrent();
assertKcActionParams(IdpLinkAction.PROVIDER_ID, RequiredActionContext.KcActionStatus.SUCCESS.name().toLowerCase());
// Check that user is linked to the IDP
assertUserLinkedToIDP(true);
assertEvents((providerRealmId, providerUserId, consumerRealmId, consumerUserId, consumerUsername) -> {
assertProviderEventsSuccess(providerRealmId, providerUserId);
assertConsumerSuccessLinkEvents(consumerRealmId, consumerUserId, consumerUsername);
});
}
@Test
public void testAccountLinkingWithDirectRoleManageAccountLinks() throws Exception {
// Remove "manage-account-link" role from user
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
String user1Id = consumerRealm.users().search("user1").iterator().next().getId();
RoleRepresentation defaultRoles = consumerRealm.roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + bc.consumerRealmName()).toRepresentation();
consumerRealm.users().get(user1Id).roles().realmLevel().remove(Collections.singletonList(defaultRoles));
ClientRepresentation accountClient = ApiUtil.findClientResourceByClientId(consumerRealm, Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).toRepresentation();
RoleRepresentation manageAccountLinks = consumerRealm.clients().get(accountClient.getId()).roles().get(AccountRoles.MANAGE_ACCOUNT_LINKS).toRepresentation();
consumerRealm.users().get(user1Id).roles().clientLevel(accountClient.getId()).add(Collections.singletonList(manageAccountLinks));
loginToConsumer();
// Redirect to link account on behalf of "broker-app" and login to the IDP
String kcAction = getKcActionParamForLinkIdp(bc.getIDPAlias());
oauth.loginForm().kcAction(kcAction).open();
confirmIdpLinking();
// Login to provider
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
events.clear();
grantPage.assertCurrent();
grantPage.accept();
appPage.assertCurrent();
assertKcActionParams(IdpLinkAction.PROVIDER_ID, RequiredActionContext.KcActionStatus.SUCCESS.name().toLowerCase());
// Check that user is linked to the IDP
assertUserLinkedToIDP(true);
assertEvents((providerRealmId, providerUserId, consumerRealmId, consumerUserId, consumerUsername) -> {
assertProviderEventsSuccess(providerRealmId, providerUserId);
assertConsumerSuccessLinkEvents(consumerRealmId, consumerUserId, consumerUsername);
});
}
@Test
public void testConsumerReauthentication() throws Exception {
loginToConsumer();