Skip to content

Commit

Permalink
[irods#38,irods#42] Handling for atomic metadata operations
Browse files Browse the repository at this point in the history
metadata_guard now properly accounts for atomic metadata operations. Added unit tests, which use PRC.
  • Loading branch information
FifthPotato authored and alanking committed Feb 1, 2024
1 parent 4d68a52 commit 6d9ce70
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 78 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,17 @@ install(FILES ${CMAKE_SOURCE_DIR}/packaging/run_metadata_guard_test.py
DESTINATION ${IRODS_HOME_DIRECTORY}/scripts
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)

install(FILES ${CMAKE_SOURCE_DIR}/packaging/irods_prc_tests/test_rule_engine_plugin_metadata_guard_atomic.py
DESTINATION ${IRODS_HOME_DIRECTORY}/scripts/irods_prc_tests
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)

set(PLUGIN_PACKAGE_NAME irods-rule-engine-plugin-metadata-guard)

include(IrodsCPackCommon)

list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CPACK_PACKAGING_INSTALL_PREFIX}${IRODS_HOME_DIRECTORY}")
list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CPACK_PACKAGING_INSTALL_PREFIX}${IRODS_HOME_DIRECTORY}/scripts")
list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CPACK_PACKAGING_INSTALL_PREFIX}${IRODS_HOME_DIRECTORY}/scripts/irods_prc_tests")
list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CPACK_PACKAGING_INSTALL_PREFIX}${IRODS_HOME_DIRECTORY}/scripts/irods")
list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CPACK_PACKAGING_INSTALL_PREFIX}${IRODS_HOME_DIRECTORY}/scripts/irods/test")

Expand Down
5 changes: 5 additions & 0 deletions irods_consortium_continuous_integration_test_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ def main():
)
)

irods_python_ci_utilities.subprocess_get_output(['python3', '-m', 'pip', 'install', 'python-irodsclient==1.1.9'], check_rc=True)

test = options.test or 'test_rule_engine_plugin_metadata_guard'

try:
test_output_file = 'log/test_output.log'
irods_python_ci_utilities.subprocess_get_output(['sudo', 'su', '-', 'irods', '-c',
f'python3 scripts/run_tests.py --xml_output --run_s {test} 2>&1 | tee {test_output_file}; exit $PIPESTATUS'],
check_rc=True)
irods_python_ci_utilities.subprocess_get_output(['sudo', 'su', '-', 'irods', '-c',
f'python3 scripts/irods_prc_tests/test_rule_engine_plugin_metadata_guard_atomic.py 2>&1 | tee {test_output_file}; exit $PIPESTATUS'],
check_rc=True)
finally:
output_root_directory = options.output_root_directory
if output_root_directory:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from irods.access import iRODSAccess
from irods.session import iRODSSession
from irods.meta import iRODSMeta, AVUOperation

import irods.exception

import json
import os
import shutil
import tempfile
import unittest

class test_metadata_guard(unittest.TestCase):

IRODS_CONFIG_FILE_PATH = '/etc/irods/server_config.json'
TEST_DATA_OBJECT_PATH = '/tempZone/home/alice/test1'

@classmethod
def backup_irods_config(cls):
cls.temp_path = os.path.join(tempfile.mkdtemp(), 'irods_config_backup')
shutil.copy2(cls.IRODS_CONFIG_FILE_PATH, cls.temp_path)

@classmethod
def restore_irods_config(cls):
shutil.copy2(cls.temp_path,cls.IRODS_CONFIG_FILE_PATH)

@classmethod
def setUpClass(cls):
with iRODSSession(host='localhost', port=1247, user='rods', password='rods', zone='tempZone') as local_admin:
# create unprivileged user
local_admin.users.create_with_password('alice', 'test')

# unprivileged user creates test file
with iRODSSession(host='localhost', port=1247, user='alice', password='test', zone='tempZone') as unprivileged_user:
unprivileged_user.data_objects.create(cls.TEST_DATA_OBJECT_PATH)
unprivileged_user.acls.set( iRODSAccess('own', cls.TEST_DATA_OBJECT_PATH, 'rods', 'tempZone'))

coll = local_admin.collections.get("/tempZone")
# add metadata_guard config AVU to collection
METADATA_GUARD_CONFIG = {
'admin_only': True,
'prefixes': ['irods::']
}
if 'irods::metadata_guard' not in coll.metadata:
coll.metadata.add('irods::metadata_guard', json.dumps(METADATA_GUARD_CONFIG))

# create backup of irods config file
cls.backup_irods_config()
# insert metadata guard rule engine plugin to irods config file
with open(cls.IRODS_CONFIG_FILE_PATH, 'r+') as config_file:
METADATA_GUARD_PLUGIN_BLOCK = {
'instance_name': 'irods_rule_engine_plugin-metadata_guard-instance',
'plugin_name': 'irods_rule_engine_plugin-metadata_guard',
'plugin_specific_configuration': {}
}
config_json = json.load(config_file)
config_json['plugin_configuration']['rule_engines'].insert(0, METADATA_GUARD_PLUGIN_BLOCK)
with open(cls.IRODS_CONFIG_FILE_PATH, 'wt') as config_file:
json.dump(config_json, config_file)

@classmethod
def tearDownClass(cls):
cls.restore_irods_config()

def test_guard_atomic_bad_config__issue_38(self):
# set metadata_guard config AVU on collection to nonsense
with iRODSSession(host='localhost', port=1247, user='rods', password='rods', zone='tempZone') as local_admin:
coll = local_admin.collections.get("/tempZone")
coll.metadata['irods::metadata_guard'] = iRODSMeta('irods::metadata_guard', '{"broken": ["irods::"], ')

with iRODSSession(host='localhost', port=1247, user='alice', password='test', zone='tempZone') as unprivileged_user:
obj = unprivileged_user.data_objects.get(self.TEST_DATA_OBJECT_PATH)
obj.metadata.apply_atomic_operations( AVUOperation(operation='add', avu=iRODSMeta('irods::badconfig','badconfig')))

# bad config means operation should succeed
self.assertEqual(obj.metadata['irods::badconfig'], iRODSMeta('irods::badconfig', 'badconfig'))
obj.metadata.apply_atomic_operations( AVUOperation(operation='remove', avu=iRODSMeta('irods::badconfig','badconfig')))

def test_guard_atomic_operations_admin_only__issue_38(self):
# set metadata_guard config AVU on collection to admin_only and irods:: as protected prefix
with iRODSSession(host='localhost', port=1247, user='rods', password='rods', zone='tempZone') as local_admin:
coll = local_admin.collections.get("/tempZone")
METADATA_GUARD_CONFIG = {
'admin_only': True,
'prefixes': ['irods::']
}
coll.metadata['irods::metadata_guard'] = iRODSMeta('irods::metadata_guard', json.dumps(METADATA_GUARD_CONFIG))

obj = local_admin.data_objects.get(self.TEST_DATA_OBJECT_PATH)
obj.metadata.apply_atomic_operations( AVUOperation(operation='add', avu=iRODSMeta('irods::adminonly','adminonly')))

# admin should still be allowed to add metadata
self.assertEqual(obj.metadata['irods::adminonly'], iRODSMeta('irods::adminonly', 'adminonly'))

with iRODSSession(host='localhost', port=1247, user='alice', password='test', zone='tempZone') as unprivileged_user:
obj = unprivileged_user.data_objects.get(self.TEST_DATA_OBJECT_PATH)

# "unguarded::" not protected, so operation should succeed
obj.metadata.apply_atomic_operations( AVUOperation(operation='add', avu=iRODSMeta('unguarded::atr1','val1')))
self.assertEqual(obj.metadata['unguarded::atr1'], iRODSMeta('unguarded::atr1', 'val1'))

# "irods::" protected, so metadata add should fail
self.assertRaises(irods.exception.CAT_INSUFFICIENT_PRIVILEGE_LEVEL, lambda: obj.metadata.apply_atomic_operations( AVUOperation(operation='add', avu=iRODSMeta('irods::atr','val'))))

# "irods::" protected, so metadata delete should fail
self.assertRaises(irods.exception.CAT_INSUFFICIENT_PRIVILEGE_LEVEL, lambda: obj.metadata.apply_atomic_operations( AVUOperation(operation='remove', avu=iRODSMeta('irods::adminonly','adminonly'))))

def test_guard_atomic_operations_editor_list__issue_38(self):
# set metadata_guard config AVU on collection to admin_only: false and irods:: as protected prefix
# also, add alice user as editor
with iRODSSession(host='localhost', port=1247, user='rods', password='rods', zone='tempZone') as local_admin:
coll = local_admin.collections.get("/tempZone")
METADATA_GUARD_CONFIG = {
'admin_only': False,
'editors': [{'name': 'rods', 'type': 'user'}, {'name': 'alice', 'type': 'user'}],
'prefixes': ['irods::']
}
coll.metadata['irods::metadata_guard'] = iRODSMeta('irods::metadata_guard', json.dumps(METADATA_GUARD_CONFIG))

with iRODSSession(host='localhost', port=1247, user='alice', password='test', zone='tempZone') as unprivileged_user:
obj = unprivileged_user.data_objects.get(self.TEST_DATA_OBJECT_PATH)

# operation should succeed, as alice is set as an editor
obj.metadata.apply_atomic_operations( AVUOperation(operation='add', avu=iRODSMeta('irods::editorlist', 'editorlist')))
self.assertEqual(obj.metadata['irods::editorlist'], iRODSMeta('irods::editorlist', 'editorlist'))

# remove alice user from editor list
with iRODSSession(host='localhost', port=1247, user='rods', password='rods', zone='tempZone') as local_admin:
coll = local_admin.collections.get("/tempZone")
METADATA_GUARD_CONFIG = {
'admin_only': False,
'editors': [{'name': 'rods', 'type': 'user'}],
'prefixes': ['irods::']
}
coll.metadata['irods::metadata_guard'] = iRODSMeta('irods::metadata_guard', json.dumps(METADATA_GUARD_CONFIG))

with iRODSSession(host='localhost', port=1247, user='alice', password='test', zone='tempZone') as unprivileged_user:
obj = unprivileged_user.data_objects.get(self.TEST_DATA_OBJECT_PATH)

# this was set previously, make sure it is still the case
self.assertEqual(obj.metadata['irods::editorlist'], iRODSMeta('irods::editorlist', 'editorlist'))
# operation should fail, as test user is no longer in editor list
self.assertRaises(irods.exception.CAT_INSUFFICIENT_PRIVILEGE_LEVEL, lambda: obj.metadata.apply_atomic_operations( AVUOperation(operation='remove', avu=iRODSMeta('irods::editorlist', 'editorlist'))))


if __name__ == '__main__':
unittest.main()
2 changes: 2 additions & 0 deletions packaging/postinst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /etc/irods
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /usr/lib/irods/plugins/rule_engines/libirods_rule_engine_plugin-metadata_guard.so
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods/scripts
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods/scripts/irods_prc_tests
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods/scripts/irods_prc_tests/test_rule_engine_plugin_metadata_guard_atomic.py
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods/scripts/irods
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods/scripts/irods/test
chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/lib/irods/scripts/irods/test/test_rule_engine_plugin_metadata_guard.py
Expand Down
Loading

0 comments on commit 6d9ce70

Please sign in to comment.