[KEYCLOAK-18427] - Allowing switching to declarative provider

This commit is contained in:
Pedro Igor 2021-06-25 10:07:16 -03:00
parent 512bcd14f7
commit 948f453e2d
27 changed files with 247 additions and 252 deletions

View file

@ -61,8 +61,7 @@ public class Profile {
WEB_AUTHN(Type.DEFAULT, Type.PREVIEW), WEB_AUTHN(Type.DEFAULT, Type.PREVIEW),
CLIENT_POLICIES(Type.DEFAULT), CLIENT_POLICIES(Type.DEFAULT),
CIBA(Type.PREVIEW), CIBA(Type.PREVIEW),
MAP_STORAGE(Type.EXPERIMENTAL), MAP_STORAGE(Type.EXPERIMENTAL);
DECLARATIVE_USER_PROFILE(Type.PREVIEW);
private final Type typeProject; private final Type typeProject;
private final Type typeProduct; private final Type typeProduct;

View file

@ -21,8 +21,8 @@ public class ProfileTest {
@Test @Test
public void checkDefaultsKeycloak() { public void checkDefaultsKeycloak() {
Assert.assertEquals("community", Profile.getName()); Assert.assertEquals("community", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.CIBA, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE); assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.CIBA, Profile.Feature.MAP_STORAGE);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.CIBA, Profile.Feature.DECLARATIVE_USER_PROFILE); assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.CIBA);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS); assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType()); Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
@ -37,8 +37,8 @@ public class ProfileTest {
Profile.init(); Profile.init();
Assert.assertEquals("product", Profile.getName()); Assert.assertEquals("product", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN, Profile.Feature.CIBA, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE); assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN, Profile.Feature.CIBA, Profile.Feature.MAP_STORAGE);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.WEB_AUTHN, Profile.Feature.CIBA, Profile.Feature.DECLARATIVE_USER_PROFILE); assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.WEB_AUTHN, Profile.Feature.CIBA);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS); assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType()); Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());

View file

@ -138,6 +138,7 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.validation.ValidationUtil; import org.keycloak.validation.ValidationUtil;
@ -1077,6 +1078,11 @@ public class RepresentationToModel {
renameRealm(realm, rep.getRealm()); renameRealm(realm, rep.getRealm());
} }
if (!Boolean.parseBoolean(rep.getAttributesOrEmpty().get("userProfileEnabled"))) {
UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
provider.setConfiguration(null);
}
// Import attributes first, so the stuff saved directly on representation (displayName, bruteForce etc) has bigger priority // Import attributes first, so the stuff saved directly on representation (displayName, bruteForce etc) has bigger priority
if (rep.getAttributes() != null) { if (rep.getAttributes() != null) {
Set<String> attrsToRemove = new HashSet<>(realm.getAttributes().keySet()); Set<String> attrsToRemove = new HashSet<>(realm.getAttributes().keySet());

View file

@ -275,11 +275,8 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
List<String> values = EMPTY_VALUE; List<String> values = EMPTY_VALUE;
AttributeMetadata metadata = metadataByAttribute.get(attributeName); AttributeMetadata metadata = metadataByAttribute.get(attributeName);
// if the attribute is not provided and does not have view permission, use the current values if (user != null && isIncludeAttributeIfNotProvided(metadata)) {
// this check makes possible to decide whether or not validation should happen for read-only attributes values = user.getAttributes().getOrDefault(attributeName, EMPTY_VALUE);
// when the context does not have access to such attributes
if (user != null && !metadata.canView(createAttributeContext(metadata))) {
values = user.getAttributes().get(attributeName);
} }
newAttributes.put(attributeName, values); newAttributes.put(attributeName, values);
@ -302,6 +299,11 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
return newAttributes; return newAttributes;
} }
protected boolean isIncludeAttributeIfNotProvided(AttributeMetadata metadata) {
// user api expects that attributes are not updated if not provided when in legacy mode
return UserProfileContext.USER_API.equals(context);
}
/** /**
* <p>Checks whether an attribute is support by the profile configuration and the current context. * <p>Checks whether an attribute is support by the profile configuration and the current context.
* *

View file

@ -286,10 +286,14 @@ public class UserResource {
UserProfileProvider provider = session.getProvider(UserProfileProvider.class); UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
UserProfile profile = provider.create(USER_API, user); UserProfile profile = provider.create(USER_API, user);
Map<String, List<String>> attributes = profile.getAttributes().getReadable(false); if (rep.getAttributes() != null) {
Map<String, List<String>> allowedAttributes = profile.getAttributes().getReadable(false);
if (!attributes.isEmpty()) { for (String attributeName : rep.getAttributes().keySet()) {
rep.setAttributes(attributes); if (!allowedAttributes.containsKey(attributeName)) {
rep.getAttributes().remove(attributeName);
}
}
} }
return rep; return rep;

View file

@ -17,7 +17,7 @@
* *
*/ */
package org.keycloak.userprofile.legacy; package org.keycloak.userprofile;
import static org.keycloak.userprofile.DefaultAttributes.READ_ONLY_ATTRIBUTE_KEY; import static org.keycloak.userprofile.DefaultAttributes.READ_ONLY_ATTRIBUTE_KEY;
import static org.keycloak.userprofile.UserProfileContext.ACCOUNT; import static org.keycloak.userprofile.UserProfileContext.ACCOUNT;
@ -44,16 +44,6 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.userprofile.AttributeContext;
import org.keycloak.userprofile.AttributeValidatorMetadata;
import org.keycloak.userprofile.Attributes;
import org.keycloak.userprofile.DefaultAttributes;
import org.keycloak.userprofile.DefaultUserProfile;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileMetadata;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.UserProfileProviderFactory;
import org.keycloak.userprofile.validator.BlankAttributeValidator; import org.keycloak.userprofile.validator.BlankAttributeValidator;
import org.keycloak.userprofile.validator.BrokeringFederatedUsernameHasValueValidator; import org.keycloak.userprofile.validator.BrokeringFederatedUsernameHasValueValidator;
import org.keycloak.userprofile.validator.DuplicateEmailValidator; import org.keycloak.userprofile.validator.DuplicateEmailValidator;
@ -79,7 +69,20 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
KeycloakSession session = c.getSession(); KeycloakSession session = c.getSession();
KeycloakContext context = session.getContext(); KeycloakContext context = session.getContext();
RealmModel realm = context.getRealm(); RealmModel realm = context.getRealm();
return ((c.getContext() == REGISTRATION_PROFILE || c.getContext() == IDP_REVIEW) && !realm.isRegistrationEmailAsUsername()) || realm.isEditUsernameAllowed();
switch (c.getContext()) {
case REGISTRATION_PROFILE:
case IDP_REVIEW:
return !realm.isRegistrationEmailAsUsername();
case ACCOUNT_OLD:
case ACCOUNT:
case UPDATE_PROFILE:
return realm.isEditUsernameAllowed();
case USER_API:
return true;
default:
return false;
}
} }
public static Pattern getRegexPatternString(String[] builtinReadOnlyAttributes) { public static Pattern getRegexPatternString(String[] builtinReadOnlyAttributes) {

View file

@ -1,4 +1,4 @@
package org.keycloak.userprofile.config; package org.keycloak.userprofile;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -7,6 +7,7 @@ import java.util.Map;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.userprofile.AttributeMetadata; import org.keycloak.userprofile.AttributeMetadata;
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.DefaultAttributes; import org.keycloak.userprofile.DefaultAttributes;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileMetadata; import org.keycloak.userprofile.UserProfileMetadata;
@ -39,4 +40,9 @@ public class DeclarativeAttributes extends DefaultAttributes {
return attributes; return attributes;
} }
@Override
protected boolean isIncludeAttributeIfNotProvided(AttributeMetadata metadata) {
return !metadata.canView(createAttributeContext(metadata));
}
} }

