1
0
forked from Georg/pyacl

Implement ACL building/writing

Add basic functionality for writing ACLs with user and group permissions
to a file.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
This commit is contained in:
Georg Pfuetzenreuter 2024-09-17 23:31:08 +02:00
parent 926b7e2c84
commit 18c3fefd1b
Signed by untrusted user: Georg
GPG Key ID: 1ED2F138E7E6FF57
5 changed files with 103 additions and 6 deletions

View File

@ -8,7 +8,17 @@ An English copy of the Licence is shipped in a file called LICENSE along with th
You may obtain copies of the Licence in any of the official languages at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12. You may obtain copies of the Licence in any of the official languages at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12.
""" """
import posix1e from pwd import getpwnam
from posix1e import (
ACL,
ACL_GROUP,
ACL_GROUP_OBJ,
ACL_MASK,
ACL_OTHER,
ACL_USER,
ACL_USER_OBJ,
)
DEFAULT_ENTRIES = [ DEFAULT_ENTRIES = [
'u::rw-', 'u::rw-',
@ -32,6 +42,15 @@ DEFAULT_ENTRYTYPES = [
MAX_PERMBITS = 3 MAX_PERMBITS = 3
LIBACL_TAGS = {
'user': ACL_USER,
'group': ACL_GROUP,
'user_obj': ACL_USER_OBJ,
'group_obj': ACL_GROUP_OBJ,
'other': ACL_OTHER,
'mask': ACL_MASK,
}
def reduce_entries(acl): def reduce_entries(acl):
entries = acl.to_any_text().decode().split() entries = acl.to_any_text().decode().split()
@ -108,8 +127,41 @@ def parse_entries(acl):
return outmap return outmap
def buildacl(target_name, target_type, read=False, write=False, execute=False):
target_types = ['user', 'group']
if target_type not in target_types or not isinstance(target_name, str):
return ValueError('Invalid use of buildacl()')
myacl = ACL()
mytags = [tag for tag in LIBACL_TAGS if tag == target_type or tag in [ltag for ltag in LIBACL_TAGS if ltag not in target_types]]
aclmap = {
entry: myacl.append()
for entry in mytags
}
for entry, reference in aclmap.items():
reference.tag_type = LIBACL_TAGS[entry]
aclmap[target_type].qualifier = getpwnam(target_name).pw_uid
for pentry in ['mask', target_type]:
perms = aclmap[pentry].permset
perms.read = read
perms.write = write
perms.execute = execute
return myacl
def acltofile(acl, path):
if acl.valid() is not True:
return ValueError('ACL is not ready to be applied.')
acl.applyto(path)
def aclfromfile(path): def aclfromfile(path):
return posix1e.ACL(file=path) return ACL(file=path)
def entriesfromfile(path): def entriesfromfile(path):

View File

@ -20,6 +20,7 @@ extend-select = [
] ]
ignore = [ ignore = [
"E501", # line lengths "E501", # line lengths
"FBT002", # booleans as function arguments
"S603", # https://github.com/astral-sh/ruff/issues/4045 "S603", # https://github.com/astral-sh/ruff/issues/4045
"S607", # makes subprocess calls in test suite more portable "S607", # makes subprocess calls in test suite more portable
] ]
@ -31,3 +32,6 @@ explicit-preview-rules = true
[lint.pydocstyle] [lint.pydocstyle]
convention = "pep257" convention = "pep257"
[lint.isort]
force-wrap-aliases = true

View File

@ -14,9 +14,18 @@ from subprocess import run
import pytest import pytest
#@pytest.fixture(scope='session')
@pytest.fixture @pytest.fixture
def sample_file(tmp_path_factory, aclin): def sample_file(tmp_path_factory):
directory = tmp_path_factory.mktemp('sample_files')
file = directory / 'file_to_be_acled'
file.touch()
assert not file.read_text() # file should exist
yield file
rmtree(directory)
@pytest.fixture
def sample_file_with_acl(tmp_path_factory, aclin):
directory = tmp_path_factory.mktemp('sample_files') directory = tmp_path_factory.mktemp('sample_files')
file = directory / 'file_with_user_read_acl' file = directory / 'file_with_user_read_acl'
file.touch() file.touch()

23
tests/matrix-apply.yaml Normal file
View File

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

View File

@ -24,6 +24,15 @@ def load_yaml(file):
@mark.parametrize('aclin, aclout', load_yaml('matrix.yaml')) @mark.parametrize('aclin, aclout', load_yaml('matrix.yaml'))
def test_parse_acl(sample_file, aclin, aclout): def test_parse_acl(sample_file_with_acl, aclin, aclout):
have = acl.parsefromfile(sample_file) have = acl.parsefromfile(sample_file_with_acl)
assert aclout == have assert aclout == have
@mark.parametrize('scenario, data', load_yaml('matrix-apply.yaml'))
def test_build_and_apply_acl(sample_file, scenario, data):
built_acl = acl.buildacl(**data['args'])
assert len(list(built_acl)) == 5 # noqa PLR2004, this is the expected size of the built ACL
assert acl.acltofile(built_acl, sample_file) is None
read_acl = acl.parsefromfile(sample_file)
assert read_acl == data['expect']