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:
parent
926b7e2c84
commit
18c3fefd1b
56
pyacl/acl.py
56
pyacl/acl.py
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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
23
tests/matrix-apply.yaml
Normal 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
|
@ -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']
|
||||||
|
Loading…
Reference in New Issue
Block a user