Removing the default cache metadata

Closes #23910
This commit is contained in:
Pedro Igor 2023-10-11 11:42:35 -03:00 committed by Alexander Schwartz
parent eedc4ceb18
commit 9c19a8972b
6 changed files with 123 additions and 22 deletions

View file

@ -105,16 +105,17 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return getRequestedClientScopes(requestedScopesString, client).map((csm) -> csm.getName()).anyMatch(configuredScopes::contains);
}
private String defaultRawConfig;
private static final Map<UserProfileContext, UserProfileMetadata> DEFAULT_METADATA = Collections.synchronizedMap(new HashMap<>());
protected String defaultRawConfig;
protected UPConfig parsedDefaultRawConfig;
public DeclarativeUserProfileProvider() {
defaultRawConfig = UPConfigUtils.readDefaultConfig();
// factory create
}
public DeclarativeUserProfileProvider(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig) {
public DeclarativeUserProfileProvider(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig, UPConfig parsedDefaultRawConfig) {
super(session, metadataRegistry);
this.defaultRawConfig = defaultRawConfig;
this.parsedDefaultRawConfig = parsedDefaultRawConfig;
}
@Override
@ -124,7 +125,7 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
@Override
protected UserProfileProvider create(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry) {
return new DeclarativeUserProfileProvider(session, metadataRegistry, defaultRawConfig);
return new DeclarativeUserProfileProvider(session, metadataRegistry, defaultRawConfig, parsedDefaultRawConfig);
}
@Override
@ -141,6 +142,16 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
return new LegacyAttributes(context, attributes, user, metadata, session);
}
@Override
protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata) {
if (isDeclarativeConfigurationEnabled) {
// default metadata for each context is based on the default realm configuration
return decorateUserProfileForCache(metadata, parsedDefaultRawConfig);
}
return metadata;
}
@Override
protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata, KeycloakSession session) {
UserProfileContext context = metadata.getContext();
@ -161,7 +172,7 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
ComponentModel component = getComponentModel().orElse(null);
if (component == null) {
return DEFAULT_METADATA.computeIfAbsent(context, (c) -> decorateUserProfileForCache(decoratedMetadata, getParsedConfig(defaultRawConfig)));
return decoratedMetadata;
}
Map<UserProfileContext, UserProfileMetadata> metadataMap = component.getNote(PARSED_CONFIG_COMPONENT_KEY);
@ -267,8 +278,14 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
@Override
public void init(Config.Scope config) {
super.init(config);
isDeclarativeConfigurationEnabled = Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_USER_PROFILE);
defaultRawConfig = UPConfigUtils.readDefaultConfig();
try {
parsedDefaultRawConfig = parseConfig(defaultRawConfig);
} catch (IOException cause) {
throw new RuntimeException("Failed to parse default user profile configuration", cause);
}
super.init(config);
}
@Override
@ -535,6 +552,22 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
throw new RuntimeException("UserProfile configuration for realm '" + session.getContext().getRealm().getName() + "' is invalid: " + errors.toString());
}
for (AttributeMetadata metadata : decoratedMetadata.getAttributes()) {
String attributeName = metadata.getName();
if (isBuiltInAttribute(attributeName)) {
UPAttribute upAttribute = parsedDefaultRawConfig.getAttribute(attributeName);
Map<String, Map<String, Object>> validations = Optional.ofNullable(upAttribute.getValidations()).orElse(Collections.emptyMap());
for (String id : validations.keySet()) {
List<AttributeValidatorMetadata> validators = metadata.getValidators();
// do not include the default validators for built-in attributes into the base metadata
// user-defined configuration will add its own validators
validators.removeIf(m -> m.getValidatorId().equals(id));
}
}
}
return decorateUserProfileForCache(decoratedMetadata, parsedConfig);
};
}

View file

@ -7,7 +7,7 @@ import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileMetadata;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.config.UPConfigUtils;
import org.keycloak.userprofile.config.UPConfig;
import java.util.Map;
@ -20,14 +20,14 @@ public class CustomUserProfileProvider extends DeclarativeUserProfileProvider {
}
public CustomUserProfileProvider(KeycloakSession session,
Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig) {
super(session, metadataRegistry, defaultRawConfig);
Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig, UPConfig parsedDefaultRawConfig) {
super(session, metadataRegistry, defaultRawConfig, parsedDefaultRawConfig);
}
@Override
protected UserProfileProvider create(KeycloakSession session,
Map<UserProfileContext, UserProfileMetadata> metadataRegistry) {
return new CustomUserProfileProvider(session, metadataRegistry, UPConfigUtils.readDefaultConfig());
return new CustomUserProfileProvider(session, metadataRegistry, defaultRawConfig, parsedDefaultRawConfig);
}
@Override

View file

@ -630,7 +630,15 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
protected void updateError(UserRepresentation user, int expectedStatus, String expectedMessage) throws IOException {
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
assertEquals(expectedStatus, response.getStatus());
assertEquals(expectedMessage, response.asJson(ErrorRepresentation.class).getErrorMessage());
ErrorRepresentation errorRep = response.asJson(ErrorRepresentation.class);
List<ErrorRepresentation> errors = errorRep.getErrors();
if (errors == null) {
assertEquals(expectedMessage, errorRep.getErrorMessage());
} else {
assertThat(errors.stream().map(ErrorRepresentation::getErrorMessage)
.filter(expectedMessage::equals).collect(Collectors.toList()), containsInAnyOrder(expectedMessage));
}
}
@Test

View file

@ -365,6 +365,7 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
@Override
public void testUpdateSingleField() throws IOException {
setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"email\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"firstName\"," + PERMISSIONS_ALL + "},"
+ "{\"name\": \"lastName\"," + PERMISSIONS_ALL + ", \"required\": {}}"
+ "]}");