View file

@ -17,7 +17,7 @@
* *
*/ */
package org.keycloak.userprofile.config; package org.keycloak.userprofile;
import static org.keycloak.common.util.ObjectUtil.isBlank; import static org.keycloak.common.util.ObjectUtil.isBlank;
import static org.keycloak.protocol.oidc.TokenManager.getRequestedClientScopes; import static org.keycloak.protocol.oidc.TokenManager.getRequestedClientScopes;
@ -25,8 +25,6 @@ import static org.keycloak.userprofile.config.UPConfigUtils.readConfig;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -35,33 +33,28 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.component.AmphibianProviderFactory; import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException; import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientScopeModel.ClientScopeRemovedEvent;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderEvent; import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.userprofile.AttributeContext; import org.keycloak.userprofile.config.DeclarativeUserProfileModel;
import org.keycloak.userprofile.AttributeMetadata; import org.keycloak.userprofile.config.UPAttribute;
import org.keycloak.userprofile.AttributeValidatorMetadata; import org.keycloak.userprofile.config.UPAttributePermissions;
import org.keycloak.userprofile.Attributes; import org.keycloak.userprofile.config.UPAttributeRequired;
import org.keycloak.userprofile.UserProfileContext; import org.keycloak.userprofile.config.UPAttributeSelector;
import org.keycloak.userprofile.UserProfileMetadata; import org.keycloak.userprofile.config.UPConfig;
import org.keycloak.userprofile.UserProfileProvider; import org.keycloak.userprofile.config.UPConfigUtils;
import org.keycloak.userprofile.legacy.AbstractUserProfileProvider;
import org.keycloak.userprofile.validator.AttributeRequiredByMetadataValidator; import org.keycloak.userprofile.validator.AttributeRequiredByMetadataValidator;
import org.keycloak.userprofile.validator.BlankAttributeValidator;
import org.keycloak.userprofile.validator.ImmutableAttributeValidator; import org.keycloak.userprofile.validator.ImmutableAttributeValidator;
import org.keycloak.validate.AbstractSimpleValidator; import org.keycloak.validate.AbstractSimpleValidator;
import org.keycloak.validate.ValidatorConfig; import org.keycloak.validate.ValidatorConfig;
@ -73,12 +66,12 @@ import org.keycloak.validate.ValidatorConfig;
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
*/ */
public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<DeclarativeUserProfileProvider> public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<UserProfileProvider>
implements AmphibianProviderFactory<DeclarativeUserProfileProvider>, EnvironmentDependentProviderFactory { implements AmphibianProviderFactory<UserProfileProvider> {
public static final String SYSTEM_DEFAULT_CONFIG_RESOURCE = "keycloak-default-user-profile.json";
public static final String ID = "declarative-user-profile"; public static final String ID = "declarative-user-profile";
public static final String UP_PIECES_COUNT_COMPONENT_CONFIG_KEY = "config-pieces-count"; public static final String UP_PIECES_COUNT_COMPONENT_CONFIG_KEY = "config-pieces-count";
public static final String REALM_USER_PROFILE_ENABLED = "userProfileEnabled";
private static final String PARSED_CONFIG_COMPONENT_KEY = "kc.user.profile.metadata"; private static final String PARSED_CONFIG_COMPONENT_KEY = "kc.user.profile.metadata";
private static final String UP_PIECE_COMPONENT_CONFIG_KEY_BASE = "config-piece-"; private static final String UP_PIECE_COMPONENT_CONFIG_KEY_BASE = "config-piece-";
@ -106,7 +99,7 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
private String defaultRawConfig; private String defaultRawConfig;
public DeclarativeUserProfileProvider() { public DeclarativeUserProfileProvider() {
// for reflection defaultRawConfig = UPConfigUtils.readDefaultConfig();
} }
public DeclarativeUserProfileProvider(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig) { public DeclarativeUserProfileProvider(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry, String defaultRawConfig) {
@ -120,18 +113,33 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
} }
@Override @Override
protected DeclarativeUserProfileProvider create(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry) { protected UserProfileProvider create(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry) {
return new DeclarativeUserProfileProvider(session, metadataRegistry, defaultRawConfig); return new DeclarativeUserProfileProvider(session, metadataRegistry, defaultRawConfig);
} }
@Override @Override
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 (!isEnabled(session)) {
return new DefaultAttributes(context, attributes, user, metadata, session);
}
return new DeclarativeAttributes(context, attributes, user, metadata, session); return new DeclarativeAttributes(context, attributes, user, metadata, session);
} }
@Override @Override
protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata, KeycloakSession session) { protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata, KeycloakSession session) {
UserProfileContext context = metadata.getContext();
UserProfileMetadata decoratedMetadata = metadata.clone();
if (!isEnabled(session)) {
if(!context.equals(UserProfileContext.USER_API) && !context.equals(UserProfileContext.REGISTRATION_USER_CREATION)) {
decoratedMetadata.addAttribute(UserModel.FIRST_NAME, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(
Messages.MISSING_FIRST_NAME))).setAttributeDisplayName("${firstName}");
decoratedMetadata.addAttribute(UserModel.LAST_NAME, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME))).setAttributeDisplayName("${lastName}");
return decoratedMetadata;
}
}
ComponentModel model = getComponentModelOrCreate(session); ComponentModel model = getComponentModelOrCreate(session);
Map<UserProfileContext, UserProfileMetadata> metadataMap = model.getNote(PARSED_CONFIG_COMPONENT_KEY); Map<UserProfileContext, UserProfileMetadata> metadataMap = model.getNote(PARSED_CONFIG_COMPONENT_KEY);
@ -141,7 +149,7 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
model.setNote(PARSED_CONFIG_COMPONENT_KEY, metadataMap); model.setNote(PARSED_CONFIG_COMPONENT_KEY, metadataMap);
} }
return metadataMap.computeIfAbsent(metadata.getContext(), (context) -> decorateUserProfileForCache(metadata, model)); return metadataMap.computeIfAbsent(context, (c) -> decorateUserProfileForCache(decoratedMetadata, model));
} }
@Override @Override
@ -175,6 +183,10 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
@Override @Override
public String getConfiguration() { public String getConfiguration() {
if (!isEnabled(session)) {
return null;
}
String cfg = getConfigJsonFromComponentModel(getComponentModel()); String cfg = getConfigJsonFromComponentModel(getComponentModel());
if (isBlank(cfg)) { if (isBlank(cfg)) {
@ -190,6 +202,8 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
removeConfigJsonFromComponentModel(component); removeConfigJsonFromComponentModel(component);
RealmModel realm = session.getContext().getRealm();
if (!isBlank(configuration)) { if (!isBlank(configuration)) {
// store new parts // store new parts
List<String> parts = UPConfigUtils.getChunks(configuration, 3800); List<String> parts = UPConfigUtils.getChunks(configuration, 3800);
@ -202,19 +216,15 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
for (String part : parts) { for (String part : parts) {
config.putSingle(UP_PIECE_COMPONENT_CONFIG_KEY_BASE + (i++), part); config.putSingle(UP_PIECE_COMPONENT_CONFIG_KEY_BASE + (i++), part);
} }
}
session.getContext().getRealm().updateComponent(component); realm.updateComponent(component);
} else {
realm.removeComponent(component);
}
} }
@Override @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {
// TODO: We should avoid blocking operations during startup. Need to review this.
try (InputStream is = getClass().getResourceAsStream(SYSTEM_DEFAULT_CONFIG_RESOURCE)) {
defaultRawConfig = StreamUtil.readString(is, Charset.defaultCharset());
} catch (IOException cause) {
throw new RuntimeException("Failed to load default user profile config file", cause);
}
} }
@Override @Override
@ -231,23 +241,19 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
* This method is called for each {@link UserProfileContext} in each realm, and metadata are cached then and this * This method is called for each {@link UserProfileContext} in each realm, and metadata are cached then and this
* method is called again only if configuration changes. * method is called again only if configuration changes.
* *
* @param metadata base to be decorated based on configuration loaded from component model * @param decoratedMetadata base to be decorated based on configuration loaded from component model
* @param model component model to get "per realm" configuration from * @param model component model to get "per realm" configuration from
* @return decorated metadata * @return decorated metadata
*/ */
protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata metadata, ComponentModel model) { protected UserProfileMetadata decorateUserProfileForCache(UserProfileMetadata decoratedMetadata, ComponentModel model) {
UserProfileContext context = metadata.getContext(); UserProfileContext context = decoratedMetadata.getContext();
UPConfig parsedConfig = getParsedConfig(model); UPConfig parsedConfig = getParsedConfig(model);
// do not change config for REGISTRATION_USER_CREATION context, everything important is covered thanks to REGISTRATION_PROFILE // do not change config for REGISTRATION_USER_CREATION context, everything important is covered thanks to REGISTRATION_PROFILE
if (parsedConfig == null || context == UserProfileContext.REGISTRATION_USER_CREATION) { if (parsedConfig == null || context == UserProfileContext.REGISTRATION_USER_CREATION) {
return metadata; return decoratedMetadata;
} }
// need to clone otherwise changes to profile config are going to be reflected
// in the default config
UserProfileMetadata decoratedMetadata = metadata.clone();
for (UPAttribute attrConfig : parsedConfig.getAttributes()) { for (UPAttribute attrConfig : parsedConfig.getAttributes()) {
String attributeName = attrConfig.getName(); String attributeName = attrConfig.getName();
List<AttributeValidatorMetadata> validators = new ArrayList<>(); List<AttributeValidatorMetadata> validators = new ArrayList<>();
@ -425,8 +431,14 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
model.getConfig().remove(UP_PIECES_COUNT_COMPONENT_CONFIG_KEY); model.getConfig().remove(UP_PIECES_COUNT_COMPONENT_CONFIG_KEY);
} }
@Override /**
public boolean isSupported() { * Returns whether the declarative provider is enabled to a realm
return Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_USER_PROFILE); *
* @deprecated should be removed once {@link DeclarativeUserProfileProvider} becomes the default.
* @param session the session
* @return {@code true} if the declarative provider is enabled. Otherwise, {@code false}.
*/
private Boolean isEnabled(KeycloakSession session) {
return session.getContext().getRealm().getAttribute(REALM_USER_PROFILE_ENABLED, false);
} }
} }

