More info on UserProfileContext
closes #25691 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
810ebf4efd
commit
eb184a8554
10 changed files with 114 additions and 72 deletions
|
@ -95,7 +95,6 @@ import org.keycloak.storage.user.UserRegistrationProvider;
|
|||
import org.keycloak.userprofile.AttributeContext;
|
||||
import org.keycloak.userprofile.AttributeGroupMetadata;
|
||||
import org.keycloak.userprofile.AttributeMetadata;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileDecorator;
|
||||
import org.keycloak.userprofile.UserProfileMetadata;
|
||||
import org.keycloak.userprofile.UserProfileUtil;
|
||||
|
@ -1085,7 +1084,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
}
|
||||
});
|
||||
|
||||
Predicate<AttributeContext> onlyAdminCondition = context -> metadata.getContext() == UserProfileContext.USER_API;
|
||||
Predicate<AttributeContext> onlyAdminCondition = context -> metadata.getContext().isAdminContext();
|
||||
|
||||
int guiOrder = (int) metadata.getAttributes().stream()
|
||||
.map(AttributeMetadata::getName)
|
||||
|
|
|
@ -35,7 +35,6 @@ import org.keycloak.storage.user.ImportedUserValidation;
|
|||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.userprofile.AttributeContext;
|
||||
import org.keycloak.userprofile.AttributeMetadata;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileDecorator;
|
||||
import org.keycloak.userprofile.UserProfileMetadata;
|
||||
|
||||
|
@ -228,7 +227,7 @@ public class SSSDFederationProvider implements UserStorageProvider,
|
|||
attributeContext.getUser() != null && model.getId().equals(attributeContext.getUser().getFederationLink());
|
||||
|
||||
// condition to view only by admin
|
||||
Predicate<AttributeContext> onlyAdminCondition = context -> metadata.getContext() == UserProfileContext.USER_API;
|
||||
Predicate<AttributeContext> onlyAdminCondition = context -> metadata.getContext().isAdminContext();
|
||||
|
||||
// guiOrder if new attributes are needed
|
||||
int guiOrder = (int) metadata.getAttributes().stream()
|
||||
|
|
|
@ -122,7 +122,7 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
|||
case ENABLED:
|
||||
return true;
|
||||
case ADMIN_EDIT:
|
||||
return UserProfileContext.USER_API.equals(context);
|
||||
return context.isAdminContext();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -436,7 +436,7 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
|||
case ADMIN_EDIT:
|
||||
case ADMIN_VIEW:
|
||||
// unmanaged attributes only available through the admin context
|
||||
return UserProfileContext.USER_API.equals(context);
|
||||
return context.isAdminContext();
|
||||
}
|
||||
|
||||
// allow unmanaged attributes if enabled to all contexts
|
||||
|
@ -524,13 +524,13 @@ public class DefaultAttributes extends HashMap<String, List<String>> implements
|
|||
@Override
|
||||
public boolean canView(AttributeContext context) {
|
||||
return canEdit(context)
|
||||
|| (UnmanagedAttributePolicy.ADMIN_VIEW.equals(unmanagedAttributePolicy) && UserProfileContext.USER_API.equals(context.getContext()));
|
||||
|| (UnmanagedAttributePolicy.ADMIN_VIEW.equals(unmanagedAttributePolicy) && context.getContext().isAdminContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEdit(AttributeContext context) {
|
||||
return UnmanagedAttributePolicy.ENABLED.equals(unmanagedAttributePolicy)
|
||||
|| (UnmanagedAttributePolicy.ADMIN_EDIT.equals(unmanagedAttributePolicy) && UserProfileContext.USER_API.equals(context.getContext()));
|
||||
|| (UnmanagedAttributePolicy.ADMIN_EDIT.equals(unmanagedAttributePolicy) && context.getContext().isAdminContext());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -257,7 +257,7 @@ public final class DefaultUserProfile implements UserProfile {
|
|||
UserProfileContext context = metadata.getContext();
|
||||
R rep;
|
||||
|
||||
if (UserProfileContext.USER_API.equals(context)) {
|
||||
if (context.isAdminContext()) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
rep = (R) ModelToRepresentation.toRepresentation(session, realm, user);
|
||||
} else {
|
||||
|
|
|
@ -81,7 +81,7 @@ public class UserProfileUtil {
|
|||
return false;
|
||||
} else {
|
||||
logger.tracef("Adding metadata attribute '%s' to user profile by user storage provider '%s' for user profile context '%s'.", attrName, storageProviderName, metadata.getContext().toString());
|
||||
Predicate<AttributeContext> onlyAdminCondition = context -> metadata.getContext() == UserProfileContext.USER_API;
|
||||
Predicate<AttributeContext> onlyAdminCondition = context -> metadata.getContext().isAdminContext();
|
||||
AttributeMetadata attributeMetadata = metadata.addAttribute(attrName, guiOrder, Collections.emptyList())
|
||||
.addWriteCondition(AttributeMetadata.ALWAYS_FALSE) // Not writable for anyone
|
||||
.addReadCondition(onlyAdminCondition) // Read-only for administrators
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2023 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;
|
||||
|
||||
/**
|
||||
* Constants related to user profile
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserProfileConstants {
|
||||
|
||||
public static final String ROLE_USER = "user";
|
||||
public static final String ROLE_ADMIN = "admin";
|
||||
}
|
|
@ -19,6 +19,11 @@
|
|||
|
||||
package org.keycloak.userprofile;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.userprofile.UserProfileConstants.ROLE_ADMIN;
|
||||
import static org.keycloak.userprofile.UserProfileConstants.ROLE_USER;
|
||||
|
||||
/**
|
||||
* <p>This interface represents the different contexts from where user profiles are managed. The core contexts are already
|
||||
* available here representing the different areas in Keycloak where user profiles are managed.
|
||||
|
@ -33,39 +38,57 @@ public enum UserProfileContext {
|
|||
/**
|
||||
* In this context, a user profile is managed by themselves during an authentication flow such as when updating the user profile.
|
||||
*/
|
||||
UPDATE_PROFILE(true),
|
||||
UPDATE_PROFILE(false, true, true),
|
||||
|
||||
/**
|
||||
* In this context, a user profile is managed through the management interface such as the Admin API.
|
||||
*/
|
||||
USER_API(false),
|
||||
USER_API(true, false, false),
|
||||
|
||||
/**
|
||||
* In this context, a user profile is managed by themselves through the account console.
|
||||
*/
|
||||
ACCOUNT(true),
|
||||
ACCOUNT(false, false, true),
|
||||
|
||||
/**
|
||||
* In this context, a user profile is managed by themselves when authenticating through a broker.
|
||||
*/
|
||||
IDP_REVIEW(false),
|
||||
IDP_REVIEW(false, true, false),
|
||||
|
||||
/**
|
||||
* In this context, a user profile is managed by themselves when registering to a realm.
|
||||
*/
|
||||
REGISTRATION(false),
|
||||
REGISTRATION(false, true, false),
|
||||
|
||||
/**
|
||||
* In this context, a user profile is managed by themselves when updating their email through an application initiated action.
|
||||
*/
|
||||
UPDATE_EMAIL(false);
|
||||
UPDATE_EMAIL(false, true, false);
|
||||
|
||||
private boolean resetEmailVerified;
|
||||
private final boolean resetEmailVerified;
|
||||
private final boolean adminContext;
|
||||
private final boolean authFlowContext;
|
||||
|
||||
UserProfileContext(boolean resetEmailVerified){
|
||||
UserProfileContext(boolean adminContext, boolean authFlowContext, boolean resetEmailVerified){
|
||||
this.adminContext = adminContext;
|
||||
this.authFlowContext = authFlowContext;
|
||||
this.resetEmailVerified = resetEmailVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true means that this context is applicable to administrators. False means that this context is applicable to regular users
|
||||
*/
|
||||
public boolean isAdminContext() {
|
||||
return adminContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if context CAN BE part of the authentication flow
|
||||
*/
|
||||
public boolean canBeAuthFlowContext() {
|
||||
return authFlowContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true means that UserModel.emailVerified flag must be reset to false in this context when email address is updated
|
||||
*/
|
||||
|
@ -73,4 +96,20 @@ public enum UserProfileContext {
|
|||
return resetEmailVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if roles configuration contains role for this context.
|
||||
*
|
||||
* @param roles to be inspected
|
||||
* @return true if roles list contains role representing checked context
|
||||
*/
|
||||
public boolean isRoleForContext(Set<String> roles) {
|
||||
if (roles == null)
|
||||
return false;
|
||||
return roles.contains(getContextRole());
|
||||
}
|
||||
|
||||
private String getContextRole() {
|
||||
return isAdminContext() ? ROLE_ADMIN : ROLE_USER;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -294,20 +294,20 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
|||
|
||||
Predicate<AttributeContext> required = AttributeMetadata.ALWAYS_FALSE;
|
||||
if (rc != null) {
|
||||
if (rc.isAlways() || UPConfigUtils.isRoleForContext(context, rc.getRoles())) {
|
||||
if (rc.isAlways() || context.isRoleForContext(rc.getRoles())) {
|
||||
required = AttributeMetadata.ALWAYS_TRUE;
|
||||
|
||||
// If scopes are configured, we will use scope-based selector and require the attribute just if scope is
|
||||
// in current authenticationSession (either default scope or by 'scope' parameter)
|
||||
if (rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||
if (UPConfigUtils.canBeAuthFlowContext(context)) {
|
||||
if (context.canBeAuthFlowContext()) {
|
||||
required = (c) -> requestedScopePredicate(c, rc.getScopes());
|
||||
} else {
|
||||
// Scopes not available for admin and account contexts
|
||||
required = AttributeMetadata.ALWAYS_FALSE;
|
||||
}
|
||||
}
|
||||
} else if (UPConfigUtils.canBeAuthFlowContext(context) && rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||
} else if (context.canBeAuthFlowContext() && rc.getScopes() != null && !rc.getScopes().isEmpty()) {
|
||||
// for contexts executed from auth flow and with configured scopes requirement
|
||||
// we have to create required validation with scopes based selector
|
||||
required = (c) -> requestedScopePredicate(c, rc.getScopes());
|
||||
|
@ -322,7 +322,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
|||
Set<String> editRoles = permissions.getEdit();
|
||||
|
||||
if (!editRoles.isEmpty()) {
|
||||
writeAllowed = ac -> UPConfigUtils.isRoleForContext(ac.getContext(), editRoles);
|
||||
writeAllowed = ac -> ac.getContext().isRoleForContext(editRoles);
|
||||
}
|
||||
|
||||
Set<String> viewRoles = permissions.getView();
|
||||
|
@ -336,7 +336,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
|||
|
||||
Predicate<AttributeContext> selector = AttributeMetadata.ALWAYS_TRUE;
|
||||
UPAttributeSelector sc = attrConfig.getSelector();
|
||||
if (sc != null && !isBuiltInAttribute(attributeName) && UPConfigUtils.canBeAuthFlowContext(context) && sc.getScopes() != null && !sc.getScopes().isEmpty()) {
|
||||
if (sc != null && !isBuiltInAttribute(attributeName) && context.canBeAuthFlowContext() && sc.getScopes() != null && !sc.getScopes().isEmpty()) {
|
||||
// for contexts executed from auth flow and with configured scopes selector
|
||||
// we have to create correct predicate
|
||||
selector = (c) -> requestedScopePredicate(c, sc.getScopes());
|
||||
|
@ -368,7 +368,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
|||
}
|
||||
|
||||
if (UserModel.EMAIL.equals(attributeName)) {
|
||||
if (UserProfileContext.USER_API.equals(context)) {
|
||||
if (context.isAdminContext()) {
|
||||
required = new Predicate<AttributeContext>() {
|
||||
@Override
|
||||
public boolean test(AttributeContext context) {
|
||||
|
@ -447,7 +447,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
|||
|
||||
private Predicate<AttributeContext> createViewAllowedPredicate(Predicate<AttributeContext> canEdit,
|
||||
Set<String> viewRoles) {
|
||||
return ac -> UPConfigUtils.isRoleForContext(ac.getContext(), viewRoles) || canEdit.test(ac);
|
||||
return ac -> ac.getContext().isRoleForContext(viewRoles) || canEdit.test(ac);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -40,7 +40,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||
import org.keycloak.userprofile.UserProfileContext;
|
||||
import org.keycloak.userprofile.UserProfileConstants;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.validate.ValidationResult;
|
||||
import org.keycloak.validate.ValidatorConfig;
|
||||
|
@ -55,8 +55,8 @@ import org.keycloak.validate.Validators;
|
|||
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_ADMIN = "admin";
|
||||
public static final String ROLE_USER = UserProfileConstants.ROLE_USER;
|
||||
public static final String ROLE_ADMIN = UserProfileConstants.ROLE_ADMIN;
|
||||
|
||||
private static final Set<String> PSEUDOROLES = new HashSet<>();
|
||||
|
||||
|
@ -287,32 +287,6 @@ public class UPConfigUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if context CAN BE part of the AuthenticationFlow.
|
||||
*
|
||||
* @param context to check
|
||||
* @return true if context CAN BE part of the auth flow
|
||||
*/
|
||||
public static boolean canBeAuthFlowContext(UserProfileContext context) {
|
||||
return context != UserProfileContext.USER_API && context != UserProfileContext.ACCOUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if roles configuration contains role given current context.
|
||||
*
|
||||
* @param context to be checked
|
||||
* @param roles to be inspected
|
||||
* @return true if roles list contains role representing checked context
|
||||
*/
|
||||
public static boolean isRoleForContext(UserProfileContext context, Set<String> roles) {
|
||||
if (roles == null)
|
||||
return false;
|
||||
if (context == UserProfileContext.USER_API)
|
||||
return roles.contains(ROLE_ADMIN);
|
||||
else
|
||||
return roles.contains(ROLE_USER);
|
||||
}
|
||||
|
||||
public static String capitalizeFirstLetter(String str) {
|
||||
if (str == null || str.isEmpty())
|
||||
return str;
|
||||
|
|
|
@ -37,38 +37,38 @@ public class UPConfigUtilsTest {
|
|||
|
||||
@Test
|
||||
public void canBeAuthFlowContext() {
|
||||
Assert.assertFalse(UPConfigUtils.canBeAuthFlowContext(UserProfileContext.ACCOUNT));
|
||||
Assert.assertFalse(UPConfigUtils.canBeAuthFlowContext(UserProfileContext.USER_API));
|
||||
Assert.assertFalse(UserProfileContext.ACCOUNT.canBeAuthFlowContext());
|
||||
Assert.assertFalse(UserProfileContext.USER_API.canBeAuthFlowContext());
|
||||
|
||||
Assert.assertTrue(UPConfigUtils.canBeAuthFlowContext(UserProfileContext.IDP_REVIEW));
|
||||
Assert.assertTrue(UPConfigUtils.canBeAuthFlowContext(UserProfileContext.REGISTRATION));
|
||||
Assert.assertTrue(UPConfigUtils.canBeAuthFlowContext(UserProfileContext.UPDATE_PROFILE));
|
||||
Assert.assertTrue(UserProfileContext.IDP_REVIEW.canBeAuthFlowContext());
|
||||
Assert.assertTrue(UserProfileContext.REGISTRATION.canBeAuthFlowContext());
|
||||
Assert.assertTrue(UserProfileContext.UPDATE_PROFILE.canBeAuthFlowContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isRoleForContext() {
|
||||
|
||||
Assert.assertFalse(UPConfigUtils.isRoleForContext(UserProfileContext.ACCOUNT, null));
|
||||
Assert.assertFalse(UserProfileContext.ACCOUNT.isRoleForContext( null));
|
||||
|
||||
Set<String> roles = new HashSet<>();
|
||||
roles.add(ROLE_ADMIN);
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.USER_API, roles));
|
||||
Assert.assertFalse(UPConfigUtils.isRoleForContext(UserProfileContext.ACCOUNT, roles));
|
||||
Assert.assertFalse(UPConfigUtils.isRoleForContext(UserProfileContext.UPDATE_PROFILE, roles));
|
||||
Assert.assertTrue(UserProfileContext.USER_API.isRoleForContext(roles));
|
||||
Assert.assertFalse(UserProfileContext.ACCOUNT.isRoleForContext(roles));
|
||||
Assert.assertFalse(UserProfileContext.UPDATE_PROFILE.isRoleForContext(roles));
|
||||
|
||||
roles = new HashSet<>();
|
||||
roles.add(ROLE_USER);
|
||||
Assert.assertFalse(UPConfigUtils.isRoleForContext(UserProfileContext.USER_API, roles));
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.ACCOUNT, roles));
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.IDP_REVIEW, roles));
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.REGISTRATION, roles));
|
||||
Assert.assertFalse(UserProfileContext.USER_API.isRoleForContext(roles));
|
||||
Assert.assertTrue(UserProfileContext.ACCOUNT.isRoleForContext(roles));
|
||||
Assert.assertTrue(UserProfileContext.IDP_REVIEW.isRoleForContext(roles));
|
||||
Assert.assertTrue(UserProfileContext.REGISTRATION.isRoleForContext(roles));
|
||||
|
||||
// both in roles
|
||||
roles.add(ROLE_ADMIN);
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.USER_API, roles));
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.ACCOUNT, roles));
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.IDP_REVIEW, roles));
|
||||
Assert.assertTrue(UPConfigUtils.isRoleForContext(UserProfileContext.REGISTRATION, roles));
|
||||
Assert.assertTrue(UserProfileContext.USER_API.isRoleForContext(roles));
|
||||
Assert.assertTrue(UserProfileContext.ACCOUNT.isRoleForContext(roles));
|
||||
Assert.assertTrue(UserProfileContext.IDP_REVIEW.isRoleForContext(roles));
|
||||
Assert.assertTrue(UserProfileContext.REGISTRATION.isRoleForContext(roles));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue