Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

10 changed files with 18 additions and 218 deletions

3
.gitignore vendored
View File

@ -1,6 +1,3 @@
dist dist
venv venv
__pycache__ __pycache__
.venv
env
*.egg-info

View File

@ -75,8 +75,3 @@ build_acl(target_name, target_type, read=False, write=False, execute=False)
## Hacking/Tests ## Hacking/Tests
Functionality is tested through `pytest`. As it requires a certain test user to be present, easiest is to use the purpose-built container image. A wrapper is provided at `scripts/test.sh`. Functionality is tested through `pytest`. As it requires a certain test user to be present, easiest is to use the purpose-built container image. A wrapper is provided at `scripts/test.sh`.
## Repository
The authoritative source of this project is at https://git.com.de/Georg/pyacl.
A mirror is provided at https://github.com/tacerus/pyacl.

View File

@ -7,5 +7,3 @@ You may not use this work except in compliance with the Licence.
An English copy of the Licence is shipped in a file called LICENSE along with this applications source code. An English copy of the Licence is shipped in a file called LICENSE along with this applications source code.
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.
""" """
from .__version__ import __version__ as __version__

View File

@ -1,10 +0,0 @@
"""
Copyright 2024, pyacl contributors
Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence").
You may not use this work except in compliance with the Licence.
An English copy of the Licence is shipped in a file called LICENSE along with this applications source code.
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.
"""
__version__ = '1.0.5'

View File

