From a305ee12ad7a5189b64eeaf0dd6cab67de5440a8 Mon Sep 17 00:00:00 2001 From: claravox Date: Fri, 2 Aug 2024 11:09:20 +0200 Subject: [PATCH] YDA-5747: script for granting read members access to vault packages --- browse.py | 2 +- folder.py | 1 - .../grant-readers-access-to-vault-packages.r | 9 ++ .../grant-readers-access-to-vault-packages.sh | 2 + vault.py | 134 ++++++++++++++++++ 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 tools/grant-readers-access-to-vault-packages.r create mode 100755 tools/grant-readers-access-to-vault-packages.sh diff --git a/browse.py b/browse.py index bf1ea0dee..68ef0cb2a 100644 --- a/browse.py +++ b/browse.py @@ -245,7 +245,7 @@ def transform(row): if sort_on == 'modified': cols = ['COLL_NAME', 'COLL_PARENT_NAME', 'MIN(COLL_CREATE_TIME)', 'ORDER(COLL_MODIFY_TIME)'] else: - cols = ['ORDER(COLL_NAME)', 'COLL_PARENT_NAME' 'MIN(COLL_CREATE_TIME)', 'MAX(COLL_MODIFY_TIME)'] + cols = ['ORDER(COLL_NAME)', 'COLL_PARENT_NAME', 'MIN(COLL_CREATE_TIME)', 'MAX(COLL_MODIFY_TIME)'] where = "COLL_PARENT_NAME like '{}%%' AND COLL_NAME like '%%{}%%'".format("/" + zone + "/home", search_string) elif search_type == 'metadata': if sort_on == 'modified': diff --git a/folder.py b/folder.py index 38553aeaa..f5ad0dd43 100644 --- a/folder.py +++ b/folder.py @@ -345,7 +345,6 @@ def set_can_modify(ctx, coll): check_access_result = msi.check_access(ctx, coll, 'modify object', irods_types.BytesBuf()) modify_access = check_access_result['arguments'][2] if modify_access != b'\x01': - # TODO set to a lower read? # This allows us permission to copy the files if not set_acl_check(ctx, "recursive", "admin:read", coll, "Could not set ACL (admin:read) for collection: {}".format(coll)): return False diff --git a/tools/grant-readers-access-to-vault-packages.r b/tools/grant-readers-access-to-vault-packages.r new file mode 100644 index 000000000..db650a521 --- /dev/null +++ b/tools/grant-readers-access-to-vault-packages.r @@ -0,0 +1,9 @@ +#!/usr/bin/irule -F + +grantReadersAccessVaultPackages { + # Grant read- groups access to corresponding vault packages + *return = ""; + rule_vault_grant_readers_vault_access(*dryRun, *verbose, *return); +} +input *dryRun="", *verbose="" +output ruleExecOut diff --git a/tools/grant-readers-access-to-vault-packages.sh b/tools/grant-readers-access-to-vault-packages.sh new file mode 100755 index 000000000..ef81bb13d --- /dev/null +++ b/tools/grant-readers-access-to-vault-packages.sh @@ -0,0 +1,2 @@ +#!/bin/bash +irule -r irods_rule_engine_plugin-irods_rule_language-instance -F /etc/irods/yoda-ruleset/tools/grant-readers-access-to-vault-packages.r '*dryRun="'$1'"' '*verbose="'$2'"' diff --git a/vault.py b/vault.py index 505fcf17f..6c149f997 100644 --- a/vault.py +++ b/vault.py @@ -37,6 +37,7 @@ 'rule_vault_enable_indexing', 'rule_vault_disable_indexing', 'rule_vault_process_status_transitions', + 'rule_vault_grant_readers_vault_access', 'api_vault_system_metadata', 'api_vault_collection_details', 'api_vault_get_package_by_reference', @@ -1135,6 +1136,139 @@ def set_vault_permissions(ctx, coll, target): return True +def reader_needs_access(ctx, group_name, coll): + """Return if research group has access to this group but readers do not""" + iter = genquery.row_iterator( + "COLL_ACCESS_USER_ID", + "COLL_NAME = '" + coll + "'", + genquery.AS_LIST, ctx + ) + reader_found = False + research_found = False + + for row in iter: + user_id = row[0] + user_name = user.name_from_id(ctx, user_id) + # Check if there are *any* readers + if user_name.startswith('read-'): + reader_found = True + elif user_name == group_name: + research_found = True + + return not reader_found and research_found + + +def set_reader_vault_permissions(ctx, group_name, zone, dry_run): + """Given a research group name, give reader group access to + vault packages if they don't have that access already. + + :param ctx: Combined type of a callback and rei struct + :param group_name: Research group name + :param zone: Zone + :param dry_run: Whether to only print which groups would be changed without changing them + + :return: Boolean whether completed successfully or there were errors. + """ + parts = group_name.split('-') + base_name = '-'.join(parts[1:]) + read_group_name = 'read-' + base_name + vault_group_name = constants.IIVAULTPREFIX + base_name + vault_path = "/" + zone + "/home/" + vault_group_name + no_errors = True + + # Do not change the permissions if there aren't any vault packages in this vault. + if collection.empty(ctx, vault_path): + return True + + if reader_needs_access(ctx, group_name, vault_path): + # Grant the research group readers read-only access to the collection + # to enable browsing through the vault. + try: + if dry_run: + log.write(ctx, "Would have granted " + read_group_name + " read access to " + vault_path) + else: + msi.set_acl(ctx, "default", "admin:read", read_group_name, vault_path) + log.write(ctx, "Granted " + read_group_name + " read access to " + vault_path) + except msi.Error: + no_errors = False + log.write(ctx, "Failed to grant " + read_group_name + " read access to " + vault_path) + + iter = genquery.row_iterator( + "COLL_NAME", + "COLL_PARENT_NAME = '{}'".format(vault_path), + genquery.AS_LIST, ctx + ) + for row in iter: + target = row[0] + if reader_needs_access(ctx, group_name, target): + try: + if dry_run: + log.write(ctx, "Would have granted " + read_group_name + " read access to " + target) + else: + msi.set_acl(ctx, "recursive", "admin:read", read_group_name, target) + log.write(ctx, "Granted " + read_group_name + " read access to " + target) + except Exception: + no_errors = False + log.write(ctx, "Failed to set read permissions for <{}> on coll <{}>".format(read_group_name, target)) + + return no_errors + + +@rule.make(inputs=[0, 1], outputs=[2]) +def rule_vault_grant_readers_vault_access(ctx, dry_run, verbose): + """Rule for granting reader members of research groups access to vault packages in their + group if they don't have access already + + :param ctx: Combined type of a callback and rei struct + :param dry_run: Whether to only print which groups would be changed without making changes + :param verbose: Whether to be more verbose + + :return: String status of completed successfully ('0') or there were errors ('1') + """ + dry_run = (dry_run == '1') + verbose = (verbose == '1') + no_errors = True + + log.write(ctx, "grant_readers_vault_access started.") + + if user.user_type(ctx) != 'rodsadmin': + log.write(ctx, "User is not rodsadmin") + return '1' + + if dry_run or verbose: + modes = [] + if dry_run: + modes.append("dry run") + if verbose: + modes.append("verbose") + log.write(ctx, "Running grant_readers_vault_access in {} mode.".format((" and ").join(modes))) + + zone = user.zone(ctx) + + # Get the group names + userIter = genquery.row_iterator( + "USER_GROUP_NAME", + "USER_TYPE = 'rodsgroup' AND USER_ZONE = '{}' AND USER_GROUP_NAME like 'research-%'".format(zone), + genquery.AS_LIST, + ctx) + + for row in userIter: + name = row[0] + if verbose: + log.write(ctx, "{}: checking permissions".format(name)) + if not set_reader_vault_permissions(ctx, name, zone, dry_run): + no_errors = False + + message = "" + if no_errors: + message = "grant_readers_vault_access completed successfully." + else: + message = "grant_readers_vault_access completed, with errors." + log.write(ctx, message) + + return '0' if no_errors else '1' + + @rule.make(inputs=range(4), outputs=range(4, 6)) def rule_vault_process_status_transitions(ctx, coll, new_coll_status, actor, previous_version): """Rule interface for processing vault status transition request.