From fed719448cdd1e9c6db8ef30799ab7e4bc70939b Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Tue, 10 Mar 2026 17:02:11 +0530 Subject: [PATCH 1/4] Added path, userId and expiration date with share folder command --- keepercommander/commands/folder.py | 6 +++- keepercommander/commands/record.py | 16 ++++++++-- .../service/util/parse_keeper_response.py | 30 ++++++++++++++----- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/keepercommander/commands/folder.py b/keepercommander/commands/folder.py index 9324ad146..152c9106f 100644 --- a/keepercommander/commands/folder.py +++ b/keepercommander/commands/folder.py @@ -577,10 +577,14 @@ def execute(self, params, **kwargs): request['data'] = utils.base64_url_encode(crypto.encrypt_aes_v1(data.encode('utf-8'), folder_key)) api.communicate(params, request) - params.sync_data = True + api.sync_down(params) params.environment_variables[LAST_FOLDER_UID] = folder_uid if request['folder_type'] == 'shared_folder': params.environment_variables[LAST_SHARED_FOLDER_UID] = folder_uid + path = get_folder_path(params, folder_uid) or name + if path.endswith('/'): + path = path[:-1] + print(json.dumps({'folder_uid': folder_uid, 'name': name, 'path': path})) return folder_uid diff --git a/keepercommander/commands/record.py b/keepercommander/commands/record.py index 87d863f85..1519fc4b4 100644 --- a/keepercommander/commands/record.py +++ b/keepercommander/commands/record.py @@ -277,9 +277,13 @@ def execute(self, params, **kwargs): admins = api.get_share_admins_for_shared_folder(params, uid) sf = api.get_shared_folder(params, uid) if fmt == 'json': + folder_path = get_folder_path(params, sf.shared_folder_uid, delimiter=os.sep) + if folder_path and folder_path.endswith(os.sep): + folder_path = folder_path[:-1] sfo = { "shared_folder_uid": sf.shared_folder_uid, "name": sf.name, + "path": folder_path or sf.name, "manage_users": sf.default_manage_users, "manage_records": sf.default_manage_records, "can_edit": sf.default_can_edit, @@ -291,17 +295,25 @@ def execute(self, params, **kwargs): 'can_edit': r['can_edit'], 'can_share': r['can_share'] } for r in sf.records] + def _format_expiration(expiration_value): + if expiration_value is None or expiration_value <= 0: + return 'never' + return datetime.datetime.fromtimestamp(expiration_value // 1000).isoformat() if sf.users: sfo['users'] = [{ 'username': u['username'], + 'user_id': u.get('account_uid'), 'manage_records': u['manage_records'], - 'manage_users': u['manage_users'] + 'manage_users': u['manage_users'], + 'expiration': _format_expiration(u.get('expiration')) } for u in sf.users] if sf.teams: sfo['teams'] = [{ 'name': t['name'], + 'team_uid': t.get('team_uid'), 'manage_records': t['manage_records'], - 'manage_users': t['manage_users'] + 'manage_users': t['manage_users'], + 'expiration': _format_expiration(t.get('expiration')) } for t in sf.teams] if admins: diff --git a/keepercommander/service/util/parse_keeper_response.py b/keepercommander/service/util/parse_keeper_response.py index 7f8f004f1..0833f5a3a 100644 --- a/keepercommander/service/util/parse_keeper_response.py +++ b/keepercommander/service/util/parse_keeper_response.py @@ -486,27 +486,41 @@ def _parse_this_device_command(response: str) -> Dict[str, Any]: @staticmethod def _parse_mkdir_command(response: str) -> Dict[str, Any]: - """Parse 'mkdir' command output to extract folder UID.""" + """Parse 'mkdir' command output to extract folder UID, path, and name.""" response_str = response.strip() - - # Success case - try to extract UID + lines = [ln.strip() for ln in response_str.split('\n') if ln.strip()] + result = { "status": "success", "command": "mkdir", "data": None } - - if re.match(r'^[a-zA-Z0-9_-]+$', response_str): + + for line in lines: + try: + data = json.loads(line) + if isinstance(data, dict) and 'folder_uid' in data: + result["data"] = { + "folder_uid": data["folder_uid"], + "path": data.get("path"), + "name": data.get("name") + } + return result + except (json.JSONDecodeError, TypeError): + pass + + last_line = lines[-1] if lines else response_str + if re.match(r'^[a-zA-Z0-9_-]+$', last_line): result["data"] = { - "folder_uid": response_str + "folder_uid": last_line } else: - uid_match = re.search(r'folder_uid=([a-zA-Z0-9_-]+)', response_str) + uid_match = re.search(r'folder_uid=([a-zA-Z0-9_-]+)', last_line) if uid_match: result["data"] = { "folder_uid": uid_match.group(1) } - + return result @staticmethod From eaa658affb1d5cff3934435148f66278c18c1a65 Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Thu, 12 Mar 2026 16:09:07 +0530 Subject: [PATCH 2/4] sync down changes --- keepercommander/commands/folder.py | 6 ++++-- keepercommander/commands/record.py | 6 ++---- keepercommander/service/util/parse_keeper_response.py | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/keepercommander/commands/folder.py b/keepercommander/commands/folder.py index 152c9106f..0f9005a3a 100644 --- a/keepercommander/commands/folder.py +++ b/keepercommander/commands/folder.py @@ -577,14 +577,16 @@ def execute(self, params, **kwargs): request['data'] = utils.base64_url_encode(crypto.encrypt_aes_v1(data.encode('utf-8'), folder_key)) api.communicate(params, request) + params.sync_data = True api.sync_down(params) params.environment_variables[LAST_FOLDER_UID] = folder_uid if request['folder_type'] == 'shared_folder': params.environment_variables[LAST_SHARED_FOLDER_UID] = folder_uid path = get_folder_path(params, folder_uid) or name - if path.endswith('/'): + if path and path.endswith('/'): path = path[:-1] - print(json.dumps({'folder_uid': folder_uid, 'name': name, 'path': path})) + response_data = {'folder_uid': folder_uid, 'name': name, 'path': path} + print(json.dumps(response_data)) return folder_uid diff --git a/keepercommander/commands/record.py b/keepercommander/commands/record.py index 1519fc4b4..829a5f5fa 100644 --- a/keepercommander/commands/record.py +++ b/keepercommander/commands/record.py @@ -277,13 +277,11 @@ def execute(self, params, **kwargs): admins = api.get_share_admins_for_shared_folder(params, uid) sf = api.get_shared_folder(params, uid) if fmt == 'json': - folder_path = get_folder_path(params, sf.shared_folder_uid, delimiter=os.sep) - if folder_path and folder_path.endswith(os.sep): - folder_path = folder_path[:-1] + path = get_folder_path(params, sf.shared_folder_uid, delimiter=os.sep) if sf.shared_folder_uid else '' sfo = { "shared_folder_uid": sf.shared_folder_uid, "name": sf.name, - "path": folder_path or sf.name, + "path": path, "manage_users": sf.default_manage_users, "manage_records": sf.default_manage_records, "can_edit": sf.default_can_edit, diff --git a/keepercommander/service/util/parse_keeper_response.py b/keepercommander/service/util/parse_keeper_response.py index 0833f5a3a..ff5d327c9 100644 --- a/keepercommander/service/util/parse_keeper_response.py +++ b/keepercommander/service/util/parse_keeper_response.py @@ -118,6 +118,15 @@ def parse_response(command: str, response: Any, log_output: str = None) -> Dict[ Returns: Dict[str, Any]: Structured JSON response """ + if isinstance(response, dict) and 'scim create' in command: + if 'scim_id' in response and 'provisioning_token' in response: + base_cmd = command.split()[0] if command.split() else command + return { + "status": "success", + "command": base_cmd, + "message": "SCIM endpoint created successfully", + "data": response, + } # Preprocess response once response_str, is_from_log = KeeperResponseParser._preprocess_response(response, log_output) From 022279fc8c7766c562ab2d95c3f88543d639299d Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Wed, 18 Mar 2026 16:26:18 +0530 Subject: [PATCH 3/4] unit test fix --- unit-tests/test_command_folder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unit-tests/test_command_folder.py b/unit-tests/test_command_folder.py index fcfd4beab..7166515f0 100644 --- a/unit-tests/test_command_folder.py +++ b/unit-tests/test_command_folder.py @@ -75,6 +75,8 @@ def is_shared_folder_folder(rq): self.assertEqual(rq['command'], 'folder_add') self.assertEqual(rq['folder_type'], 'shared_folder_folder') + mock.patch('keepercommander.api.sync_down').start() + KeeperApiHelper.communicate_expect([is_user_folder]) cmd.execute(params, user_folder=True, folder='New Folder') From 04c94c2000e8a54d156680b6088e9145eab3a8cb Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Fri, 20 Mar 2026 13:43:46 +0530 Subject: [PATCH 4/4] undo unit test and remove param sync down changes --- keepercommander/commands/folder.py | 8 +++----- keepercommander/service/util/parse_keeper_response.py | 9 --------- unit-tests/test_command_folder.py | 2 -- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/keepercommander/commands/folder.py b/keepercommander/commands/folder.py index 0f9005a3a..f857b9f43 100644 --- a/keepercommander/commands/folder.py +++ b/keepercommander/commands/folder.py @@ -578,15 +578,13 @@ def execute(self, params, **kwargs): api.communicate(params, request) params.sync_data = True - api.sync_down(params) params.environment_variables[LAST_FOLDER_UID] = folder_uid if request['folder_type'] == 'shared_folder': params.environment_variables[LAST_SHARED_FOLDER_UID] = folder_uid - path = get_folder_path(params, folder_uid) or name - if path and path.endswith('/'): - path = path[:-1] + parent_path = get_folder_path(params, base_folder.uid) if base_folder.uid else '' + path = f'{parent_path}{name}' response_data = {'folder_uid': folder_uid, 'name': name, 'path': path} - print(json.dumps(response_data)) + logging.info(json.dumps(response_data)) return folder_uid diff --git a/keepercommander/service/util/parse_keeper_response.py b/keepercommander/service/util/parse_keeper_response.py index ff5d327c9..0833f5a3a 100644 --- a/keepercommander/service/util/parse_keeper_response.py +++ b/keepercommander/service/util/parse_keeper_response.py @@ -118,15 +118,6 @@ def parse_response(command: str, response: Any, log_output: str = None) -> Dict[ Returns: Dict[str, Any]: Structured JSON response """ - if isinstance(response, dict) and 'scim create' in command: - if 'scim_id' in response and 'provisioning_token' in response: - base_cmd = command.split()[0] if command.split() else command - return { - "status": "success", - "command": base_cmd, - "message": "SCIM endpoint created successfully", - "data": response, - } # Preprocess response once response_str, is_from_log = KeeperResponseParser._preprocess_response(response, log_output) diff --git a/unit-tests/test_command_folder.py b/unit-tests/test_command_folder.py index 7166515f0..fcfd4beab 100644 --- a/unit-tests/test_command_folder.py +++ b/unit-tests/test_command_folder.py @@ -75,8 +75,6 @@ def is_shared_folder_folder(rq): self.assertEqual(rq['command'], 'folder_add') self.assertEqual(rq['folder_type'], 'shared_folder_folder') - mock.patch('keepercommander.api.sync_down').start() - KeeperApiHelper.communicate_expect([is_user_folder]) cmd.execute(params, user_folder=True, folder='New Folder')