@ -58,7 +58,8 @@ def reduce_entries(acl):
Return: List of entries converted to strings Return: List of entries converted to strings
""" """
entries = acl.to_any_text().decode().split() entries = acl.to_any_text().decode().split()
return [entry for entry in entries if entry not in DEFAULT_ENTRIES] entries = [entry for entry in entries if entry not in DEFAULT_ENTRIES]
return entries
def parse_permission_string(strpermission): def parse_permission_string(strpermission):
@ -133,7 +134,7 @@ def parse_acl(acl): # noqa PLR0912, FIXME: uncomplexify this
Return: Complete ACL map Return: Complete ACL map
""" """
permap = { permap = {
permission: False for permission in DEFAULT_PERMISSIONS permission: False for permission in DEFAULT_PERMISSIONS.keys()
} }
outmap = { outmap = {
group: { group: {
@ -165,7 +166,7 @@ def parse_acl(acl): # noqa PLR0912, FIXME: uncomplexify this
for tag_high, tag_low in LIBACL_TAGS.items(): for tag_high, tag_low in LIBACL_TAGS.items():
if tag_low == tag_type: if tag_low == tag_type:
lowmap = permap.copy() lowmap = permap.copy()
for permission in lowmap: for permission in lowmap.keys():
lowmap[permission] = getattr(permset, permission) lowmap[permission] = getattr(permset, permission)
outtag = tag_high outtag = tag_high
if tag_type == ACL_USER_OBJ: if tag_type == ACL_USER_OBJ:
@ -178,7 +179,7 @@ def parse_acl(acl): # noqa PLR0912, FIXME: uncomplexify this
outname = name outname = name
if outtag not in outmap: if outtag not in outmap:
outmap[outtag] = {} outmap[outtag] = {}
if len(outmap[outtag]) == 1 and next(iter(outmap[outtag].keys())) is None: if len(outmap[outtag]) == 1 and list(outmap[outtag].keys())[0] is None:
del outmap[outtag][None] del outmap[outtag][None]
outmap[outtag][outname] = lowmap outmap[outtag][outname] = lowmap
break break
@ -239,45 +240,7 @@ def apply_acl_to_path(acl, path):
""" """
if acl.valid() is not True: if acl.valid() is not True:
return ValueError('ACL is not ready to be applied.') return ValueError('ACL is not ready to be applied.')
return acl.applyto(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] and tag_type == existing_tag_type and ( 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): def read_acl_from_path(path):
@ -303,14 +266,3 @@ def parse_acl_from_path(path):
Return: Complete ACL map Return: Complete ACL map
""" """
return parse_acl(read_acl_from_path(path)) return parse_acl(read_acl_from_path(path))
# ignoring T201, debug function is expected to print
def debug_dump_acl_entries(acl):
for entry in acl:
print(f'tag: {entry.tag_type}', end='') # noqa T201
try:
print(f' qual: {entry.qualifier}') # noqa T201
except TypeError:
print() # noqa T201
print(f'read: {entry.permset.read}') # noqa T201

View File

@ -1,54 +1,20 @@
[build-system] [build-system]
requires = ["setuptools", "wheel"] requires = [
build-backend = "setuptools.build_meta" 'hatchling',
]
build-backend = 'hatchling.build'
[project] [project]
name = 'pyacl' name = 'pyacl'
description = 'High level abstractions over pylibacl' description = 'High level abstractions over pylibacl'
dynamic = ['license', 'readme', 'version'] version = '0.0.1'
authors = [ authors = [
{ name='Georg Pfuetzenreuter', email='georg+python@lysergic.dev' }, { name='Georg Pfuetzenreuter', email='georg+python@lysergic.dev' },
] ]
classifiers = [ readme = 'README.md'
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development",
"Typing :: Typed",
"Operating System :: POSIX :: Linux",
]
requires-python = '>=3.6' requires-python = '>=3.6'
[tool.hatch.build.targets.pyacl]
dependencies = [ dependencies = [
'pylibacl==0.5.4', 'pylibacl',
] ]
[project.optional-dependencies]
dev = [
"pytest",
"ruff",
"PyYAML"
]
[tool.setuptools]
include-package-data = true # include non-python files in the package (default)
[tool.setuptools.dynamic]
version = {attr = "pyacl.__version__"}
readme = {file = ["README.md"]}
[tool.setuptools.packages.find]
where = ["."]
exclude = ["scripts", "scripts.*", "docs.*", "tests", "tests.*", "ruff.toml"]

View File

@ -2,27 +2,18 @@
# https://docs.astral.sh/ruff/rules/ # https://docs.astral.sh/ruff/rules/
extend-select = [ extend-select = [
"A", # flake8-builtins "A", # flake8-builtins
"ARG", # flake8-unused-arguments
"BLE", # flake8-blind-except "BLE", # flake8-blind-except
"C4", # flake8-comprehensions "C4", # flake8-comprehensions
"COM", # flake8-commas "COM", # flake8-commas
"CPY001",# flake8-copyright
"E", # pycodestyle "E", # pycodestyle
"E261", # spaces before inline comments "E261", # spaces before inline comments
"ERA", # eradicate "ERA", # eradicate
"EXE", # flake8-executable "EXE", # flake8-executable
"FBT", # flake8-boolean-trap "FBT", # flake8-boolean-trap
"I", # isort "I", # isort
"INP", # flake8-no-pep420
"ISC", # flake8-implicit-str-concat "ISC", # flake8-implicit-str-concat
"N", # pep8-naming
"PL", # Pylint "PL", # Pylint
"RET", # flake8-return
"RSE", # flake8-raise
"RUF", # Ruff-specific rules
"S", # flake8-bandit "S", # flake8-bandit
"SIM", # flake8-simplify
"T20", # flake8-print
"UP", # pyupgrade "UP", # pyupgrade
"W", # pycodestyle "W", # pycodestyle
"YTT", # flake8-2020 "YTT", # flake8-2020
@ -37,12 +28,7 @@ preview = true
explicit-preview-rules = true explicit-preview-rules = true
[lint.per-file-ignores] [lint.per-file-ignores]
"pyacl/__init__.py" = ["PLC0414"] # allow explicit re-exports / avoid conflict with F401 "tests/*.py" = ["S101"] # allow "assert" in test suites
"tests/*.py" = [
"INP001", # tests do not need to be part of a package
"S101", # allow "assert" in test suites
"T201", # lazy printing is ok in tests
]
[lint.pydocstyle] [lint.pydocstyle]
convention = "pep257" convention = "pep257"

View File

@ -1,21 +0,0 @@
[metadata]
name = pyacl
version = attr: pyacl.__version__
author = Georg Pfuetzenreuter
author_email = georg+python@lysergic.dev
description = High level abstractions over pylibacl
license = EUPL-1.2
long_description = file: README.md, LICENSE
[options]
package_dir=
=.
packages=find:
python_requires = >=3.6
[options.packages.find]
exclude=
scripts*
tests*
*.toml
where=.

View File

@ -1,24 +0,0 @@
---
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,61 +23,22 @@ def load_yaml(file):
return list(data.items()) 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')) @mark.parametrize('aclin, aclout', load_yaml('matrix.yaml'))
def test_parse_acl_through_string(sample_file_with_acl, aclout): def test_parse_acl_through_string(sample_file_with_acl, aclin, aclout):
have = acl.parse_acl_from_path_via_string(sample_file_with_acl) have = acl.parse_acl_from_path_via_string(sample_file_with_acl)
assert aclout == have assert aclout == have
@mark.parametrize('aclin, aclout', load_yaml('matrix.yaml')) @mark.parametrize('aclin, aclout', load_yaml('matrix.yaml'))
def test_parse_acl_native(sample_file_with_acl, aclout): def test_parse_acl_native(sample_file_with_acl, aclin, aclout):
have = acl.parse_acl_from_path(sample_file_with_acl) have = acl.parse_acl_from_path(sample_file_with_acl)
assert aclout == have assert aclout == have
@mark.parametrize('mode', ['fresh', 'update'])
@mark.parametrize('scenario, data', load_yaml('matrix-apply.yaml')) @mark.parametrize('scenario, data', load_yaml('matrix-apply.yaml'))
def test_build_and_apply_acl(sample_file, mode, scenario, data): # noqa ARG001, scenario is only used for easier inspection of the dataset def test_build_and_apply_acl(sample_file, scenario, data):
built_acl = acl.build_acl(**data['args']) 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 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 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) read_acl = acl.parse_acl_from_path(sample_file)
assert read_acl == data['expect'] 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']