From 6089726f662220cbd863e26f77e66bfb765431a7 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Thu, 5 Feb 2026 22:05:31 -0500 Subject: [PATCH] [_763] Adapt GEN_ADMIN call style for removing remote user. When calling to GEN_ADMIN to remove a remote user with separate user and zone parameters, the server will not remove the /tempZone/**/user#hello collections. So, we now force PRC to use an integrated user#zone format. --- irods/manager/user_manager.py | 40 +++++++++++++++++++++++++++++++-- irods/test/admin_test.py | 42 ++++++++++++++++++++++++++++++----- irods/test/user_group_test.py | 1 + 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/irods/manager/user_manager.py b/irods/manager/user_manager.py index 90f4be813..1407f3005 100644 --- a/irods/manager/user_manager.py +++ b/irods/manager/user_manager.py @@ -45,7 +45,38 @@ def remove_quota(self, user_name, resource="total"): self._get_session, "set-quota", "user", user_name, resource, "0" ) + @staticmethod + def _parse_user_and_zone(user_param, zone_param): + """ + Parse out user and zone components from the arguments given. + + Args: + user_param: either a simple user name, or a combination of both + user and zone names joined with "#". + zone_param: a simple zone name, + + Returns: + The resulting parsed user and zone. + + Raises: + RuntimeError: in the case of formatting errors or conflicting zone names. + """ + if '#' in user_param: + u_parsed_user, u_parsed_zone = user_param.split('#', 1) + if not u_parsed_zone: + raise RuntimeError("The compound user#zone specification may not contain a zero-length zone") + if '#' in u_parsed_zone: + raise RuntimeError(f"{u_parsed_zone = } is wrongly formatted") + if zone_param and (u_parsed_zone != zone_param): + raise RuntimeError( + f"Two nonzero-length zone names ({u_parsed_zone}, {zone_param}) were given, but they do not agree." + ) + return u_parsed_user, u_parsed_zone + return user_param, zone_param + def get(self, user_name, user_zone=""): + user_name, user_zone = self._parse_user_and_zone(user_name, user_zone) + if not user_zone: user_zone = self.sess.zone @@ -121,6 +152,12 @@ def create(self, user_name, user_type, user_zone="", auth_str=""): def remove(self, user_name, user_zone="", _object=None): if _object is None: _object = self.get(user_name, user_zone) + + if _object.type == "rodsgroup": # noqa: SIM108 + uz_args = (f"{_object.name}",) + else: + uz_args = (f"{_object.name}#{_object.zone}",) + message_body = GeneralAdminRequest( "rm", ( @@ -128,8 +165,7 @@ def remove(self, user_name, user_zone="", _object=None): if (_object.type != "rodsgroup" or self.sess.server_version < (4, 3, 2)) else "group" ), - user_name, - user_zone, + *uz_args, ) request = iRODSMessage( "RODS_API_REQ", msg=message_body, int_info=api_number["GENERAL_ADMIN_AN"] diff --git a/irods/test/admin_test.py b/irods/test/admin_test.py index 91faad393..3009235d0 100644 --- a/irods/test/admin_test.py +++ b/irods/test/admin_test.py @@ -4,16 +4,18 @@ import os import sys import unittest -from irods.models import User, Group + +import irods.keywords as kw +from irods.column import Like from irods.exception import ( - UserDoesNotExist, - ResourceDoesNotExist, SYS_NO_API_PRIV, + ResourceDoesNotExist, + UserDoesNotExist, ) -from irods.session import iRODSSession +from irods.models import Collection, Group, User from irods.resource import iRODSResource -import irods.test.helpers as helpers -import irods.keywords as kw +from irods.session import iRODSSession +from irods.test import helpers class TestAdmin(unittest.TestCase): @@ -531,6 +533,34 @@ def test_set_user_info(self): with self.assertRaises(UserDoesNotExist): self.sess.users.get(self.new_user_name) + def test_deleting_remote_user_including_home_collection_and_trash_artifact__issue_763(self): + # Test and confirm that, when passing user and zone parameters separately in calls to + # remove remote users, that both /tempZone/home/user#zone and /tempZone/trash/home/user#zone + # are deleted. + remote_zone = remote_user = None + try: + remote_zone = (sess := self.sess).zones.create('other_zone', 'remote') + remote_user = sess.users.create(user_name='myuser', user_type='rodsuser', user_zone=remote_zone.name) + + def get_collection_artifacts(): + return list( + sess.query(Collection).filter(Like(Collection.name, f'%/{remote_user.name}#{remote_zone.name}')) + ) + + # Two collection artifacts should be present, with names: + # //home/remote_user#remote_zone + # //trash/home/remote_user#remote_zone + self.assertEqual(len(get_collection_artifacts()), 2) + + remote_user.remove() + + # The above-mentioned artifacts should have been deleted along with the remote user. + self.assertEqual(len(get_collection_artifacts()), 0) + + finally: + if remote_zone: + remote_zone.remove() + if __name__ == "__main__": # let the tests find the parent irods lib diff --git a/irods/test/user_group_test.py b/irods/test/user_group_test.py index 5ce8b84aa..4d9f0c529 100644 --- a/irods/test/user_group_test.py +++ b/irods/test/user_group_test.py @@ -143,6 +143,7 @@ def generator(p=OLDPASS): shutil.rmtree(ENV_DIR) ses.users.remove("alice") + def test_modifying_password_at_various_lengths__issue_328(self): ses = self.sess try: