Do not require non-builtin attributes for service accounts
Closes #26716 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
6bbf8358b4
commit
bb12f3fb82
2 changed files with 49 additions and 15 deletions
|
@ -105,7 +105,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
protected Attributes createAttributes(UserProfileContext context, Map<String, ?> attributes,
|
protected Attributes createAttributes(UserProfileContext context, Map<String, ?> attributes,
|
||||||
UserModel user, UserProfileMetadata metadata) {
|
UserModel user, UserProfileMetadata metadata) {
|
||||||
|
|
||||||
if (user != null && user.getServiceAccountClientLink() != null) {
|
if (isServiceAccountUser(user)) {
|
||||||
return new LegacyAttributes(context, attributes, user, metadata, session);
|
return new LegacyAttributes(context, attributes, user, metadata, session);
|
||||||
}
|
}
|
||||||
return new DefaultAttributes(context, attributes, user, metadata, session);
|
return new DefaultAttributes(context, attributes, user, metadata, session);
|
||||||
|
@ -285,13 +285,14 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
Predicate<AttributeContext> required = AttributeMetadata.ALWAYS_FALSE;
|
Predicate<AttributeContext> required = AttributeMetadata.ALWAYS_FALSE;
|
||||||
if (rc != null) {
|
if (rc != null) {
|
||||||
if (rc.isAlways() || context.isRoleForContext(rc.getRoles())) {
|
if (rc.isAlways() || context.isRoleForContext(rc.getRoles())) {
|
||||||
required = AttributeMetadata.ALWAYS_TRUE;
|
// service accounts does not require common attributes
|
||||||
|
required = c -> !isServiceAccountUser(c.getUser());
|
||||||
|
|
||||||
// If scopes are configured, we will use scope-based selector and require the attribute just if scope is
|
// 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)
|
// in current authenticationSession (either default scope or by 'scope' parameter)
|
||||||
if (rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
if (rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||||
if (context.canBeAuthFlowContext()) {
|
if (context.canBeAuthFlowContext()) {
|
||||||
required = (c) -> requestedScopePredicate(c, rc.getScopes());
|
required = (c) -> !isServiceAccountUser(c.getUser()) && requestedScopePredicate(c, rc.getScopes());
|
||||||
} else {
|
} else {
|
||||||
// Scopes not available for admin and account contexts
|
// Scopes not available for admin and account contexts
|
||||||
required = AttributeMetadata.ALWAYS_FALSE;
|
required = AttributeMetadata.ALWAYS_FALSE;
|
||||||
|
@ -300,7 +301,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
} else if (context.canBeAuthFlowContext() && rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
} else if (context.canBeAuthFlowContext() && 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
|
||||||
required = (c) -> requestedScopePredicate(c, rc.getScopes());
|
required = (c) -> !isServiceAccountUser(c.getUser()) && requestedScopePredicate(c, rc.getScopes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +365,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
public boolean test(AttributeContext context) {
|
public boolean test(AttributeContext context) {
|
||||||
UserModel user = context.getUser();
|
UserModel user = context.getUser();
|
||||||
|
|
||||||
if (user != null && user.getServiceAccountClientLink() != null) {
|
if (isServiceAccountUser(user)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +442,10 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
return ac -> ac.getContext().isRoleForContext(viewRoles) || canEdit.test(ac);
|
return ac -> ac.getContext().isRoleForContext(viewRoles) || canEdit.test(ac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isServiceAccountUser(UserModel user) {
|
||||||
|
return user != null && user.getServiceAccountClientLink() != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get parsed config file configured in model. Default one used if not configured.
|
* Get parsed config file configured in model. Default one used if not configured.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,25 +21,33 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.BadRequestException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.Rule;
|
import java.util.Set;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPAttributeRequired;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
|
||||||
import org.keycloak.testsuite.util.ClientBuilder;
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.keycloak.userprofile.UserProfileConstants;
|
||||||
|
import org.keycloak.validate.validators.EmailValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -49,13 +57,6 @@ public class ServiceAccountUserProfileTest extends AbstractKeycloakTest {
|
||||||
private static String userId;
|
private static String userId;
|
||||||
private static String userName;
|
private static String userName;
|
||||||
|
|
||||||
@Rule
|
|
||||||
public AssertEvents events = new AssertEvents(this);
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
|
||||||
|
@ -158,4 +159,32 @@ public class ServiceAccountUserProfileTest extends AbstractKeycloakTest {
|
||||||
assertFalse(representation.getAttributes().isEmpty());
|
assertFalse(representation.getAttributes().isEmpty());
|
||||||
assertEquals("attr-1-value", representation.getAttributes().get("attr-1").get(0));
|
assertEquals("attr-1-value", representation.getAttributes().get("attr-1").get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmailFormatIsEnforced() {
|
||||||
|
final RealmResource realm = adminClient.realm("test");
|
||||||
|
UserResource serviceAccount = realm.users().get(userId);
|
||||||
|
UserRepresentation rep = serviceAccount.toRepresentation();
|
||||||
|
rep.setEmail("invalidEmail");
|
||||||
|
BadRequestException e = assertThrows(BadRequestException.class, () -> serviceAccount.update(rep));
|
||||||
|
assertThat(e.getResponse().readEntity(String.class), containsString(EmailValidator.MESSAGE_INVALID_EMAIL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributesAreNotRequired() {
|
||||||
|
final RealmResource realm = adminClient.realm("test");
|
||||||
|
UPConfig config = realm.users().userProfile().getConfiguration();
|
||||||
|
UPAttribute lastName = config.getAttribute(UserModel.LAST_NAME);
|
||||||
|
assertNotNull("The attribute lastName is not defined in User Profile", lastName);
|
||||||
|
UPAttributeRequired upRequired = new UPAttributeRequired(Set.of(
|
||||||
|
UserProfileConstants.ROLE_ADMIN, UserProfileConstants.ROLE_USER), null);
|
||||||
|
lastName.setRequired(upRequired);
|
||||||
|
realm.users().userProfile().update(config);
|
||||||
|
getCleanup().addCleanup(() -> realm.users().userProfile().update(null));
|
||||||
|
|
||||||
|
UserResource serviceAccount = realm.users().get(userId);
|
||||||
|
UserRepresentation rep = serviceAccount.toRepresentation();
|
||||||
|
rep.setLastName(null);
|
||||||
|
serviceAccount.update(rep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue