This is an automated email from the ASF dual-hosted git repository.

dhavalshah9131 pushed a commit to branch ds_RANGER-5800_ran-2.10
in repository https://gitbox.apache.org/repos/asf/ranger.git

commit 9934da47ab484a614520f705b2a9251cc5cba3c0
Author: Dhaval Shah <dhavalshah9131@@gmail.com>
AuthorDate: Wed Sep 23 20:51:29 2026 +0530

    RANGER-5800 : UserGroup API require additional checks
---
 .../main/java/org/apache/ranger/biz/XUserMgr.java  | 22 +++++++++++
 .../users_details/UserForm.jsx                     | 11 ++++--
 .../java/org/apache/ranger/biz/TestXUserMgr.java   | 44 ++++++++++++++++++++++
 3 files changed, 73 insertions(+), 4 deletions(-)

diff --git a/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java 
b/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java
index 1b013b314..973936239 100755
--- a/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java
@@ -513,6 +513,7 @@ else if (oldUserProfile.getUserSource() == 
RangerCommonEnums.USER_EXTERNAL) {
                vXUser.setPassword(hiddenPasswordString);
 
                Long userId = vXUser.getId();
+               validateGroupMembershipChangeAccess(userId, groupIdList);
                List<Long> groupUsersToRemove = new ArrayList<Long>();
                List<XXTrxLogV2> groupUserTrxLogs = 
createOrDelGrpUserWithUpdatedGrpId(vXUser, groupIdList,userId, 
groupUsersToRemove);
 
@@ -530,6 +531,27 @@ else if (oldUserProfile.getUserSource() == 
RangerCommonEnums.USER_EXTERNAL) {
 
                return vXUser;
        }
+
+       private void validateGroupMembershipChangeAccess(Long userId, 
Collection<Long> groupIdList) {
+               if (groupIdList == null || userId == null) {
+                       return;
+               }
+               UserSessionBase session = ContextUtil.getCurrentUserSession();
+               if (session == null || session.isUserAdmin()) {
+                       return;
+               }
+               List<Long> existingGroupIds = 
daoManager.getXXGroupUser().findGroupIdListByUserId(userId);
+               Set<Long> requestedGroupIds = new HashSet<>(groupIdList);
+               Set<Long> currentGroupIds     = new HashSet<>();
+               if (CollectionUtils.isNotEmpty(existingGroupIds)) {
+                       currentGroupIds.addAll(existingGroupIds);
+               }
+               if (!requestedGroupIds.equals(currentGroupIds)) {
+                       throw restErrorUtil.create403RESTException(
+                                       "Logged-in user is not permitted to 
modify group membership.");
+               }
+       }
+
        private List<XXTrxLogV2> createOrDelGrpUserWithUpdatedGrpId(VXUser 
vXUser, Collection<Long> groupIdList,Long userId, List<Long> 
groupUsersToRemove) {
                Collection<String> groupNamesSet = new HashSet<String>();
                List<XXTrxLogV2> trxLogList = new ArrayList<>();
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
index 2eb9e5a85..fbeaaceb0 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
@@ -35,7 +35,7 @@ import {
   UserTypes
 } from "Utils/XAEnums";
 import { toast } from "react-toastify";
-import { InfoIcon, getUserAccessRoleList, serverError } from "Utils/XAUtils";
+import { InfoIcon, getUserAccessRoleList, isSystemAdmin, serverError } from 
"Utils/XAUtils";
 import { getUserProfile, setUserProfile } from "Utils/appState";
 import { cloneDeep, has, isEmpty, isUndefined } from "lodash";
 import { SyncSourceDetails } from 
"Views/UserGroupRoleListing/SyncSourceDetails";
@@ -84,6 +84,7 @@ function UserForm(props) {
   const isExternalOrFederatedUser =
     userInfo?.userSource == UserTypes.USER_EXTERNAL.value ||
     userInfo?.userSource == UserTypes.USER_FEDERATED.value;
+  const isInternalUser = userInfo?.userSource == UserTypes.USER_INTERNAL.value;
 
   const handleSubmit = async (formData) => {
     let userFormData = {};
@@ -237,9 +238,7 @@ function UserForm(props) {
         loadOptions={loadOptions}
         defaultOptions
         isMulti
-        isDisabled={
-          isEditView && userInfo && isExternalOrFederatedUser ? true : false
-        }
+        isDisabled={disabledGroupField()}
         styles={selectInputCustomStyles}
       />
     );
@@ -278,6 +277,10 @@ function UserForm(props) {
     return disabledUserRolefield;
   };
 
+  const disabledGroupField = () => {
+    return !(isSystemAdmin() && isEditView && isInternalUser)
+  };
+
   const userRoleListData = () => {
     return getUserAccessRoleList();
   };
diff --git 
a/security-admin/src/test/java/org/apache/ranger/biz/TestXUserMgr.java 
b/security-admin/src/test/java/org/apache/ranger/biz/TestXUserMgr.java
index f6ab5b116..708507809 100644
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestXUserMgr.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestXUserMgr.java
@@ -815,6 +815,50 @@ public void test05UpdateXUser() {
                Assert.assertNotNull(dbvxUser);
        }
 
+       @Test
+       public void testUpdateXUser_KeyAdminCannotChangeGroupMembership() {
+               destroySession();
+               setupKeyAdmin();
+
+               VXUser vxUser = vxUser();
+               vxUser.setId(userId);
+               vxUser.setFirstName("test");
+               
vxUser.setUserRoleList(Collections.singletonList(RangerConstants.ROLE_USER));
+
+               VXPortalUser vXPortalUser = userProfile();
+               
vXPortalUser.setUserRoleList(Collections.singletonList(RangerConstants.ROLE_USER));
+
+               
Mockito.when(userMgr.getUserProfileByLoginId(vxUser.getName())).thenReturn(vXPortalUser);
+               
Mockito.when(xUserService.readResource(userId)).thenReturn(vxUser);
+
+               XXPortalUser xXPortalUser = xxPortalUser(vXPortalUser);
+               
Mockito.when(userMgr.updateUserWithPass(Mockito.any())).thenReturn(xXPortalUser);
+               
Mockito.when(xUserService.updateResource(vxUser)).thenReturn(vxUser);
+               Mockito.when(xUserService.getTransactionLog(
+                                               Mockito.any(VXUser.class),
+                                               Mockito.any(),
+                                               Mockito.anyInt()))
+                               .thenReturn(new ArrayList<>());
+
+               VXUser loggedInUser = vxUser();
+               loggedInUser.setName(keyadminLoginID);
+               
loggedInUser.setUserRoleList(Collections.singletonList(RangerConstants.ROLE_KEY_ADMIN));
+               
Mockito.when(xUserService.getXUserByUserName(keyadminLoginID)).thenReturn(loggedInUser);
+
+               XXGroupUserDao xxGroupUserDao = 
Mockito.mock(XXGroupUserDao.class);
+               
Mockito.when(daoManager.getXXGroupUser()).thenReturn(xxGroupUserDao);
+               Mockito.when(xxGroupUserDao.findGroupIdListByUserId(userId))
+                               .thenReturn(Collections.emptyList());
+
+               vxUser.setGroupIdList(Collections.singletonList(105L));
+
+               Mockito.when(restErrorUtil.create403RESTException(
+                                               "Logged-in user is not 
permitted to modify group membership."))
+                               .thenThrow(new WebApplicationException());
+
+               Assertions.assertThrows(WebApplicationException.class, () -> 
xUserMgr.updateXUser(vxUser));
+       }
+
        @Test
        public void test06ModifyUserVisibilitySetOne() {
                setup();

Reply via email to