View file

@ -20,6 +20,7 @@
package org.keycloak.userprofile.config; package org.keycloak.userprofile.config;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.UserProfileProvider; import org.keycloak.userprofile.UserProfileProvider;
/** /**

View file

@ -20,6 +20,7 @@ import static org.keycloak.common.util.ObjectUtil.isBlank;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -28,6 +29,7 @@ import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -45,6 +47,7 @@ import org.keycloak.validate.Validators;
*/ */
public class UPConfigUtils { public class UPConfigUtils {
private static final String SYSTEM_DEFAULT_CONFIG_RESOURCE = "keycloak-default-user-profile.json";
public static final String ROLE_USER = "user"; public static final String ROLE_USER = "user";
public static final String ROLE_ADMIN = "admin"; public static final String ROLE_ADMIN = "admin";
@ -260,4 +263,11 @@ public class UPConfigUtils {
return str.substring(0, 1).toUpperCase() + str.substring(1); return str.substring(0, 1).toUpperCase() + str.substring(1);
} }
public static String readDefaultConfig() {
try (InputStream is = UPConfigUtils.class.getResourceAsStream(SYSTEM_DEFAULT_CONFIG_RESOURCE)) {
return StreamUtil.readString(is, Charset.defaultCharset());
} catch (IOException cause) {
throw new RuntimeException("Failed to load default user profile config file", cause);
}
}
} }

View file

@ -1,70 +0,0 @@
/*
*
* * Copyright 2021 Red Hat, Inc. and/or its affiliates
* * and other contributors as indicated by the @author tags.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.keycloak.userprofile.legacy;
import java.util.Map;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;
import org.keycloak.userprofile.AttributeValidatorMetadata;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileMetadata;
import org.keycloak.userprofile.validator.BlankAttributeValidator;
/**
* @author <a href="mailto:markus.till@bosch.io">Markus Till</a>
*/
public class DefaultUserProfileProvider extends AbstractUserProfileProvider<DefaultUserProfileProvider> {
private static final String PROVIDER_ID = "legacy-user-profile";
public DefaultUserProfileProvider() {
// for reflection
}
public DefaultUserProfileProvider(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> validators) {
super(session, validators);
}
@Override
protected DefaultUserProfileProvider create(KeycloakSession session, Map<UserProfileContext, UserProfileMetadata> metadataRegistry) {
return new DefaultUserProfileProvider(session, metadataRegistry);
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public int order() {
return 1;
}
protected UserProfileMetadata configureUserProfile(UserProfileMetadata metadata) {
UserProfileContext ctx = metadata.getContext();
if(ctx != UserProfileContext.USER_API && ctx != UserProfileContext.REGISTRATION_USER_CREATION) {
metadata.addAttribute(UserModel.FIRST_NAME, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_FIRST_NAME))).setAttributeDisplayName("${firstName}");
metadata.addAttribute(UserModel.LAST_NAME, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME))).setAttributeDisplayName("${lastName}");
}
return metadata;
}
}

View file

@ -16,5 +16,4 @@
# * limitations under the License. # * limitations under the License.
# */ # */
# #
org.keycloak.userprofile.legacy.DefaultUserProfileProvider org.keycloak.userprofile.DeclarativeUserProfileProvider
org.keycloak.userprofile.config.DeclarativeUserProfileProvider

View file

@ -19,9 +19,6 @@ echo ** Adding max-detail-length to eventsStore spi **
echo ** Adding spi=userProfile with legacy-user-profile configuration of read-only attributes ** echo ** Adding spi=userProfile with legacy-user-profile configuration of read-only attributes **
/subsystem=keycloak-server/spi=userProfile/:add /subsystem=keycloak-server/spi=userProfile/:add
/subsystem=keycloak-server/spi=userProfile/provider=legacy-user-profile/:add(properties={},enabled=true)
/subsystem=keycloak-server/spi=userProfile/provider=legacy-user-profile/:map-put(name=properties,key=read-only-attributes,value=[deniedFoo,deniedBar*,deniedSome/thing,deniedsome*thing])
/subsystem=keycloak-server/spi=userProfile/provider=legacy-user-profile/:map-put(name=properties,key=admin-read-only-attributes,value=[deniedSomeAdmin])
/subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:add(properties={},enabled=true) /subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:add(properties={},enabled=true)
/subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:map-put(name=properties,key=read-only-attributes,value=[deniedFoo,deniedBar*,deniedSome/thing,deniedsome*thing]) /subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:map-put(name=properties,key=read-only-attributes,value=[deniedFoo,deniedBar*,deniedSome/thing,deniedsome*thing])
/subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:map-put(name=properties,key=admin-read-only-attributes,value=[deniedSomeAdmin]) /subsystem=keycloak-server/spi=userProfile/provider=declarative-user-profile/:map-put(name=properties,key=admin-read-only-attributes,value=[deniedSomeAdmin])

View file

@ -24,7 +24,4 @@ spi.truststore.file.file=${kc.home.dir}/conf/keycloak.truststore
spi.truststore.file.password=secret spi.truststore.file.password=secret
# http client connection reuse settings # http client connection reuse settings
spi.connections-http-client.default.reuse-connections=false spi.connections-http-client.default.reuse-connections=false
# user profile provider settings
spi.user-profile.provider=${keycloak.userProfile.provider:legacy-user-profile}

View file

