diff --git a/keepercommander/importer/commands.py b/keepercommander/importer/commands.py index 223f5525b..83f2d75ed 100644 --- a/keepercommander/importer/commands.py +++ b/keepercommander/importer/commands.py @@ -155,6 +155,7 @@ def _cyberark_skip_arg(value): apply_membership_parser = argparse.ArgumentParser(prog='apply-membership', description='Loads shared folder membership from JSON file') apply_membership_parser.add_argument('--full-sync', dest='full_sync', action='store_true', help='Update and remove membership also.') +apply_membership_parser.add_argument('--unsafe', dest='unsafe', action='store_true', help='Combined with --full-sync. Will remove yourself from folders where you have no permission.') apply_membership_parser.add_argument('name', type=str, nargs='?', help='Input file name. "shared_folder_membership.json" if omitted.') apply_membership_parser.error = raise_parse_exception apply_membership_parser.exit = suppress_exit @@ -547,8 +548,9 @@ def execute(self, params, **kwargs): teams.append(obj) full_sync = kwargs.get('full_sync') is True + unsafe = kwargs.get('unsafe') is True if len(shared_folders) > 0: - imp_exp.import_user_permissions(params, shared_folders, full_sync) + imp_exp.import_user_permissions(params, shared_folders, full_sync, unsafe) if len(teams) > 0: imp_exp.import_teams(params, teams, full_sync) diff --git a/keepercommander/importer/imp_exp.py b/keepercommander/importer/imp_exp.py index 75914c0df..66e1d3075 100644 --- a/keepercommander/importer/imp_exp.py +++ b/keepercommander/importer/imp_exp.py @@ -617,7 +617,7 @@ def import_teams(params, teams, full_sync=False): # type: (KeeperParams, List[ def import_user_permissions(params, shared_folders, - full_sync=False): # type: (KeeperParams, List[ImportSharedFolder], bool) -> None + full_sync=False, unsafe=False): # type: (KeeperParams, List[ImportSharedFolder], bool) -> None if not shared_folders: return @@ -654,7 +654,7 @@ def import_user_permissions(params, folders = [x for x in folders if x.uid in params.shared_folder_cache] if folders: - permissions = prepare_folder_permission(params, folders, full_sync) + permissions = prepare_folder_permission(params, folders, full_sync, unsafe) if permissions: teams_added = 0 users_added = 0 @@ -1889,32 +1889,46 @@ def prepare_folder_add(params, folders, records, manage_users, manage_records, c is_last = True if digest not in folder_hash: - folder_uid = api.generate_record_uid() - folder_type = 'shared_folder' if is_last else 'user_folder' + existing_by_uid = None + if is_last: + sf_uid = getattr(fol, 'uid', None) + if sf_uid and sf_uid in params.shared_folder_cache and sf_uid in params.folder_cache: + # The import file names an existing shared folder by uid. + # Reuse it in place (no new folder, no move) so any + # records that reference this path resolve to it. + shared_folder_key = params.shared_folder_cache[sf_uid]['shared_folder_key_unencrypted'] + existing_by_uid = sf_uid, 'shared_folder', shared_folder_key + + if existing_by_uid is not None: + folder_uid, folder_type, folder_key = existing_by_uid + folder_hash[digest] = existing_by_uid + else: + folder_uid = api.generate_record_uid() + folder_type = 'shared_folder' if is_last else 'user_folder' - fol_req = folder_pb2.FolderRequest() - fol_req.folderUid = base64.urlsafe_b64decode(folder_uid + '==') - fol_req.folderType = 2 if folder_type == 'shared_folder' else 1 + fol_req = folder_pb2.FolderRequest() + fol_req.folderUid = base64.urlsafe_b64decode(folder_uid + '==') + fol_req.folderType = 2 if folder_type == 'shared_folder' else 1 - if parent_uid: - fol_req.parentFolderUid = base64.urlsafe_b64decode(parent_uid + '==') + if parent_uid: + fol_req.parentFolderUid = base64.urlsafe_b64decode(parent_uid + '==') - folder_key = utils.generate_aes_key() - fol_req.encryptedFolderKey = crypto.encrypt_aes_v1(folder_key, params.data_key) + folder_key = utils.generate_aes_key() + fol_req.encryptedFolderKey = crypto.encrypt_aes_v1(folder_key, params.data_key) - data = {'name': comp} - fol_req.folderData = crypto.encrypt_aes_v1(json.dumps(data).encode('utf-8'), folder_key) + data = {'name': comp} + fol_req.folderData = crypto.encrypt_aes_v1(json.dumps(data).encode('utf-8'), folder_key) - if folder_type == 'shared_folder': - fol_req.sharedFolderFields.encryptedFolderName = \ - crypto.encrypt_aes_v1(comp.encode('utf-8'), folder_key) - fol_req.sharedFolderFields.manageUsers = fol.manage_users or manage_users - fol_req.sharedFolderFields.manageRecords = fol.manage_records or manage_records - fol_req.sharedFolderFields.canEdit = fol.can_edit or can_edit - fol_req.sharedFolderFields.canShare = fol.can_share or can_share + if folder_type == 'shared_folder': + fol_req.sharedFolderFields.encryptedFolderName = \ + crypto.encrypt_aes_v1(comp.encode('utf-8'), folder_key) + fol_req.sharedFolderFields.manageUsers = fol.manage_users or manage_users + fol_req.sharedFolderFields.manageRecords = fol.manage_records or manage_records + fol_req.sharedFolderFields.canEdit = fol.can_edit or can_edit + fol_req.sharedFolderFields.canShare = fol.can_share or can_share - folder_add.append(fol_req) - folder_hash[digest] = folder_uid, folder_type, folder_key if folder_type == 'shared_folder' else None + folder_add.append(fol_req) + folder_hash[digest] = folder_uid, folder_type, folder_key if folder_type == 'shared_folder' else None else: folder_uid, folder_type, folder_key = folder_hash[digest] if is_last: @@ -2572,7 +2586,7 @@ def prepare_record_link(params, records): return record_links -def prepare_folder_permission(params, folders, full_sync): +def prepare_folder_permission(params, folders, full_sync, unsafe=False): # type: (KeeperParams, List[ImportSharedFolder], bool) -> list """Prepare a list of API interactions for changes to folder permissions.""" shared_folder_lookup = {} @@ -2668,6 +2682,9 @@ def prepare_folder_permission(params, folders, full_sync): existing_users.update((x['username'] for x in shared_folder['users'])) if params.user in existing_users: existing_users.remove(params.user) + if unsafe: + # Set user to end of array to be removed last + existing_users.add(params.user) keep_teams = set() keep_users = set()