View file

@ -20,15 +20,19 @@ package org.keycloak.testsuite.admin;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jakarta.ws.rs.WebApplicationException;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.Profile.Feature;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.UserProfileAttributeMetadata;
import org.keycloak.representations.idm.UserProfileMetadata;
import org.keycloak.representations.idm.RealmRepresentation;
@ -119,6 +123,22 @@ public class UserTestWithUserProfile extends UserTest {
return attribute;
}
@Test
public void testDefaultCharacterValidationOnUsername() {
List<String> invalidNames = List.of("1user\\\\", "2user\\\\%", "3user\\\\*", "4user\\\\_");
for (String invalidName : invalidNames) {
try {
createUser(invalidName, "test@invalid.org");
fail("Should fail because the username contains invalid characters");
} catch (WebApplicationException bre) {
assertEquals(400, bre.getResponse().getStatus());
ErrorRepresentation error = bre.getResponse().readEntity(ErrorRepresentation.class);
assertEquals("username contains invalid character.", error.getErrorMessage());
}
}
}
@Override
protected boolean isDeclarativeUserProfile() {
return true;

View file

@ -121,6 +121,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, "profiled-user");
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfileProvider provider = getUserProfileProvider(session);
@ -136,8 +139,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertTrue(ve.isAttributeOnError("address"));
}
assertThat(profile.getAttributes().nameSet(),
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, "address"));
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, "address");
attributes.put("address", "myaddress");
@ -157,6 +159,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, "profiled-user");
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfileProvider provider = getUserProfileProvider(session);
@ -311,6 +316,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
assertTrue(ve.isAttributeOnError("address"));
}
user.setSingleAttribute(UserModel.FIRST_NAME, "john");
user.setSingleAttribute(UserModel.LAST_NAME, "doe");
user.setSingleAttribute(UserModel.EMAIL, "jd@keycloak.org");
user.setAttribute("address", Arrays.asList("fixed-address"));
profile = provider.create(UserProfileContext.ACCOUNT, user);
@ -326,6 +334,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
private static void testGetProfileAttributes(KeycloakSession session) {
RealmModel realm = session.getContext().getRealm();
UserModel user = session.users().addUser(realm, org.keycloak.models.utils.KeycloakModelUtils.generateId());
user.setFirstName("John");
user.setLastName("John");
user.setEmail(org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfileProvider provider = getUserProfileProvider(session);
provider.setConfiguration("{\"attributes\": [{\"name\": \"address\", \"required\": {}, \"permissions\": {\"edit\": [\"user\"]}}]}");
@ -345,9 +356,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
}
assertNotNull(attributes.getFirstValue(UserModel.USERNAME));
assertNull(attributes.getFirstValue(UserModel.EMAIL));
assertNull(attributes.getFirstValue(UserModel.FIRST_NAME));
assertNull(attributes.getFirstValue(UserModel.LAST_NAME));
assertNotNull(attributes.getFirstValue(UserModel.EMAIL));
assertNotNull(attributes.getFirstValue(UserModel.FIRST_NAME));
assertNotNull(attributes.getFirstValue(UserModel.LAST_NAME));
assertNull(attributes.getFirstValue("address"));
user.setAttribute("address", Arrays.asList("fixed-address"));
@ -508,6 +519,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
attributes.put("address", Arrays.asList("fixed-address"));
attributes.put("department", Arrays.asList("sales"));
@ -519,7 +533,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
UserModel user = profile.create();
assertThat(profile.getAttributes().nameSet(),
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE, "department"));
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE, "department"));
assertNull(user.getFirstAttribute("department"));
@ -558,6 +572,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, "readonly@foo.bar");
UserProfileProvider provider = getUserProfileProvider(session);
@ -569,7 +585,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
UserModel user = profile.create();
assertThat(profile.getAttributes().nameSet(),
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE));
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE));
profile = provider.create(UserProfileContext.USER_API, attributes, user);
@ -600,6 +616,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, "canchange@foo.bar");
UserProfileProvider provider = getUserProfileProvider(session);
@ -611,7 +629,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
UserModel user = profile.create();
assertThat(profile.getAttributes().nameSet(),
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE));
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE));
profile = provider.create(UserProfileContext.USER_API, attributes, user);
@ -637,6 +655,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId());
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
attributes.put("address", Arrays.asList("fixed-address"));
attributes.put("department", Arrays.asList("sales"));
attributes.put("phone", Arrays.asList("fixed-phone"));
@ -649,7 +670,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
UserProfile profile = provider.create(UserProfileContext.ACCOUNT, attributes);
UserModel user = profile.create();
assertThat(profile.getAttributes().nameSet(),
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE, "address", "department", "phone"));
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE, "address", "department", "phone"));
attributes.put("address", Arrays.asList("change-address"));
attributes.put("department", Arrays.asList("changed-sales"));
@ -837,6 +858,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
Map<String, Object> attributes = new HashMap<>();
attributes.put(UserModel.USERNAME, "us");
attributes.put(UserModel.FIRST_NAME, "John");
attributes.put(UserModel.LAST_NAME, "Doe");
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -934,6 +958,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
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, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
// not present attributes are OK
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -997,6 +1024,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
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, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -1049,6 +1079,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
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, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
// null is OK as attribute is optional
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -1168,6 +1201,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
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, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
// NO fail on common contexts
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -1218,6 +1254,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
attributes.put(UserModel.USERNAME, "user");
attributes.put(UserModel.FIRST_NAME, "user");
attributes.put(UserModel.LAST_NAME, "user");
attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org");
// NO fail on USER contexts
UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
@ -1400,6 +1437,8 @@ public class UserProfileTest extends AbstractUserProfileTest {
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 for which is attribute NOT configured as required