@ -20,50 +20,40 @@
package org.keycloak.testsuite.admin.userprofile; package org.keycloak.testsuite.admin.userprofile;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import static org.keycloak.userprofile.config.UPConfigUtils.readDefaultConfig;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.HashMap;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.UserProfileResource; import org.keycloak.admin.client.resource.UserProfileResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.StreamUtil; import org.keycloak.common.util.StreamUtil;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.admin.AbstractAdminTest; import org.keycloak.testsuite.admin.AbstractAdminTest;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider; import org.keycloak.userprofile.config.UPConfigUtils;
import org.keycloak.userprofile.UserProfileSpi;
import org.keycloak.userprofile.config.DeclarativeUserProfileProvider;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE, skipRestart = false)
@SetDefaultProvider(spi = UserProfileSpi.ID, providerId = DeclarativeUserProfileProvider.ID,
beforeEnableFeature = false,
onlyUpdateDefault = true
)
@AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE) @AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE)
public class UserProfileAdminTest extends AbstractAdminTest { public class UserProfileAdminTest extends AbstractAdminTest {
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
} }
@Test @Test
public void testDefaultConfigIfNoneSet() { public void testDefaultConfigIfNoneSet() {
String defaultRawConfig; assertEquals(readDefaultConfig(), testRealm().users().userProfile().getConfiguration());
try (InputStream is = DeclarativeUserProfileProvider.class.getResourceAsStream(DeclarativeUserProfileProvider.SYSTEM_DEFAULT_CONFIG_RESOURCE)) {
defaultRawConfig = StreamUtil.readString(is, Charset.defaultCharset());
} catch (IOException cause) {
throw new RuntimeException("Failed to load default user profile config file", cause);
}
assertEquals(defaultRawConfig, testRealm().users().userProfile().getConfiguration());
} }
@Test @Test

View file

@ -39,6 +39,10 @@ import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.util.*; import org.keycloak.testsuite.util.*;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.jgroups.util.Util.assertTrue; import static org.jgroups.util.Util.assertTrue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
@ -261,10 +265,20 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
registerPage.assertCurrent(); registerPage.assertCurrent();
assertEquals("Please specify username.", registerPage.getInputAccountErrors().getUsernameError()); assertEquals("Please specify username.", registerPage.getInputAccountErrors().getUsernameError());
assertEquals("Please specify first name.", registerPage.getInputAccountErrors().getFirstNameError()); assertThat(registerPage.getInputAccountErrors().getFirstNameError(), anyOf(
assertEquals("Please specify last name.", registerPage.getInputAccountErrors().getLastNameError()); containsString("Please specify first name"),
assertEquals("Please specify email.", registerPage.getInputAccountErrors().getEmailError()); containsString("Please specify this field")
assertEquals("Please specify password.", registerPage.getInputPasswordErrors().getPasswordError()); ));
assertThat(registerPage.getInputAccountErrors().getLastNameError(), anyOf(
containsString("Please specify last name"),
containsString("Please specify this field")
));
assertThat(registerPage.getInputAccountErrors().getEmailError(), anyOf(
containsString("Please specify email"),
containsString("Please specify this field")
));
assertThat(registerPage.getInputPasswordErrors().getPasswordError(), is("Please specify password."));
events.expectRegister(null, "registerUserMissingUsername@email") events.expectRegister(null, "registerUserMissingUsername@email")
.removeDetail(Details.USERNAME) .removeDetail(Details.USERNAME)

View file

