mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
User Profile: If required roles ('user') and reqired scopes are set, the required scopes have no effect
closes #25475 Signed-off-by: mposolda <mposolda@gmail.com> (cherry picked from commit cd154cf3189a8ccda78da1d8f36d64b1ff2fff1b)
This commit is contained in:
parent
79f3ca5590
commit
753485c1c5
@ -327,6 +327,17 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
|
||||
if (rc != null) {
|
||||
if (rc.isAlways() || UPConfigUtils.isRoleForContext(context, rc.getRoles())) {
|
||||
required = AttributeMetadata.ALWAYS_TRUE;
|
||||
|
||||
// If scopes are configured, we will use scope-based selector and require the attribute just if scope is
|
||||
// in current authenticationSession (either default scope or by 'scope' parameter)
|
||||
if (rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||
if (UPConfigUtils.canBeAuthFlowContext(context)) {
|
||||
required = (c) -> requestedScopePredicate(c, rc.getScopes());
|
||||
} else {
|
||||
// Scopes not available for admin and account contexts
|
||||
required = AttributeMetadata.ALWAYS_FALSE;
|
||||
}
|
||||
}
|
||||
} else if (UPConfigUtils.canBeAuthFlowContext(context) && rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||
// for contexts executed from auth flow and with configured scopes requirement
|
||||
// we have to create required validation with scopes based selector
|
||||
|
||||
@ -29,6 +29,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
@ -209,7 +210,11 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
|
||||
|
||||
@Override
|
||||
public String getClientNote(String name) {
|
||||
return null;
|
||||
if (OAuth2Constants.SCOPE.equals(name) && scopes != null && !scopes.isEmpty()) {
|
||||
return String.join(" ", scopes);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -57,6 +57,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||
import org.keycloak.userprofile.AttributeGroupMetadata;
|
||||
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||
@ -92,8 +93,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||
testRealm.setClientScopes(new ArrayList<>());
|
||||
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("customer").protocol("openid-connect").build());
|
||||
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("client-a").protocol("openid-connect").build());
|
||||
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("some-optional-scope").protocol("openid-connect").build());
|
||||
ClientRepresentation client = KeycloakModelUtils.createClient(testRealm, "client-a");
|
||||
client.setDefaultClientScopes(Collections.singletonList("customer"));
|
||||
client.setOptionalClientScopes(Collections.singletonList("some-optional-scope"));
|
||||
KeycloakModelUtils.createClient(testRealm, "client-b");
|
||||
}
|
||||
|
||||
@ -1522,6 +1525,85 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@ModelTest
|
||||
public void testRequiredByOptionalClientScope(KeycloakSession session) throws IOException {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
session.getContext().setRealm(realm);
|
||||
|
||||
UserProfileProvider provider = getUserProfileProvider(session);
|
||||
UPConfig config = provider.getConfiguration();
|
||||
|
||||
UPAttributePermissions permissions = new UPAttributePermissions();
|
||||
permissions.setEdit(Set.of(ROLE_ADMIN, ROLE_USER));
|
||||
|
||||
UPAttributeRequired required = new UPAttributeRequired();
|
||||
required.setRoles(Set.of(ROLE_ADMIN, ROLE_USER));
|
||||
required.setScopes(Set.of("some-optional-scope"));
|
||||
|
||||
UPAttribute attrAddress = new UPAttribute(ATT_ADDRESS);
|
||||
attrAddress.setRequired(required);
|
||||
attrAddress.setPermissions(permissions);
|
||||
config.getAttributes().add(attrAddress);
|
||||
provider.setConfiguration(JsonSerialization.writeValueAsString(config));
|
||||
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
attributes.put(UserModel.USERNAME, "user");
|
||||
attributes.put(UserModel.FIRST_NAME, "John");
|
||||
attributes.put(UserModel.LAST_NAME, "Doe");
|
||||
attributes.put(UserModel.EMAIL, "user@email.test");
|
||||
|
||||
// client with default scopes. No address scope included
|
||||
configureAuthenticationSession(session, "client-a", null);
|
||||
|
||||
// No fail on admin and account console as they do not have scopes
|
||||
UserProfile profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
profile.validate();
|
||||
profile = provider.create(UserProfileContext.ACCOUNT, attributes);
|
||||
profile.validate();
|
||||
|
||||
// no fail on auth flow scopes when scope is not required
|
||||
profile = provider.create(UserProfileContext.REGISTRATION, attributes);
|
||||
profile.validate();
|
||||
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
|
||||
profile.validate();
|
||||
profile = provider.create(UserProfileContext.IDP_REVIEW, attributes);
|
||||
profile.validate();
|
||||
|
||||
// client with default scopes for which is attribute NOT configured as required
|
||||
configureAuthenticationSession(session, "client-a", Set.of("some-optional-scope"));
|
||||
|
||||
// No fail on admin and account console as they do not have scopes
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes);
|
||||
profile.validate();
|
||||
profile = provider.create(UserProfileContext.ACCOUNT, attributes);
|
||||
profile.validate();
|
||||
|
||||
// fail on auth flow scopes when scope is required
|
||||
try {
|
||||
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
|
||||
profile.validate();
|
||||
fail("Should fail validation");
|
||||
} catch (ValidationException ve) {
|
||||
assertTrue(ve.isAttributeOnError(ATT_ADDRESS));
|
||||
}
|
||||
try {
|
||||
profile = provider.create(UserProfileContext.REGISTRATION, attributes);
|
||||
profile.validate();
|
||||
fail("Should fail validation");
|
||||
} catch (ValidationException ve) {
|
||||
assertTrue(ve.isAttributeOnError(ATT_ADDRESS));
|
||||
}
|
||||
try {
|
||||
profile = provider.create(UserProfileContext.IDP_REVIEW, attributes);
|
||||
profile.validate();
|
||||
fail("Should fail validation");
|
||||
} catch (ValidationException ve) {
|
||||
assertTrue(ve.isAttributeOnError(ATT_ADDRESS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigurationInvalidScope() {
|
||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testConfigurationInvalidScope);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user