anchela commented on code in PR #862: URL: https://github.com/apache/jackrabbit-oak/pull/862#discussion_r1125479846
########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java: ########## @@ -78,6 +78,16 @@ public interface UserConstants { */ String PARAM_ADMIN_ID = "adminId"; + /** + * Configuration option defining the ID of the administratorGroups field. + */ + String ADMINISTRATOR_GROUPS_CONFIG_ID = "administratorGroups"; Review Comment: - this constant should follow the naming convention of the other configuration options and start with PARAM_ (instead of trailing CONFIG_ID) - also it should make the intention clear, which is that this an option used for the impersonation feature - i would also not limit this to groups.... but rather allow for any principal name. ########## oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java: ########## @@ -83,6 +84,12 @@ public class UserConfigurationImpl extends ConfigurationBase implements UserConf description = "Path underneath which user nodes are being created.") String usersPath() default UserConstants.DEFAULT_USER_PATH; + @AttributeDefinition( + name = "Administrators groups", Review Comment: see below. the name is misleading and imho not describing what this option is being used for. ########## oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserImplTest.java: ########## @@ -87,6 +89,19 @@ public void testAdministratorIsAdmin() throws Exception { assertTrue(getAdminUser().isAdmin()); } + @Test + public void testUserInAdministratorGroupsIsAdmin() throws RepositoryException, CommitFailedException { Review Comment: see comment above.... i am not really happy with the change and this test should not be needed in the first place. ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/package-info.java: ########## @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("1.2.1") +@Version("1.3.0") Review Comment: see above. is IMHO not needed. ########## oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java: ########## @@ -129,9 +135,17 @@ public void changePassword(@Nullable String password, @NotNull String oldPasswor changePassword(password); } + /** + * Disables the user. + * <p> + * The user with the configured param {@link UserConstants#PARAM_ADMIN_ID} cannot be disabled. + * + * @throws RepositoryException if the user is the default admin one (configuration param + * {@link UserConstants#PARAM_ADMIN_ID}) + */ @Override public void disable(@Nullable String reason) throws RepositoryException { - if (isAdmin) { + if (UserUtil.isAdmin(getUserManager().getConfig(), getID())) { Review Comment: please revert (see comment above) ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/UserConstants.java: ########## @@ -78,6 +78,16 @@ public interface UserConstants { */ String PARAM_ADMIN_ID = "adminId"; + /** + * Configuration option defining the ID of the administratorGroups field. + */ + String ADMINISTRATOR_GROUPS_CONFIG_ID = "administratorGroups"; + + /** + * Default value for the administrator group {@link #ADMINISTRATOR_GROUPS_CONFIG_ID}. + */ + String DEFAULT_ADMINISTRATORS_GROUP = "administrators"; Review Comment: this is an AEM implementation detail. there exists no 'administrators' group out of the box in Oak and this constant doesn't make sense. please remove and leave the default value in the UserConfigurationImpl empty. ########## oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java: ########## @@ -83,6 +84,12 @@ public class UserConfigurationImpl extends ConfigurationBase implements UserConf description = "Path underneath which user nodes are being created.") String usersPath() default UserConstants.DEFAULT_USER_PATH; + @AttributeDefinition( + name = "Administrators groups", + description = "List of groups whose members have admin rights.", + type = AttributeType.STRING) + String[] administratorGroups() default {UserConstants.DEFAULT_ADMINISTRATORS_GROUP}; Review Comment: - for backwards compatibility the default should be an empty list - the default administrators group name does not make sense in the context of oak as it is an implementation detail of Adobe AEM which does not exist in Oak out of the box - the name of the option is a bit misleading: this feature is not about administrative rights but about who is able to always impersonate any other user.... and it should say so - finally i would not limit this to 'groups' but allow for any kind of principal names ########## oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java: ########## @@ -16,41 +16,38 @@ */ package org.apache.jackrabbit.oak.security.user; +import static org.apache.jackrabbit.oak.api.Type.STRING; + import java.security.Principal; import javax.jcr.Credentials; import javax.jcr.RepositoryException; - import org.apache.jackrabbit.api.security.user.Impersonation; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.namepath.NamePathMapper; +import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType; import org.apache.jackrabbit.oak.spi.security.user.UserConstants; import org.apache.jackrabbit.oak.spi.security.user.UserIdCredentials; import org.apache.jackrabbit.oak.spi.security.user.util.PasswordUtil; import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil; -import org.apache.jackrabbit.oak.plugins.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static org.apache.jackrabbit.oak.api.Type.STRING; - /** * UserImpl... */ class UserImpl extends AuthorizableImpl implements User { - private final boolean isAdmin; private final PasswordHistory pwHistory; UserImpl(String id, Tree tree, UserManagerImpl userManager) throws RepositoryException { super(id, tree, userManager); - - isAdmin = UserUtil.isAdmin(userManager.getConfig(), id); pwHistory = new PasswordHistory(userManager.getConfig()); } + Review Comment: additional whitespace unrelated to the proposed improvement. please remove ########## oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserConfigurationImpl.java: ########## @@ -83,6 +84,12 @@ public class UserConfigurationImpl extends ConfigurationBase implements UserConf description = "Path underneath which user nodes are being created.") String usersPath() default UserConstants.DEFAULT_USER_PATH; + @AttributeDefinition( + name = "Administrators groups", + description = "List of groups whose members have admin rights.", Review Comment: nope.... has nothing to do with 'admin rights'. this is about impersonation and the description should state so. also i would not limit this to groups... see below. ########## oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserImpl.java: ########## @@ -79,9 +76,18 @@ public Principal getPrincipal() throws RepositoryException { } //---------------------------------------------------------------< User >--- + + /** + * The user is considered admin if it is the user with the id {@link UserConstants#DEFAULT_ADMIN_ID} or a member of + * a group configured as an administrators group using the config id + * {@link UserConstants#ADMINISTRATOR_GROUPS_CONFIG_ID}. + * + * @return true if the user has the id "admin" or a member of a configured administrators group. + */ @Override public boolean isAdmin() { - return isAdmin; + return UserUtil.isAdmin(getUserManager().getConfig(), getID()) + || UserUtil.isMemberOfAnAdministratorGroup(this, getUserManager().getConfig()); Review Comment: this is a change in API contract of the User.isAdmin definition which I don't like as it may have undesired consequences outside of the task at hand, which is to allow for configured principals to be able to impersonate any other user. this method is public API and used in many places and i don't think changing it in the context of "Give admin impersonation rights to members of administrator groups" is justified. also: your UserUtil.isMemberofAnAdministratorsGroup is not truely evaluating group membership. this is quite misleading.... ########## oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenInfoTest.java: ########## @@ -204,6 +210,12 @@ public void testRemoveTokenTreeRemovalFails() { Root r = mock(Root.class); when(r.getTree(path)).thenReturn(tokenTree); when(r.getTree(userPath)).thenReturn(root.getTree(userPath)); + when(r.getTree(NODE_TYPES_PATH)).thenReturn(root.getTree(NODE_TYPES_PATH)); Review Comment: i don't understand why the additional lines 213-218 are needed. this seems unrelated to the task at hand, no? if it is unrelated -> remove if it is relate this indicates imho that the patch is changing too many things for the task at hand (all-impersonation allowed for certain principals) -> in this case i would the patch to be refactored such that it is not needed. ########## oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserManagerImplTest.java: ########## @@ -91,7 +91,7 @@ public void after() throws Exception { } private UserManagerImpl createUserManager(@NotNull Root root, @NotNull PartialValueFactory pvf) { - return new UserManagerImpl(root, pvf, getSecurityProvider(), UserMonitor.NOOP, mock(DynamicMembershipService.class)); + return new UserManagerImpl(root, pvf, getSecurityProvider(), UserMonitor.NOOP, new DynamicMembershipTracker()); Review Comment: how is that related to the task at hand? not sure i get it.... IMHO this should not be needed. ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java: ########## @@ -39,13 +48,43 @@ */ public final class UserUtil implements UserConstants { + private static final Logger log = LoggerFactory.getLogger(UserUtil.class); Review Comment: see comments below -> will no longer be needed ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/package-info.java: ########## @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("2.5.0") +@Version("2.6.0") Review Comment: imho this is not needed ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java: ########## @@ -39,13 +48,43 @@ */ public final class UserUtil implements UserConstants { + private static final Logger log = LoggerFactory.getLogger(UserUtil.class); + private UserUtil() { } public static boolean isAdmin(@NotNull ConfigurationParameters parameters, @NotNull String userId) { return getAdminId(parameters).equals(userId); } + public static boolean isMemberOfAnAdministratorGroup(@NotNull Authorizable authorizable, @NotNull ConfigurationParameters parameters) { Review Comment: i am not convinced that this method should be part of the public API because it is only evaluating an configuration option related to impersonation, which can be kept internally.... the only place where the configuration option needs to be evaluated is in "ImpersonationImpl" -> move it there or in the non-exported user-utility class in org.apache.jackrabbit.oak.security.user (in oak-core) so, no need to change the public API and the package version. also, and this is the most important piece: the reason why ImpersonationImpl today does NOT support impersonation being granted for groups today is the fact that I don't want group-membership to be evaluated in the 'Impersonation.allows' method. that was the reason for the comment you removed :). because that neither scales nor performs. So, instead this method should not be limited to any kind of group operation but instead, I would like to have a simple principal-name comparison as follows: while looping over the set of principal in the subject passed to 'Impersonation.allows', simply test if any of the principal-name matches any of the values present in the configuration. done. this means: - not limiting it to group principals - not making any attempt to evaluate group membership as long as the principal is present in the subject we are good an no costy evaluation is need. if you believe limiting this to group principals would be sensible we can have a simple check for matching name + principal being an instanceof GroupPrincipal. long story short: move it to oak-core and simplify ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java: ########## @@ -39,13 +48,43 @@ */ public final class UserUtil implements UserConstants { + private static final Logger log = LoggerFactory.getLogger(UserUtil.class); + private UserUtil() { } public static boolean isAdmin(@NotNull ConfigurationParameters parameters, @NotNull String userId) { return getAdminId(parameters).equals(userId); } + public static boolean isMemberOfAnAdministratorGroup(@NotNull Authorizable authorizable, @NotNull ConfigurationParameters parameters) { + String[] configuredAdministratorGroups = parameters.getConfigValue(ADMINISTRATOR_GROUPS_CONFIG_ID, new String[]{ + UserConstants.DEFAULT_ADMINISTRATORS_GROUP + }); + @NotNull Set<String> groupIds = getGroupIds(authorizable); + return Arrays.stream(configuredAdministratorGroups).anyMatch(groupIds::contains); + } + + /** + * Retrieves the group ids of the groups this user is a member of. + * + * @return a set of group ids + */ + private static @NotNull Set<String> getGroupIds(@NotNull Authorizable authorizable) { + Set<String> groupIds = new HashSet<>(); + try { + @NotNull Iterator<Group> groups = authorizable.declaredMemberOf(); Review Comment: if you wanted to truely evaluate group membership (which is not needed imho), you would need to call 'memberOf()' which also includes inherited group membership. just evaluating declared membership is most likely not what you intended..... but as i said that method should not be needed to start with. ########## oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtilTest.java: ########## @@ -246,7 +275,7 @@ public void testGetAuthorizableRootPathNullType() { } - @Test(expected = NullPointerException.class) + @Test(expected = IllegalArgumentException.class) Review Comment: why is this change needed? ########## oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtil.java: ########## @@ -39,13 +48,43 @@ */ public final class UserUtil implements UserConstants { + private static final Logger log = LoggerFactory.getLogger(UserUtil.class); + private UserUtil() { } public static boolean isAdmin(@NotNull ConfigurationParameters parameters, @NotNull String userId) { return getAdminId(parameters).equals(userId); } + public static boolean isMemberOfAnAdministratorGroup(@NotNull Authorizable authorizable, @NotNull ConfigurationParameters parameters) { + String[] configuredAdministratorGroups = parameters.getConfigValue(ADMINISTRATOR_GROUPS_CONFIG_ID, new String[]{ + UserConstants.DEFAULT_ADMINISTRATORS_GROUP + }); + @NotNull Set<String> groupIds = getGroupIds(authorizable); + return Arrays.stream(configuredAdministratorGroups).anyMatch(groupIds::contains); + } + + /** + * Retrieves the group ids of the groups this user is a member of. + * + * @return a set of group ids + */ + private static @NotNull Set<String> getGroupIds(@NotNull Authorizable authorizable) { Review Comment: see above. this is potentially an expensive operation. see above on how i would envision this impersonation-for-administrators to work. this method is not needed imho ########## oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java: ########## @@ -187,6 +189,29 @@ public void testAuthenticateImpersonationCredentials2() throws Exception { assertTrue(authentication.authenticate(new ImpersonationCredentials(sc, mockAuthInfo(userId)))); } + @Test + public void testImpersonationByAdministrator() throws Exception { Review Comment: once you have refactored the patch, please add a bit of testing to ImpersonationImplTest that essentially verifies that group membership is not being evaluated during thee 'impersonation.allows' call :) something like: - get the Impersonation object from user - call allows(Subject) with a subject that you have created > test1: configured-can-impersonate-all principal name not contained in the principal-set => not allowed > test2: configured-can-impersonate-all principal name is contained in the principal set of the subject => must be allowed. > more tests depending on th impl: either verify that type of principal does not matter.... or if you prefer that this option is limited to GroupPrincipals -> add a test that verifies that the config option is being ignored if no group-principal with the configured name is present that's how i believe it should work :) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@jackrabbit.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org