Implement ACL merging/updating #4

Merged
Georg merged 2 commits from feature/update_acl into main 2024-09-24 00:25:44 +02:00
3 changed files with 114 additions and 1 deletions

View File

@ -243,6 +243,46 @@ def apply_acl_to_path(acl, path):
acl.applyto(path)
def merge_acls(acl1, acl2):
"""
Example usage: merge_acls(posix1e.ACL, posix1e.ACL)
Return: posix1e.ACL
"""
acl3 = ACL(acl=acl1)
for entry in acl2:
tag_type = entry.tag_type
# keep existing entries which may only exist once
if tag_type not in [ACL_USER_OBJ, ACL_GROUP_OBJ, ACL_OTHER]:
# replace existing user/group entries with new ones if the uid/gid matches
if tag_type in [ACL_USER, ACL_GROUP, ACL_MASK]:
for existing_entry in acl3:
existing_tag_type = existing_entry.tag_type
if tag_type in [ACL_USER, ACL_GROUP, ACL_MASK]:
if tag_type == existing_tag_type:
if tag_type == ACL_MASK or entry.qualifier == existing_entry.qualifier:
acl3.delete_entry(existing_entry)
acl3.append(entry)
acl3.calc_mask()
return acl3
def update_acl_on_path(new_acl, path):
"""
Example usage: update_acl_on_path(posix1e.ACL, '/etc/foo.txt')
Return: None
"""
acl = merge_acls(read_acl_from_path(path), new_acl)
return apply_acl_to_path(acl, path)
def read_acl_from_path(path):
"""
Example usage: read_acl_from_path('/etc/foo.txt')
@ -266,3 +306,13 @@ def parse_acl_from_path(path):
Return: Complete ACL map
"""
return parse_acl(read_acl_from_path(path))
def debug_dump_acl_entries(acl):
for entry in acl:
print(f'tag: {entry.tag_type}', end='')
try:
print(f' qual: {entry.qualifier}')
except TypeError:
print()
print(f'read: {entry.permset.read}')

View File

@ -0,0 +1,24 @@
---
user:user:rw:
args:
target_name: user
target_type: user
read: false
write: true
expect:
user:
user:
read: false
write: true
execute: false
group: &null_allfalse
null:
read: true
write: false
execute: false
mask: &null_ro
null:
read: true
write: true
execute: false
other: *null_allfalse

View File

@ -23,6 +23,19 @@ def load_yaml(file):
return list(data.items())
def load_yamls(file1, file2):
data1 = load_yaml(file1)
data2 = load_yaml(file2)
out = []
for lentry in data1:
for i in range(len(lentry)):
ix = i - 1
out.append( ({'first': data1[ix], 'second': data2[ix]}) ) # noqa UP034, pytest mangles this in the parameters
return out
@mark.parametrize('aclin, aclout', load_yaml('matrix.yaml'))
def test_parse_acl_through_string(sample_file_with_acl, aclin, aclout):
have = acl.parse_acl_from_path_via_string(sample_file_with_acl)
@ -35,10 +48,36 @@ def test_parse_acl_native(sample_file_with_acl, aclin, aclout):
assert aclout == have
@mark.parametrize('mode', ['fresh', 'update'])
@mark.parametrize('scenario, data', load_yaml('matrix-apply.yaml'))
def test_build_and_apply_acl(sample_file, scenario, data):
def test_build_and_apply_acl(sample_file, mode, scenario, data):
built_acl = acl.build_acl(**data['args'])
assert len(list(built_acl)) == 5 # noqa PLR2004, this is the expected size of the built ACL
assert acl.apply_acl_to_path(built_acl, sample_file) is None
if mode == 'update':
assert acl.update_acl_on_path(built_acl, sample_file) is None
read_acl = acl.parse_acl_from_path(sample_file)
assert read_acl == data['expect']
@mark.parametrize('data', load_yamls('matrix-apply.yaml', 'matrix-apply-update.yaml') )
def test_build_and_update_acl(sample_file, data):
# we're updating the default instead of overwriting with apply
# one better way would be to have both "before" and "after" in matrix-apply-update.yaml instead of re-using the one for apply
# another would be to somehow adjust the apply map
data['first'][1]['expect']['group'][None]['read'] = True
data['first'][1]['expect']['other'][None]['read'] = True
built_acl1 = acl.build_acl(**data['first'][1]['args'])
assert len(list(built_acl1)) == 5 # noqa PLR2004, this is the expected size of the built ACL
built_acl2 = acl.build_acl(**data['second'][1]['args'])
assert len(list(built_acl2)) == 5 # noqa PLR2004
assert acl.update_acl_on_path(built_acl1, sample_file) is None
read_acl1 = acl.parse_acl_from_path(sample_file)
assert read_acl1 == data['first'][1]['expect']
assert acl.update_acl_on_path(built_acl2, sample_file) is None
read_acl2 = acl.parse_acl_from_path(sample_file)
assert read_acl2 == data['second'][1]['expect']