Skip to content

Commit cd154cf

Browse files
committed
User Profile: If required roles ('user') and reqired scopes are set, the required scopes have no effect
closes keycloak#25475 Signed-off-by: mposolda <[email protected]>
1 parent 59536be commit cd154cf

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,17 @@ protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata de
296296
if (rc != null) {
297297
if (rc.isAlways() || UPConfigUtils.isRoleForContext(context, rc.getRoles())) {
298298
required = AttributeMetadata.ALWAYS_TRUE;
299+
300+
// If scopes are configured, we will use scope-based selector and require the attribute just if scope is
301+
// in current authenticationSession (either default scope or by 'scope' parameter)
302+
if (rc.getScopes() != null && !rc.getScopes().isEmpty()) {
303+
if (UPConfigUtils.canBeAuthFlowContext(context)) {
304+
required = (c) -> requestedScopePredicate(c, rc.getScopes());
305+
} else {
306+
// Scopes not available for admin and account contexts
307+
required = AttributeMetadata.ALWAYS_FALSE;
308+
}
309+
}
299310
} else if (UPConfigUtils.canBeAuthFlowContext(context) && rc.getScopes() != null && !rc.getScopes().isEmpty()) {
300311
// for contexts executed from auth flow and with configured scopes requirement
301312
// we have to create required validation with scopes based selector

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/AbstractUserProfileTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Set;
3030

3131
import org.junit.Before;
32+
import org.keycloak.OAuth2Constants;
3233
import org.keycloak.common.Profile;
3334
import org.keycloak.component.ComponentModel;
3435
import org.keycloak.models.ClientModel;
@@ -214,7 +215,11 @@ public void clearAuthNotes() {
214215

215216
@Override
216217
public String getClientNote(String name) {
217-
return null;
218+
if (OAuth2Constants.SCOPE.equals(name) && scopes != null && !scopes.isEmpty()) {
219+
return String.join(" ", scopes);
220+
} else {
221+
return null;
222+
}
218223
}
219224

220225
@Override

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
6262
import org.keycloak.representations.userprofile.config.UPGroup;
6363
import org.keycloak.services.messages.Messages;
64+
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
6465
import org.keycloak.testsuite.runonserver.RunOnServer;
6566
import org.keycloak.testsuite.util.LDAPRule;
6667
import org.keycloak.userprofile.AttributeGroupMetadata;
@@ -98,8 +99,10 @@ public void configureTestRealm(RealmRepresentation testRealm) {
9899
testRealm.setClientScopes(new ArrayList<>());
99100
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("customer").protocol("openid-connect").build());
100101
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("client-a").protocol("openid-connect").build());
102+
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("some-optional-scope").protocol("openid-connect").build());
101103
ClientRepresentation client = KeycloakModelUtils.createClient(testRealm, "client-a");
102104
client.setDefaultClientScopes(Collections.singletonList("customer"));
105+
client.setOptionalClientScopes(Collections.singletonList("some-optional-scope"));
103106
KeycloakModelUtils.createClient(testRealm, "client-b");
104107
}
105108

@@ -1429,6 +1432,74 @@ private static void testRequiredByClientScope(KeycloakSession session) {
14291432

14301433
}
14311434

1435+
@Test
1436+
@ModelTest
1437+
public void testRequiredByOptionalClientScope(KeycloakSession session) {
1438+
RealmModel realm = session.realms().getRealmByName("test");
1439+
session.getContext().setRealm(realm);
1440+
1441+
UserProfileProvider provider = getUserProfileProvider(session);
1442+
UPConfig config = parseDefaultConfig();
1443+
config.addOrReplaceAttribute(new UPAttribute(ATT_ADDRESS, new UPAttributePermissions(Set.of(), Set.of(ROLE_ADMIN, ROLE_USER)), new UPAttributeRequired(Set.of(ROLE_ADMIN, ROLE_USER), Set.of("some-optional-scope"))));
1444+
provider.setConfiguration(config);
1445+
1446+
Map<String, Object> attributes = new HashMap<>();
1447+
1448+
attributes.put(UserModel.USERNAME, "user");
1449+
attributes.put(UserModel.FIRST_NAME, "John");
1450+
attributes.put(UserModel.LAST_NAME, "Doe");
1451+
attributes.put(UserModel.EMAIL, "[email protected]");
1452+
1453+
// client with default scopes. No address scope included
1454+
configureAuthenticationSession(session, "client-a", null);
1455+
1456+
// No fail on admin and account console as they do not have scopes
1457+
UserProfile profile = provider.create(UserProfileContext.USER_API, attributes);
1458+
profile.validate();
1459+
profile = provider.create(UserProfileContext.ACCOUNT, attributes);
1460+
profile.validate();
1461+
1462+
// no fail on auth flow scopes when scope is not required
1463+
profile = provider.create(UserProfileContext.REGISTRATION, attributes);
1464+
profile.validate();
1465+
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
1466+
profile.validate();
1467+
profile = provider.create(UserProfileContext.IDP_REVIEW, attributes);
1468+
profile.validate();
1469+
1470+
// client with default scopes for which is attribute NOT configured as required
1471+
configureAuthenticationSession(session, "client-a", Set.of("some-optional-scope"));
1472+
1473+
// No fail on admin and account console as they do not have scopes
1474+
profile = provider.create(UserProfileContext.USER_API, attributes);
1475+
profile.validate();
1476+
profile = provider.create(UserProfileContext.ACCOUNT, attributes);
1477+
profile.validate();
1478+
1479+
// fail on auth flow scopes when scope is required
1480+
try {
1481+
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
1482+
profile.validate();
1483+
fail("Should fail validation");
1484+
} catch (ValidationException ve) {
1485+
assertTrue(ve.isAttributeOnError(ATT_ADDRESS));
1486+
}
1487+
try {
1488+
profile = provider.create(UserProfileContext.REGISTRATION, attributes);
1489+
profile.validate();
1490+
fail("Should fail validation");
1491+
} catch (ValidationException ve) {
1492+
assertTrue(ve.isAttributeOnError(ATT_ADDRESS));
1493+
}
1494+
try {
1495+
profile = provider.create(UserProfileContext.IDP_REVIEW, attributes);
1496+
profile.validate();
1497+
fail("Should fail validation");
1498+
} catch (ValidationException ve) {
1499+
assertTrue(ve.isAttributeOnError(ATT_ADDRESS));
1500+
}
1501+
}
1502+
14321503
@Test
14331504
public void testConfigurationInvalidScope() {
14341505
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testConfigurationInvalidScope);

0 commit comments

Comments
 (0)