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>
This commit is contained in:
parent
59536becec
commit
cd154cf318
3 changed files with 88 additions and 1 deletions
|
@ -296,6 +296,17 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
if (rc != null) {
|
if (rc != null) {
|
||||||
if (rc.isAlways() || UPConfigUtils.isRoleForContext(context, rc.getRoles())) {
|
if (rc.isAlways() || UPConfigUtils.isRoleForContext(context, rc.getRoles())) {
|
||||||
required = AttributeMetadata.ALWAYS_TRUE;
|
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()) {
|
} else if (UPConfigUtils.canBeAuthFlowContext(context) && rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||||
// for contexts executed from auth flow and with configured scopes requirement
|
// for contexts executed from auth flow and with configured scopes requirement
|
||||||
// we have to create required validation with scopes based selector
|
// we have to create required validation with scopes based selector
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
@ -214,7 +215,11 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientNote(String name) {
|
public String getClientNote(String name) {
|
||||||
return null;
|
if (OAuth2Constants.SCOPE.equals(name) && scopes != null && !scopes.isEmpty()) {
|
||||||
|
return String.join(" ", scopes);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -61,6 +61,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
|
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
|
||||||
import org.keycloak.representations.userprofile.config.UPGroup;
|
import org.keycloak.representations.userprofile.config.UPGroup;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
|
||||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||||
import org.keycloak.testsuite.util.LDAPRule;
|
import org.keycloak.testsuite.util.LDAPRule;
|
||||||
import org.keycloak.userprofile.AttributeGroupMetadata;
|
import org.keycloak.userprofile.AttributeGroupMetadata;
|
||||||
|
@ -98,8 +99,10 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
testRealm.setClientScopes(new ArrayList<>());
|
testRealm.setClientScopes(new ArrayList<>());
|
||||||
testRealm.getClientScopes().add(ClientScopeBuilder.create().name("customer").protocol("openid-connect").build());
|
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("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");
|
ClientRepresentation client = KeycloakModelUtils.createClient(testRealm, "client-a");
|
||||||
client.setDefaultClientScopes(Collections.singletonList("customer"));
|
client.setDefaultClientScopes(Collections.singletonList("customer"));
|
||||||
|
client.setOptionalClientScopes(Collections.singletonList("some-optional-scope"));
|
||||||
KeycloakModelUtils.createClient(testRealm, "client-b");
|
KeycloakModelUtils.createClient(testRealm, "client-b");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1429,6 +1432,74 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ModelTest
|
||||||
|
public void testRequiredByOptionalClientScope(KeycloakSession session) {
|
||||||
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
session.getContext().setRealm(realm);
|
||||||
|
|
||||||
|
UserProfileProvider provider = getUserProfileProvider(session);
|
||||||
|
UPConfig config = parseDefaultConfig();
|
||||||
|
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"))));
|
||||||
|
provider.setConfiguration(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
|
@Test
|
||||||
public void testConfigurationInvalidScope() {
|
public void testConfigurationInvalidScope() {
|
||||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testConfigurationInvalidScope);
|
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testConfigurationInvalidScope);
|
||||||
|
|
Loading…
Reference in a new issue