@ -17,9 +17,11 @@
package org.keycloak.testsuite.forms; package org.keycloak.testsuite.forms;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -29,15 +31,12 @@ import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
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.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage; import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
@ -47,19 +46,12 @@ import org.keycloak.testsuite.pages.VerifyEmailPage;
import org.keycloak.testsuite.util.ClientScopeBuilder; import org.keycloak.testsuite.util.ClientScopeBuilder;
import org.keycloak.testsuite.util.GreenMailRule; import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.KeycloakModelUtils; import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.userprofile.UserProfileSpi;
import org.keycloak.userprofile.config.DeclarativeUserProfileProvider;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE, skipRestart = false)
@SetDefaultProvider(spi = UserProfileSpi.ID, providerId = DeclarativeUserProfileProvider.ID,
beforeEnableFeature = false,
onlyUpdateDefault = true
)
@AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE) @AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE)
public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest { public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
@ -103,18 +95,21 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
List<String> scopes = new ArrayList<>(); List<String> scopes = new ArrayList<>();
scopes.add(SCOPE_LAST_NAME); scopes.add(SCOPE_LAST_NAME);
scopes.add(VerifyProfileTest.SCOPE_DEPARTMENT); scopes.add(VerifyProfileTest.SCOPE_DEPARTMENT);
client_scope_default = KeycloakModelUtils.createClient(testRealm, "client-a"); client_scope_default = KeycloakModelUtils.createClient(testRealm, "client-a");
client_scope_default.setDefaultClientScopes(scopes); client_scope_default.setDefaultClientScopes(scopes);
client_scope_default.setRedirectUris(Collections.singletonList("*")); client_scope_default.setRedirectUris(Collections.singletonList("*"));
client_scope_optional = KeycloakModelUtils.createClient(testRealm, "client-b"); client_scope_optional = KeycloakModelUtils.createClient(testRealm, "client-b");
client_scope_optional.setOptionalClientScopes(scopes); client_scope_optional.setOptionalClientScopes(scopes);
client_scope_optional.setRedirectUris(Collections.singletonList("*")); client_scope_optional.setRedirectUris(Collections.singletonList("*"));
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
} }
@Test @Test
public void testRregisterUserSuccess_lastNameOptional() { public void testRegisterUserSuccess_lastNameOptional() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ UP_CONFIG_BASIC_ATTRIBUTES + UP_CONFIG_BASIC_ATTRIBUTES
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
@ -248,14 +243,14 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
events.expectRegister("registeruserinvalidlastnamelength", "registerUserInvalidLastNameLength@email") events.expectRegister("registeruserinvalidlastnamelength", "registerUserInvalidLastNameLength@email")
.error("invalid_registration").assertEvent(); .error("invalid_registration").assertEvent();
} }
@Test @Test
public void testAttributeDisplayName() { public void testAttributeDisplayName() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "}," + "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", \"displayName\" : \"Department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}}" + "{\"name\": \"department\", \"displayName\" : \"Department\", " + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}}"
+ "]}"); + "]}");
loginPage.open(); loginPage.open();
@ -271,50 +266,50 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
// direct value in display name // direct value in display name
Assert.assertEquals("Department",registerPage.getLabelForField("department")); Assert.assertEquals("Department",registerPage.getLabelForField("department"));
} }
@Test @Test
public void testRegisterUserSuccess_requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration() { public void testRegisterUserSuccess_requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\",\"displayName\":\"${firstName}\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "}," + "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\", \"displayName\" : \"Department\", " + VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}" + "{\"name\": \"department\", \"displayName\" : \"Department\", " + VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE + ", \"required\":{}}"
+ "]}"); + "]}");
loginPage.open(); loginPage.open();
loginPage.clickRegister(); loginPage.clickRegister();
registerPage.assertCurrent(); registerPage.assertCurrent();
Assert.assertFalse(registerPage.isDepartmentPresent()); Assert.assertFalse(registerPage.isDepartmentPresent());
registerPage.register("FirstName", "LastName", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration@email", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration", "password", "password"); registerPage.register("FirstName", "LastName", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration@email", "requiredReadOnlyAttributeNotRenderedAndNotBlockingRegistration", "password", "password");
appPage.assertCurrent(); appPage.assertCurrent();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
} }
@Test @Test
public void testRegisterUserSuccess_attributeRequiredAndSelectedByScopeMustBeSet() { public void testRegisterUserSuccess_attributeRequiredAndSelectedByScopeMustBeSet() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "}," + "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + "},"
+ "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}" + "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}"
+ "]}"); + "]}");
oauth.scope(VerifyProfileTest.SCOPE_DEPARTMENT).clientId(client_scope_optional.getClientId()).openLoginForm(); oauth.scope(VerifyProfileTest.SCOPE_DEPARTMENT).clientId(client_scope_optional.getClientId()).openLoginForm();
loginPage.clickRegister(); loginPage.clickRegister();
registerPage.assertCurrent(); registerPage.assertCurrent();
//check required validation works //check required validation works
registerPage.register("FirstAA", "LastAA", "attributeRequiredAndSelectedByScopeMustBeSet@email", "attributeRequiredAndSelectedByScopeMustBeSet", "password", "password", ""); registerPage.register("FirstAA", "LastAA", "attributeRequiredAndSelectedByScopeMustBeSet@email", "attributeRequiredAndSelectedByScopeMustBeSet", "password", "password", "");
registerPage.assertCurrent(); registerPage.assertCurrent();
registerPage.register("FirstAA", "LastAA", "attributeRequiredAndSelectedByScopeMustBeSet@email", "attributeRequiredAndSelectedByScopeMustBeSet", "password", "password", "DepartmentAA"); registerPage.register("FirstAA", "LastAA", "attributeRequiredAndSelectedByScopeMustBeSet@email", "attributeRequiredAndSelectedByScopeMustBeSet", "password", "password", "DepartmentAA");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
@ -327,23 +322,23 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
@Test @Test
public void testRegisterUserSuccess_attributeNotRequiredAndSelectedByScopeCanBeIgnored() { public void testRegisterUserSuccess_attributeNotRequiredAndSelectedByScopeCanBeIgnored() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}" + "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}"
+ "]}"); + "]}");
oauth.scope(VerifyProfileTest.SCOPE_DEPARTMENT).clientId(client_scope_optional.getClientId()).openLoginForm(); oauth.scope(VerifyProfileTest.SCOPE_DEPARTMENT).clientId(client_scope_optional.getClientId()).openLoginForm();
loginPage.clickRegister(); loginPage.clickRegister();
registerPage.assertCurrent(); registerPage.assertCurrent();
Assert.assertTrue(registerPage.isDepartmentPresent()); Assert.assertTrue(registerPage.isDepartmentPresent());
registerPage.register("FirstAA", "LastAA", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "password", "password", null); registerPage.register("FirstAA", "LastAA", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email", "attributeNotRequiredAndSelectedByScopeCanBeIgnored", "password", "password", null);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
String userId = events.expectRegister("attributeNotRequiredAndSelectedByScopeCanBeIgnored", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email",client_scope_optional.getClientId()).assertEvent().getUserId(); String userId = events.expectRegister("attributeNotRequiredAndSelectedByScopeCanBeIgnored", "attributeNotRequiredAndSelectedByScopeCanBeIgnored@email",client_scope_optional.getClientId()).assertEvent().getUserId();
UserRepresentation user = getUser(userId); UserRepresentation user = getUser(userId);
assertEquals("FirstAA", user.getFirstName()); assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName()); assertEquals("LastAA", user.getLastName());
@ -353,23 +348,23 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
@Test @Test
public void testRegisterUserSuccess_attributeNotRequiredAndSelectedByScopeCanBeSet() { public void testRegisterUserSuccess_attributeNotRequiredAndSelectedByScopeCanBeSet() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}" + "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}"
+ "]}"); + "]}");
oauth.clientId(client_scope_default.getClientId()).openLoginForm(); oauth.clientId(client_scope_default.getClientId()).openLoginForm();
loginPage.clickRegister(); loginPage.clickRegister();
registerPage.assertCurrent(); registerPage.assertCurrent();
Assert.assertTrue(registerPage.isDepartmentPresent()); Assert.assertTrue(registerPage.isDepartmentPresent());
registerPage.register("FirstAA", "LastAA", "attributeNotRequiredAndSelectedByScopeCanBeSet@email", "attributeNotRequiredAndSelectedByScopeCanBeSet", "password", "password", "Department AA"); registerPage.register("FirstAA", "LastAA", "attributeNotRequiredAndSelectedByScopeCanBeSet@email", "attributeNotRequiredAndSelectedByScopeCanBeSet", "password", "password", "Department AA");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
String userId = events.expectRegister("attributeNotRequiredAndSelectedByScopeCanBeSet", "attributeNotRequiredAndSelectedByScopeCanBeSet@email",client_scope_default.getClientId()).assertEvent().getUserId(); String userId = events.expectRegister("attributeNotRequiredAndSelectedByScopeCanBeSet", "attributeNotRequiredAndSelectedByScopeCanBeSet@email",client_scope_default.getClientId()).assertEvent().getUserId();
UserRepresentation user = getUser(userId); UserRepresentation user = getUser(userId);
assertEquals("FirstAA", user.getFirstName()); assertEquals("FirstAA", user.getFirstName());
assertEquals("LastAA", user.getLastName()); assertEquals("LastAA", user.getLastName());
@ -379,19 +374,19 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
@Test @Test
public void testRegisterUserSuccess_attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration() { public void testRegisterUserSuccess_attributeRequiredButNotSelectedByScopeIsNotRenderedAndNotBlockingRegistration() {
setUserProfileConfiguration("{\"attributes\": [" setUserProfileConfiguration("{\"attributes\": ["
+ "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"firstName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}}," + "{\"name\": \"lastName\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\": {}},"
+ "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}" + "{\"name\": \"department\"," + VerifyProfileTest.PERMISSIONS_ALL + ", \"required\":{}, \"selector\":{\"scopes\":[\""+VerifyProfileTest.SCOPE_DEPARTMENT+"\"]}}"
+ "]}"); + "]}");
oauth.clientId(client_scope_optional.getClientId()).openLoginForm(); oauth.clientId(client_scope_optional.getClientId()).openLoginForm();
loginPage.clickRegister(); loginPage.clickRegister();
registerPage.assertCurrent(); registerPage.assertCurrent();
Assert.assertFalse(registerPage.isDepartmentPresent()); Assert.assertFalse(registerPage.isDepartmentPresent());
registerPage.register("FirstAA", "LastAA", "attributeRequiredButNotSelectedByScopeIsNotRendered@email", "attributeRequiredButNotSelectedByScopeIsNotRendered", "password", "password"); registerPage.register("FirstAA", "LastAA", "attributeRequiredButNotSelectedByScopeIsNotRendered@email", "attributeRequiredButNotSelectedByScopeIsNotRendered", "password", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
@ -402,7 +397,7 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
assertEquals(null, user.firstAttribute(VerifyProfileTest.ATTRIBUTE_DEPARTMENT)); assertEquals(null, user.firstAttribute(VerifyProfileTest.ATTRIBUTE_DEPARTMENT));
} }
private void assertUserRegistered(String userId, String username, String email, String firstName, String lastName) { private void assertUserRegistered(String userId, String username, String email, String firstName, String lastName) {
events.expectLogin().detail("username", username.toLowerCase()).user(userId).assertEvent(); events.expectLogin().detail("username", username.toLowerCase()).user(userId).assertEvent();
@ -421,7 +416,7 @@ public class RegisterWithUserProfileTest extends AbstractTestRealmKeycloakTest {
protected UserRepresentation getUser(String userId) { protected UserRepresentation getUser(String userId) {
return testRealm().users().get(userId).toRepresentation(); return testRealm().users().get(userId).toRepresentation();
} }
protected UserRepresentation getUserByUsername(String username) { protected UserRepresentation getUserByUsername(String username) {
List<UserRepresentation> users = testRealm().users().search(username); List<UserRepresentation> users = testRealm().users().search(username);
if(users!=null && !users.isEmpty()) if(users!=null && !users.isEmpty())

View file

@ -0,0 +1,24 @@
package org.keycloak.testsuite.forms;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import java.util.HashMap;
import org.keycloak.representations.idm.RealmRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class UserProfileRegisterTest extends RegisterTest {
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
}
}

View file

@ -19,9 +19,11 @@ package org.keycloak.testsuite.forms;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -33,7 +35,6 @@ import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.common.Profile;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
@ -43,8 +44,6 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
@ -54,16 +53,10 @@ import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
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.UserProfileSpi;
import org.keycloak.userprofile.config.DeclarativeUserProfileProvider;
/** /**
* @author Vlastimil Elias <velias@redhat.com> * @author Vlastimil Elias <velias@redhat.com>
*/ */
@EnableFeature(value = Profile.Feature.DECLARATIVE_USER_PROFILE, skipRestart = false)
@SetDefaultProvider(spi = UserProfileSpi.ID, providerId = DeclarativeUserProfileProvider.ID,
beforeEnableFeature = false,
onlyUpdateDefault = true)
@AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE) @AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE)
public class VerifyProfileTest extends AbstractTestRealmKeycloakTest { public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
@ -135,6 +128,10 @@ public class VerifyProfileTest extends AbstractTestRealmKeycloakTest {
client_scope_optional = KeycloakModelUtils.createClient(testRealm, "client-b"); client_scope_optional = KeycloakModelUtils.createClient(testRealm, "client-b");
client_scope_optional.setOptionalClientScopes(Collections.singletonList(SCOPE_DEPARTMENT)); client_scope_optional.setOptionalClientScopes(Collections.singletonList(SCOPE_DEPARTMENT));
client_scope_optional.setRedirectUris(Collections.singletonList("*")); client_scope_optional.setRedirectUris(Collections.singletonList("*"));
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
} }
@Rule @Rule

View file

@ -19,6 +19,9 @@
package org.keycloak.testsuite.user.profile; package org.keycloak.testsuite.user.profile;
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.REALM_USER_PROFILE_ENABLED;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -27,10 +30,11 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.userprofile.config.DeclarativeUserProfileProvider; import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.UserProfileProvider; import org.keycloak.userprofile.UserProfileProvider;
/** /**
@ -233,4 +237,12 @@ public abstract class AbstractUserProfileTest extends AbstractTestRealmKeycloakT
} }
}; };
} }
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
if (testRealm.getAttributes() == null) {
testRealm.setAttributes(new HashMap<>());
}
testRealm.getAttributes().put(REALM_USER_PROFILE_ENABLED, Boolean.TRUE.toString());
}
} }

View file

@ -43,10 +43,8 @@ import java.util.function.Consumer;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException; import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -54,11 +52,8 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
import org.keycloak.testsuite.runonserver.RunOnServer; import org.keycloak.testsuite.runonserver.RunOnServer;
import org.keycloak.userprofile.UserProfileSpi; import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.config.DeclarativeUserProfileProvider;
import org.keycloak.userprofile.config.UPAttribute; import org.keycloak.userprofile.config.UPAttribute;
import org.keycloak.userprofile.config.UPAttributePermissions; import org.keycloak.userprofile.config.UPAttributePermissions;
import org.keycloak.userprofile.config.UPAttributeRequired; import org.keycloak.userprofile.config.UPAttributeRequired;
@ -80,15 +75,12 @@ import org.keycloak.validate.validators.LengthValidator;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@EnableFeature(Profile.Feature.DECLARATIVE_USER_PROFILE)
@SetDefaultProvider(spi = UserProfileSpi.ID, providerId = DeclarativeUserProfileProvider.ID,
beforeEnableFeature = false,
onlyUpdateDefault = true)
@AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE) @AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE)
public class UserProfileTest extends AbstractUserProfileTest { public class UserProfileTest extends AbstractUserProfileTest {
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
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());

View file

@ -220,10 +220,6 @@
"userProfile": { "userProfile": {
"provider": "${keycloak.userProfile.provider:}", "provider": "${keycloak.userProfile.provider:}",
"legacy-user-profile": {
"read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ],
"admin-read-only-attributes": [ "deniedSomeAdmin" ]
},
"declarative-user-profile": { "declarative-user-profile": {
"read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ], "read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ],
"admin-read-only-attributes": [ "deniedSomeAdmin" ] "admin-read-only-attributes": [ "deniedSomeAdmin" ]

View file

@ -140,10 +140,6 @@
"userProfile": { "userProfile": {
"provider": "${keycloak.userProfile.provider:}", "provider": "${keycloak.userProfile.provider:}",
"legacy-user-profile": {
"read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ],
"admin-read-only-attributes": [ "deniedSomeAdmin" ]
},
"declarative-user-profile": { "declarative-user-profile": {
"read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ], "read-only-attributes": [ "deniedFoo", "deniedBar*", "deniedSome/thing", "deniedsome*thing" ],
"admin-read-only-attributes": [ "deniedSomeAdmin" ] "admin-read-only-attributes": [ "deniedSomeAdmin" ]

View file

@ -32,6 +32,8 @@ realm-detail.protocol-endpoints.tooltip=Shows the configuration of the protocol
realm-detail.protocol-endpoints.oidc=OpenID Endpoint Configuration realm-detail.protocol-endpoints.oidc=OpenID Endpoint Configuration
realm-detail.protocol-endpoints.saml=SAML 2.0 Identity Provider Metadata realm-detail.protocol-endpoints.saml=SAML 2.0 Identity Provider Metadata
realm-detail.userManagedAccess.tooltip=If enabled, users are allowed to manage their resources and permissions using the Account Management Console. realm-detail.userManagedAccess.tooltip=If enabled, users are allowed to manage their resources and permissions using the Account Management Console.
userProfileEnabled=User Profile Enabled
userProfileEnabled.tooltip=If enabled, allows managing user profiles.
userManagedAccess=User-Managed Access userManagedAccess=User-Managed Access
registrationAllowed=User registration registrationAllowed=User registration
registrationAllowed.tooltip=Enable/disable the registration page. A link for registration will show on login page too. registrationAllowed.tooltip=Enable/disable the registration page. A link for registration will show on login page too.

View file

@ -280,8 +280,10 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
} }
} }
$scope.realm = angular.copy(realm); $scope.realm = angular.copy(realm);
$scope.realm.attributes['userProfileEnabled'] = $scope.realm.attributes['userProfileEnabled'] == 'true';
var oldCopy = angular.copy($scope.realm); var oldCopy = angular.copy($scope.realm);
$scope.realmCopy = oldCopy;
$scope.changed = $scope.create; $scope.changed = $scope.create;
@ -309,6 +311,7 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
if (Current.realms[i].realm == realmCopy.realm) { if (Current.realms[i].realm == realmCopy.realm) {
Current.realm = Current.realms[i]; Current.realm = Current.realms[i];
oldCopy = angular.copy($scope.realm); oldCopy = angular.copy($scope.realm);
$scope.realmCopy = oldCopy;
} }
} }
}); });

View file

@ -55,6 +55,14 @@
<kc-tooltip>{{:: 'realm-detail.userManagedAccess.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'realm-detail.userManagedAccess.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label" for="userProfileEnabled">{{:: 'userProfileEnabled' | translate}}</label>
<div class="col-md-6">
<input ng-model="realm.attributes['userProfileEnabled']" name="userProfileEnabled" id="userProfileEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'userProfileEnabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label">{{:: 'endpoints' | translate}}</label> <label class="col-md-2 control-label">{{:: 'endpoints' | translate}}</label>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -19,6 +19,6 @@
<a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'realm-tab-client-policies' | translate}}</a> <a href="#/realms/{{realm.realm}}/client-policies/profiles">{{:: 'realm-tab-client-policies' | translate}}</a>
</li> </li>
<li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'realm-tab-security-defenses' | translate}}</a></li> <li ng-class="{active: path[2] == 'defense'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/defense/headers">{{:: 'realm-tab-security-defenses' | translate}}</a></li>
<li ng-class="{active: path[2] == 'user-profile'}" data-ng-show="access.viewRealm && serverInfo.featureEnabled('DECLARATIVE_USER_PROFILE')"><a href="#/realms/{{realm.realm}}/user-profile">{{:: 'realm-tab-user-profile' | translate}}</a></li> <li ng-class="{active: path[2] == 'user-profile'}" data-ng-show="access.viewRealm && (realm.attributes['userProfileEnabled'] == true || realm.attributes['userProfileEnabled'] == 'true')"><a href="#/realms/{{realm.realm}}/user-profile">{{:: 'realm-tab-user-profile' | translate}}</a></li>
</ul> </ul>
</div> </div>