Holger Frey
3 years ago
25 changed files with 1817 additions and 902 deletions
@ -0,0 +1,4 @@ |
|||||||
|
[flake8] |
||||||
|
select = C,E,F,W,S |
||||||
|
ignore = E203,W503 |
||||||
|
per-file-ignores = tests/*:S101 |
@ -0,0 +1,68 @@ |
|||||||
|
# ---> Python (custom) |
||||||
|
# Byte-compiled / optimized / DLL files |
||||||
|
__pycache__/ |
||||||
|
*.py[cod] |
||||||
|
*$py.class |
||||||
|
|
||||||
|
# C extensions |
||||||
|
*.so |
||||||
|
|
||||||
|
# Distribution / packaging |
||||||
|
.Python |
||||||
|
.venv/ |
||||||
|
.env/ |
||||||
|
env/ |
||||||
|
build/ |
||||||
|
develop-eggs/ |
||||||
|
dist/ |
||||||
|
downloads/ |
||||||
|
eggs/ |
||||||
|
.eggs/ |
||||||
|
lib/ |
||||||
|
lib64/ |
||||||
|
parts/ |
||||||
|
sdist/ |
||||||
|
var/ |
||||||
|
*.egg-info/ |
||||||
|
.installed.cfg |
||||||
|
*.egg |
||||||
|
|
||||||
|
# PyInstaller |
||||||
|
# Usually these files are written by a python script from a template |
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it. |
||||||
|
*.manifest |
||||||
|
*.spec |
||||||
|
|
||||||
|
# Installer logs |
||||||
|
pip-log.txt |
||||||
|
pip-delete-this-directory.txt |
||||||
|
|
||||||
|
# Unit test / coverage reports |
||||||
|
htmlcov/ |
||||||
|
.tox/ |
||||||
|
.coverage |
||||||
|
.coverage.* |
||||||
|
.cache |
||||||
|
nosetests.xml |
||||||
|
coverage.xml |
||||||
|
*,cover |
||||||
|
|
||||||
|
# Translations |
||||||
|
*.mo |
||||||
|
*.pot |
||||||
|
|
||||||
|
# Django stuff: |
||||||
|
*.log |
||||||
|
|
||||||
|
# Sphinx documentation |
||||||
|
docs/_build/ |
||||||
|
|
||||||
|
# PyBuilder |
||||||
|
target/ |
||||||
|
|
||||||
|
# Mac Stuff |
||||||
|
.DS_Store |
||||||
|
|
||||||
|
# Editors |
||||||
|
.vscode |
||||||
|
|
@ -0,0 +1,38 @@ |
|||||||
|
repos: |
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks |
||||||
|
rev: v2.4.0 |
||||||
|
hooks: |
||||||
|
- id: check-added-large-files |
||||||
|
- id: check-byte-order-marker |
||||||
|
- id: check-json |
||||||
|
- id: check-merge-conflict |
||||||
|
- id: check-toml |
||||||
|
- id: debug-statements |
||||||
|
- id: detect-private-key |
||||||
|
- repo: local |
||||||
|
hooks: |
||||||
|
- id: isort-project |
||||||
|
name: isort_project |
||||||
|
entry: isort -rc elab_users |
||||||
|
language: system |
||||||
|
pass_filenames: false |
||||||
|
- id: isort-test |
||||||
|
name: isort_test |
||||||
|
entry: isort -rc tests |
||||||
|
language: system |
||||||
|
pass_filenames: false |
||||||
|
- id: black |
||||||
|
name: black |
||||||
|
entry: black elab_users tests |
||||||
|
language: system |
||||||
|
pass_filenames: false |
||||||
|
- id: flake8 |
||||||
|
name: flake8 |
||||||
|
entry: flake8 elab_users tests |
||||||
|
language: system |
||||||
|
pass_filenames: false |
||||||
|
- id: pytest |
||||||
|
name: pytest |
||||||
|
entry: pytest tests |
||||||
|
pass_filenames: false |
||||||
|
language: system |
@ -0,0 +1,4 @@ |
|||||||
|
0.0.1 - first version |
||||||
|
---------------------- |
||||||
|
|
||||||
|
- setting up the project |
@ -0,0 +1,117 @@ |
|||||||
|
Contributing |
||||||
|
============ |
||||||
|
|
||||||
|
Contributions are welcome, and they are greatly appreciated! Every little bit |
||||||
|
helps, and credit will always be given. |
||||||
|
|
||||||
|
You can contribute in many ways: |
||||||
|
|
||||||
|
Types of Contributions |
||||||
|
---------------------- |
||||||
|
|
||||||
|
### Report Bugs |
||||||
|
|
||||||
|
Report bugs at https://git.cpi.imtek.uni-freiburg.de/CPI/elab-users.git/issues. |
||||||
|
|
||||||
|
If you are reporting a bug, please include: |
||||||
|
|
||||||
|
* Your operating system name and version. |
||||||
|
* Any details about your local setup that might be helpful in troubleshooting. |
||||||
|
* Detailed steps to reproduce the bug. |
||||||
|
|
||||||
|
### Fix Bugs |
||||||
|
|
||||||
|
Look through the GitHub issues for bugs. Anything tagged with "bug" and "help |
||||||
|
wanted" is open to whoever wants to implement it. |
||||||
|
|
||||||
|
### Implement Features |
||||||
|
|
||||||
|
Look through the GitHub issues for features. Anything tagged with "enhancement" |
||||||
|
and "help wanted" is open to whoever wants to implement it. |
||||||
|
|
||||||
|
### Write Documentation |
||||||
|
|
||||||
|
Elab Users could always use more documentation, whether as part of the |
||||||
|
official Elab Users docs, in docstrings, or even on the web in blog posts, |
||||||
|
articles, and such. |
||||||
|
|
||||||
|
### Submit Feedback |
||||||
|
|
||||||
|
The best way to send feedback is to file an issue at https://git.cpi.imtek.uni-freiburg.de/CPI/elab-users.git/issues. |
||||||
|
|
||||||
|
If you are proposing a feature: |
||||||
|
|
||||||
|
* Explain in detail how it would work. |
||||||
|
* Keep the scope as narrow as possible, to make it easier to implement. |
||||||
|
* Remember that this is a volunteer-driven project, and that contributions |
||||||
|
are welcome :) |
||||||
|
|
||||||
|
Get Started! |
||||||
|
------------ |
||||||
|
|
||||||
|
Ready to contribute? Here's how to set up `elab_users` for local development. |
||||||
|
|
||||||
|
1. Fork the `elab_users` repo on GitHub. |
||||||
|
2. Clone your fork locally:: |
||||||
|
|
||||||
|
`$ git clone git@github.com:your_name_here/elab_users.git` |
||||||
|
|
||||||
|
3. Install your local copy into a virtualenv. |
||||||
|
|
||||||
|
`$ cd elab_users/` |
||||||
|
`$ make devenv` |
||||||
|
|
||||||
|
4. Create a branch for local development:: |
||||||
|
|
||||||
|
`$ git checkout -b name-of-your-bugfix-or-feature` |
||||||
|
|
||||||
|
Now you can make your changes locally. |
||||||
|
|
||||||
|
5. When you're done making changes, check that your changes passes the linters and the |
||||||
|
tests, including testing other Python versions with tox:: |
||||||
|
|
||||||
|
``` |
||||||
|
$ make lint |
||||||
|
$ make coverage |
||||||
|
$ make tox |
||||||
|
``` |
||||||
|
|
||||||
|
6. Commit your changes and push your branch to GitHub:: |
||||||
|
|
||||||
|
``` |
||||||
|
$ git add . |
||||||
|
$ git commit -m "Your detailed description of your changes." |
||||||
|
$ git push origin name-of-your-bugfix-or-feature |
||||||
|
``` |
||||||
|
|
||||||
|
7. Submit a pull request through the GitHub website. |
||||||
|
|
||||||
|
Pull Request Guidelines |
||||||
|
----------------------- |
||||||
|
|
||||||
|
Before you submit a pull request, check that it meets these guidelines: |
||||||
|
|
||||||
|
1. The pull request should include tests. |
||||||
|
2. If the pull request adds functionality, the docs should be updated. Put |
||||||
|
your new functionality into a function with a docstring, and add the |
||||||
|
feature to the list in README.md and CHANGES.md |
||||||
|
|
||||||
|
Tips |
||||||
|
---- |
||||||
|
|
||||||
|
To run a quick set of tests without coverage report |
||||||
|
|
||||||
|
$ make test |
||||||
|
|
||||||
|
Deploying |
||||||
|
--------- |
||||||
|
|
||||||
|
A reminder for the maintainers on how to deploy. |
||||||
|
Bump the version in `elab_users/__init__.py` and |
||||||
|
make sure all your changes are committed (including an entry in CHANGES.md). |
||||||
|
|
||||||
|
$ git tag <new version> |
||||||
|
$ git push |
||||||
|
$ git push --tags |
||||||
|
$ flit publish |
||||||
|
|
@ -0,0 +1,10 @@ |
|||||||
|
/* |
||||||
|
* ---------------------------------------------------------------------------- |
||||||
|
* "THE BEER-WARE LICENSE" (Revision 42): |
||||||
|
* frey@imtek.de wrote this file. As long as you retain this notice you |
||||||
|
* can do whatever you want with this stuff. If we meet some day, and you think |
||||||
|
* this stuff is worth it, you can buy me a beer in return. Holger Frey |
||||||
|
* ---------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
|
@ -0,0 +1,90 @@ |
|||||||
|
.PHONY: clean clean-test clean-pyc clean-build docs help |
||||||
|
.DEFAULT_GOAL := help |
||||||
|
|
||||||
|
define BROWSER_PYSCRIPT |
||||||
|
import os, webbrowser, sys |
||||||
|
|
||||||
|
try: |
||||||
|
from urllib import pathname2url |
||||||
|
except: |
||||||
|
from urllib.request import pathname2url |
||||||
|
|
||||||
|
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) |
||||||
|
endef |
||||||
|
export BROWSER_PYSCRIPT |
||||||
|
|
||||||
|
define PRINT_HELP_PYSCRIPT |
||||||
|
import re, sys |
||||||
|
|
||||||
|
for line in sys.stdin: |
||||||
|
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) |
||||||
|
if match: |
||||||
|
target, help = match.groups() |
||||||
|
print("%-20s %s" % (target, help)) |
||||||
|
endef |
||||||
|
export PRINT_HELP_PYSCRIPT |
||||||
|
|
||||||
|
BROWSER := python -c "$$BROWSER_PYSCRIPT" |
||||||
|
|
||||||
|
help: |
||||||
|
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) |
||||||
|
|
||||||
|
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
||||||
|
|
||||||
|
clean-build: ## remove build artifacts
|
||||||
|
rm -fr build/ |
||||||
|
rm -fr dist/ |
||||||
|
rm -fr .eggs/ |
||||||
|
find . -name '*.egg-info' -exec rm -fr {} + |
||||||
|
find . -name '*.egg' -exec rm -f {} + |
||||||
|
|
||||||
|
clean-pyc: ## remove Python file artifacts
|
||||||
|
find . -name '*.pyc' -exec rm -f {} + |
||||||
|
find . -name '*.pyo' -exec rm -f {} + |
||||||
|
find . -name '*~' -exec rm -f {} + |
||||||
|
find . -name '__pycache__' -exec rm -fr {} + |
||||||
|
|
||||||
|
clean-test: ## remove test and coverage artifacts
|
||||||
|
rm -fr .pytest_cache/ |
||||||
|
rm -fr .tox/ |
||||||
|
rm -f .coverage |
||||||
|
rm -fr htmlcov/ |
||||||
|
|
||||||
|
lint: ## reformat with black and check style with flake8
|
||||||
|
isort elab_users |
||||||
|
isort tests |
||||||
|
black elab_users tests |
||||||
|
flake8 elab_users tests |
||||||
|
|
||||||
|
test: lint ## run tests quickly with the default Python
|
||||||
|
pytest tests -x --disable-warnings -m "not fun" |
||||||
|
|
||||||
|
testall: lint ## run tests quickly with the default Python
|
||||||
|
pytest tests |
||||||
|
|
||||||
|
coverage: lint ## full test suite, check code coverage and open coverage report
|
||||||
|
pytest tests --cov=elab_users -m "fun" |
||||||
|
coverage html |
||||||
|
$(BROWSER) htmlcov/index.html |
||||||
|
|
||||||
|
tox: ## run fully isolated tests with tox
|
||||||
|
tox |
||||||
|
|
||||||
|
install: ## install updated project.toml with flint
|
||||||
|
flit install --pth-file |
||||||
|
|
||||||
|
devenv: ## setup development environment
|
||||||
|
python3 -m venv --prompt elab_users .venv |
||||||
|
.venv/bin/pip3 install --upgrade pip |
||||||
|
.venv/bin/pip3 install flit |
||||||
|
.venv/bin/flit install --pth-file |
||||||
|
|
||||||
|
repo: devenv ## complete project setup with development environment and git repo
|
||||||
|
git init . |
||||||
|
git branch -m main |
||||||
|
git add . |
||||||
|
git commit -m "import of project template" |
||||||
|
git remote add origin https://git.cpi.imtek.uni-freiburg.de/CPI/elab-users.git |
||||||
|
git push -u origin main --no-verify |
||||||
|
|
||||||
|
.venv/bin/pre-commit install --install-hooks |
@ -1,848 +0,0 @@ |
|||||||
[groups] |
|
||||||
administrators = JuergenRuehe, OswaldPrucker |
|
||||||
alumni = AlexeyKopyshev, AndreasBoenisch, AndreasEver, AnkeWoerz, AnneLoesche, ArthurMartens, ArulGeetha, CamillaOestevold, CanerKaganaslan, ChristianSchuh, ChristineBunte, CkPandiyarajan, CleoStannard, FanWu, GerhardBaaken, GinoRodriguez, GuillermoBenites, HeikeHaller, IrenaEipert, JacobBelardi, JenniferPfau, JoachimLauterwasser, JohannesBaader, KatrinMoosmann, KeLi, KerstinSchuh, KimberlySimancas, MarcoArmbruster, MariaVoehringer, MariaVohringer, MartinRendl, MartinVellinger, MartinaAuerswald, MatthiasLischka, MessRechner, MichaelaFrase, MiriamScheckenbach, MonicaPerez, NinoLomadze, Nongluck, OliverDornfeld, PeterZahn, PhilippDiefenthaler, PhilippWollermann, RebeccaBlell, RodrigoNavarro, SaraFuchs, SebastianBoehmer, SebastianSebald, SimonBodendorfer, SimonSchuster, SirasaYodmongkol, ThidaratWangkam, TobiasHeitzler, TobiasKoenig, TristanBourrel, UlrikeRiehle, ViVek, VinicioCarias, WolfgangEhm, YnSekhar, ZouStaarter |
|
||||||
users = AlexanderDietz, AliciaMalekLuz, AndreasMader, AnnaSchuler, AnneBuderer, ChristophScheibelein, CrispinAmiriNaini, DanielaMoessner, DavidBoschert, DavidSchwaerzle, EstherRiga, FrankScherag, FranziskaDorner, GregorOsterwinter, HeidiPerez, HolgerFrey, JanNiklasSchoenberg, JonGreen, KarenLienkamp, KatyaSergeeva, LauraHerrera, MalwinaPajestka, MaraFlorea, MarcZinggeler, MarcelHoffmann, MarcelRothfelder, MartinKoerner, MartinSchoenstein, MatthiasMenzel, MelanieEichhorn, MichaelHenze, MonikaKurowska, MostafaMahmoud, NataliaSchatz, NicoleBirsner, NilsKorf, PengZou, PetraHettich, PhilipKotrade, RaduCristianMutihac, RomanErath, SamarKazan, SaschaEngel, SebastianBonaus, ShararehSahneh, SureshReddyBanda, ThananthornKanokwijitsilp, ThomasBrandstetter, TianyangZheng, VanessaWeiss, VitaliyKondrashov, WibkeHartleb, XiaoqiangHou, ZhuolingDeng |
|
||||||
restricted = BeniPrasser, JuliaSaar, SimonZunker, UrmilShah, YongZhou |
|
||||||
|
|
||||||
[cpi:/] |
|
||||||
@administrators= r |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[AlexanderDietz:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
AlexanderDietz= r |
|
||||||
|
|
||||||
[AlexeyKopyshev:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[AliciaMalekLuz:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
AliciaMalekLuz= r |
|
||||||
|
|
||||||
[AndreasBoenisch:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[AndreasEver:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[AndreasEvers:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
UrmilShah = r |
|
||||||
|
|
||||||
[AndreasMader:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
AndreasMader= r |
|
||||||
|
|
||||||
[AnkeWoerz:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[AnnaSchuler:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
AnnaSchuler= r |
|
||||||
|
|
||||||
[AnneBuderer:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
AnneBuderer= r |
|
||||||
|
|
||||||
[AnneLoesche:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[AnselmHoppmann:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[ArthurMartens:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[ArulGeetha:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[BeniPrasser:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
BeniPrasser= r |
|
||||||
|
|
||||||
[CamillaOestevold:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[CanerKaganaslan:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[ChristianSchuh:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[ChristineBunte:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[ChristophScheibelein:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
ChristophScheibelein= r |
|
||||||
|
|
||||||
[CkPandiyarajan:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[CleoStannard:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[CrispinAmiriNaini:/] |
|
||||||
@administrators= rw |
|
||||||
@users = |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
CrispinAmiriNaini= r |
|
||||||
|
|
||||||
[DanielaMoessner:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
DanielaMoessner= r |
|
||||||
|
|
||||||
[DavidBoschert:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
DavidBoschert= r |
|
||||||
|
|
||||||
[DavidSchwaerzle:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
DavidSchwaerzle= r |
|
||||||
|
|
||||||
[DennisTrenkle:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[DingdingHe:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[EstherRiga:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
EstherRiga= r |
|
||||||
|
|
||||||
[FanWu:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[FrankScherag:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
FrankScherag= r |
|
||||||
|
|
||||||
[FranziskaDorner:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
FranziskaDorner= r |
|
||||||
|
|
||||||
[GerhardBaaken:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[GinoRodriguez:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[GregorOsterwinter:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
GregorOsterwinter= r |
|
||||||
|
|
||||||
[GuillermoBenites:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[HeidiPerez:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
HeidiPerez= r |
|
||||||
|
|
||||||
[HeikeHaller:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[HolgerFrey:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
UrmilShah = r |
|
||||||
HolgerFrey= rw |
|
||||||
|
|
||||||
[IrenaEipert:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[JacobBelardi:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[JanNiklasSchoenberg:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
JanNiklasSchoenberg= r |
|
||||||
|
|
||||||
[JenniferPfau:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[JoachimLauterwasser:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[JohannesBaader:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[JonGreen:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
JonGreen= r |
|
||||||
|
|
||||||
[JonasGroten:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[JuergenRuehe:/] |
|
||||||
@administrators= rw |
|
||||||
@users = |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
JuergenRuehe= r |
|
||||||
|
|
||||||
[JuliaSaar:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
JuliaSaar= r |
|
||||||
|
|
||||||
[KarenLienkamp:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
KarenLienkamp= r |
|
||||||
|
|
||||||
[KatrinMoosmann:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[KatyaSergeeva:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
KatyaSergeeva= r |
|
||||||
|
|
||||||
[KeLi:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[KerstinSchuh:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[KimberlySimancas:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[LauraHerrera:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
LauraHerrera= r |
|
||||||
|
|
||||||
[MalwinaPajestka:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MalwinaPajestka= r |
|
||||||
|
|
||||||
[MaraFlorea:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MaraFlorea= r |
|
||||||
|
|
||||||
[MarcZinggeler:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MarcZinggeler= r |
|
||||||
|
|
||||||
[MarcelHoffmann:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MarcelHoffmann= r |
|
||||||
|
|
||||||
[MarcelRothfelder:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MarcelRothfelder= r |
|
||||||
|
|
||||||
[MarcoArmbruster:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MariaVoehringer:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MartinKoerner:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MartinKoerner= r |
|
||||||
|
|
||||||
[MartinMarazita:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MartinRendl:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MartinSchoenstein:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MartinSchoenstein= r |
|
||||||
|
|
||||||
[MartinVellinger:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MartinaAuerswald:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MatthiasLischka:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MatthiasMenzel:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MatthiasMenzel= r |
|
||||||
|
|
||||||
[MaxMustermann:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MelanieEichhorn:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MelanieEichhorn= r |
|
||||||
|
|
||||||
[MessRechner:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MichaelHenze:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MichaelHenze= r |
|
||||||
|
|
||||||
[MichaelaFrase:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MiriamScheckenbach:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MonicaPerez:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[MonikaKurowska:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MonikaKurowska= r |
|
||||||
|
|
||||||
[MostafaMahmoud:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
MostafaMahmoud= r |
|
||||||
|
|
||||||
[NataliaSchatz:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
NataliaSchatz= r |
|
||||||
|
|
||||||
[NicolasSchorr:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[NicoleBirsner:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
NicoleBirsner= r |
|
||||||
|
|
||||||
[NilsKorf:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
NilsKorf= r |
|
||||||
|
|
||||||
[NinoLomadze:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[Nongluck:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[OliverDornfeld:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[OswaldPrucker:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[PengZou:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
PengZou= r |
|
||||||
|
|
||||||
[PeterZahn:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[PetraHettich:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
PetraHettich= r |
|
||||||
|
|
||||||
[PhilipKotrade:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
PhilipKotrade= r |
|
||||||
|
|
||||||
[PhilippDiefenthaler:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[RaduCristianMutihac:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
RaduCristianMutihac= r |
|
||||||
|
|
||||||
[RebeccaBlell:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[RodrigoNavarro:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[RomanErath:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
RomanErath= r |
|
||||||
|
|
||||||
[SamarKazan:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
SamarKazan= r |
|
||||||
|
|
||||||
[SaraFuchs:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[SaschaEngel:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
SaschaEngel= r |
|
||||||
|
|
||||||
[SebastianBoehmer:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[SebastianBonaus:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
SebastianBonaus= r |
|
||||||
|
|
||||||
[ShararehSahneh:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
ShararehSahneh= r |
|
||||||
|
|
||||||
[SimonBodendorfer:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[SimonEbner:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[SimonSchuster:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[SimonZunker:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
SimonZunker= r |
|
||||||
|
|
||||||
[SirasaYodmongkol:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[SureshReddyBanda:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
SureshReddyBanda= r |
|
||||||
|
|
||||||
[ThananthornKanokwijitsilp:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
ThananthornKanokwijitsilp= r |
|
||||||
|
|
||||||
[ThidaratWangkam:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[ThomasBrandstetter:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
ThomasBrandstetter= r |
|
||||||
|
|
||||||
[TianyangZheng:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
TianyangZheng= r |
|
||||||
|
|
||||||
[TobiasHeitzler:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[TobiasKoenig:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[TristanBourrel:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[UlrikeRiehle:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[UrmilShah:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
UrmilShah= r |
|
||||||
|
|
||||||
[VanessaWeiss:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
VanessaWeiss= r |
|
||||||
|
|
||||||
[ViVek:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[VinicioCarias:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[VitaliyKondrashov:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
VitaliyKondrashov= r |
|
||||||
SimonZunker = r |
|
||||||
|
|
||||||
[WibkeHartleb:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
WibkeHartleb= r |
|
||||||
|
|
||||||
[WolfgangEhm:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[XiaoqiangHou:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
XiaoqiangHou= r |
|
||||||
|
|
||||||
[YnSekhar:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
|
|
||||||
[YongZhou:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
YongZhou= r |
|
||||||
|
|
||||||
[ZhuolingDeng:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
||||||
ZhuolingDeng= r |
|
||||||
|
|
||||||
[ZouStaarter:/] |
|
||||||
@administrators= rw |
|
||||||
@users = r |
|
||||||
@restricted = |
|
||||||
@alumni = |
|
@ -0,0 +1,206 @@ |
|||||||
|
""" Elab Users |
||||||
|
|
||||||
|
Manage elab (svn) users |
||||||
|
""" |
||||||
|
|
||||||
|
__version__ = "0.0.1" |
||||||
|
|
||||||
|
import os |
||||||
|
import sys |
||||||
|
import argparse |
||||||
|
import subprocess # noqa: S404 |
||||||
|
from pathlib import Path |
||||||
|
|
||||||
|
from .authz import AuthzConfigParser |
||||||
|
from .constants import ( |
||||||
|
USERS, |
||||||
|
ADMINS, |
||||||
|
ALUMNI, |
||||||
|
READ_ACL, |
||||||
|
WRITE_ACL, |
||||||
|
RESTRICTED, |
||||||
|
SVN_SUFFIX, |
||||||
|
AUTHZ_FILE_NAME, |
||||||
|
HTPWD_FILE_NAME, |
||||||
|
) |
||||||
|
|
||||||
|
SVN_REPOS_PATH = Path(os.getenv("SVN_REPOS_PATH", default=".")).resolve() |
||||||
|
|
||||||
|
COMMANDS = ["user", "group", "add", "restricted", "retire", "password"] |
||||||
|
|
||||||
|
|
||||||
|
def get_config(svn_dir=SVN_REPOS_PATH, authz=AUTHZ_FILE_NAME): |
||||||
|
authz_path = Path(svn_dir) / authz |
||||||
|
if not authz_path.is_file(): |
||||||
|
sys.exit(f"Could not find authz file at {authz_path}") |
||||||
|
return AuthzConfigParser.from_file(authz_path) |
||||||
|
|
||||||
|
|
||||||
|
def list_users(svn_dir=SVN_REPOS_PATH, authz=AUTHZ_FILE_NAME): |
||||||
|
"""list all users""" |
||||||
|
config = get_config(svn_dir, authz) |
||||||
|
groups = config.group_users() |
||||||
|
for name, usernames in groups.items(): |
||||||
|
print(f"Users in group '{name}':") |
||||||
|
for name in sorted(usernames): |
||||||
|
print(f" {name}") |
||||||
|
|
||||||
|
|
||||||
|
def show_group_info(groupname, svn_dir=SVN_REPOS_PATH, authz=AUTHZ_FILE_NAME): |
||||||
|
"""show group information""" |
||||||
|
config = get_config(svn_dir, authz) |
||||||
|
groups = config.group_users() |
||||||
|
if groupname not in groups: |
||||||
|
sys.exit(f"Group '{groupname}' not found in authz file") |
||||||
|
print(f"Users in group '{groupname}':") |
||||||
|
for name in sorted(groups[groupname]): |
||||||
|
print(f" {name}") |
||||||
|
|
||||||
|
|
||||||
|
def add_new_user( |
||||||
|
username, |
||||||
|
group, |
||||||
|
svn_dir=SVN_REPOS_PATH, |
||||||
|
authz=AUTHZ_FILE_NAME, |
||||||
|
htpwd=HTPWD_FILE_NAME, |
||||||
|
handler=subprocess, |
||||||
|
): |
||||||
|
"""add a user, restricted or regular""" |
||||||
|
config = get_config(svn_dir, authz) |
||||||
|
if username in config.elab_users: |
||||||
|
sys.exit(f"Username '{username}' already in use") |
||||||
|
if username.lower() in {i.name.lower() for i in svn_dir.iterdir()}: |
||||||
|
sys.exit(f"Username '{username}' not allowed") |
||||||
|
user = config.add_journal_acl_for(username, group) |
||||||
|
user.create_new_repository(svn_dir, handler) |
||||||
|
password = user.set_new_password(svn_dir / htpwd, handler=handler) |
||||||
|
print("New password for :") |
||||||
|
print(f"username: {username}") |
||||||
|
print(f"password: {password}") |
||||||
|
print(f"url: https://svn.cpi.imtek.uni-freiburg.de/{username}") |
||||||
|
config.write_to_file() |
||||||
|
|
||||||
|
|
||||||
|
def retire_user( |
||||||
|
username, |
||||||
|
svn_dir=SVN_REPOS_PATH, |
||||||
|
authz=AUTHZ_FILE_NAME, |
||||||
|
htpwd=HTPWD_FILE_NAME, |
||||||
|
handler=subprocess, |
||||||
|
): |
||||||
|
config = get_config(svn_dir, authz) |
||||||
|
if username not in config.elab_users: |
||||||
|
sys.exit(f"User {username} not found.") |
||||||
|
user = config.elab_users[username] |
||||||
|
if user.group == ALUMNI: |
||||||
|
sys.exit(f"User '{username}' is already in group '{ALUMNI}'") |
||||||
|
if user.group == ADMINS: |
||||||
|
sys.exit( |
||||||
|
( |
||||||
|
f"User '{username}' is in group '{ADMINS}', " |
||||||
|
f"will not moved to '{ALUMNI}'" |
||||||
|
) |
||||||
|
) |
||||||
|
config.move_user_to_alumni(username) |
||||||
|
config.write_to_file() |
||||||
|
user.delete_password(svn_dir / htpwd, handler=handler) |
||||||
|
print(f"Moved user {username} to alumni") |
||||||
|
|
||||||
|
|
||||||
|
def change_password( |
||||||
|
username, |
||||||
|
svn_dir=SVN_REPOS_PATH, |
||||||
|
authz=AUTHZ_FILE_NAME, |
||||||
|
htpwd=HTPWD_FILE_NAME, |
||||||
|
handler=subprocess, |
||||||
|
): |
||||||
|
config = get_config(svn_dir, authz) |
||||||
|
if username not in config.elab_users: |
||||||
|
sys.exit(f"User {username} not found.") |
||||||
|
user = config.elab_users[username] |
||||||
|
password = user.set_new_password(svn_dir / htpwd, handler=handler) |
||||||
|
print("New password for :") |
||||||
|
print(f"username: {username}") |
||||||
|
print(f"password: {password}") |
||||||
|
|
||||||
|
|
||||||
|
def show_user_info(username, svn_dir=SVN_REPOS_PATH, authz=AUTHZ_FILE_NAME): |
||||||
|
config = get_config(svn_dir, authz) |
||||||
|
if username not in config.elab_users: |
||||||
|
sys.exit(f"User {username} not found.") |
||||||
|
|
||||||
|
user = config.elab_users[username] |
||||||
|
print(f"User {user.name} is in group '{user.group}':") |
||||||
|
|
||||||
|
# print the write acls for a user |
||||||
|
if user.group == ADMINS: |
||||||
|
print(" Write access is granted to all journals.") |
||||||
|
elif user.write_acl: |
||||||
|
write_acl = [item + SVN_SUFFIX for item in user.write_acl] |
||||||
|
print(" Write access is granted to:", ", ".join(write_acl)) |
||||||
|
else: |
||||||
|
print(" Write access is NOT granted to any journals") |
||||||
|
|
||||||
|
# print the read acls for a user |
||||||
|
if user.group == ADMINS: |
||||||
|
print(" Read access is granted to all journals.") |
||||||
|
elif user.group == USERS: |
||||||
|
print(" Read access is granted to (nearly) all journals.") |
||||||
|
elif user.read_acl: |
||||||
|
read_acl = [item + SVN_SUFFIX for item in user.read_acl] |
||||||
|
print(" Read access is granted to:", ", ".join(read_acl)) |
||||||
|
else: |
||||||
|
print(" Read access is NOT granted to any journals") |
||||||
|
|
||||||
|
journal = config.get_journal_info(username) |
||||||
|
print(f"Labjournal {username}{SVN_SUFFIX}") |
||||||
|
|
||||||
|
# print the write acls for a journal |
||||||
|
if journal[WRITE_ACL]: |
||||||
|
print(" Write access granted to:", ", ".join(journal[WRITE_ACL])) |
||||||
|
else: |
||||||
|
print(" No write access granted to anybody") |
||||||
|
|
||||||
|
# print the read acls for a journal |
||||||
|
if journal[READ_ACL]: |
||||||
|
print(" Read access granted to:", ", ".join(journal[READ_ACL])) |
||||||
|
else: |
||||||
|
print(" No read access granted to anybody") |
||||||
|
|
||||||
|
|
||||||
|
def main( |
||||||
|
svn_dir=SVN_REPOS_PATH, |
||||||
|
authz=AUTHZ_FILE_NAME, |
||||||
|
htpwd=HTPWD_FILE_NAME, |
||||||
|
handler=subprocess, |
||||||
|
cli_args=None, |
||||||
|
): |
||||||
|
parser = argparse.ArgumentParser(prog="elab-users") |
||||||
|
parser.add_argument( |
||||||
|
"command", |
||||||
|
nargs="?", |
||||||
|
help="one of the commands: [" + ", ".join(COMMANDS) + "]", |
||||||
|
) |
||||||
|
parser.add_argument( |
||||||
|
"name", nargs="?", help="user or group to perform the command on" |
||||||
|
) |
||||||
|
args = parser.parse_args(cli_args) |
||||||
|
print(args.command) |
||||||
|
print(args.name) |
||||||
|
|
||||||
|
if not args.command: |
||||||
|
list_users(svn_dir, authz) |
||||||
|
elif args.command.lower() not in COMMANDS: |
||||||
|
show_user_info(args.command, svn_dir, authz) |
||||||
|
elif args.command.lower() == "user": |
||||||
|
show_user_info(args.name, svn_dir, authz) |
||||||
|
elif args.command.lower() == "group": |
||||||
|
show_group_info(args.name, svn_dir, authz) |
||||||
|
elif args.command.lower() == "add": |
||||||
|
add_new_user(args.name, USERS, svn_dir, authz, htpwd, handler) |
||||||
|
elif args.command.lower() == "restricted": |
||||||
|
add_new_user(args.name, RESTRICTED, svn_dir, authz, htpwd, handler) |
||||||
|
elif args.command.lower() == "retire": |
||||||
|
retire_user(args.name, svn_dir, authz, htpwd, handler) |
||||||
|
elif args.command.lower() == "password": |
||||||
|
change_password(args.name, svn_dir, authz, htpwd, handler) |
@ -0,0 +1,183 @@ |
|||||||
|
import re |
||||||
|
import configparser |
||||||
|
|
||||||
|
from .users import ElabUser |
||||||
|
from .constants import ( |
||||||
|
USERS, |
||||||
|
ADMINS, |
||||||
|
ALUMNI, |
||||||
|
READ_ACL, |
||||||
|
WRITE_ACL, |
||||||
|
RESTRICTED, |
||||||
|
SVN_SUFFIX, |
||||||
|
GROUP_DEFAULTS, |
||||||
|
) |
||||||
|
|
||||||
|
RE_LIST_SEPARATORS = re.compile("[\t ,;]+") |
||||||
|
|
||||||
|
|
||||||
|
def format_ini_option(key, value): |
||||||
|
"""formats a key value pair for writing an ini file""" |
||||||
|
return " = ".join((key, str(value).replace("\n", "\n\t"))) |
||||||
|
|
||||||
|
|
||||||
|
class AuthzConfigParser(configparser.ConfigParser): |
||||||
|
"""custom functions for parsing the "authz" file as used at cpi |
||||||
|
|
||||||
|
there is a dict of users defined, the journals themselves can be accessed |
||||||
|
via the sections functionality of the ConfigParser base class |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
"""initialization of the class""" |
||||||
|
self.elab_users = {} |
||||||
|
self.original_path = None |
||||||
|
super().__init__() |
||||||
|
|
||||||
|
def optionxform(self, value): |
||||||
|
"""reset the method to use cases sensitive names""" |
||||||
|
return str(value) |
||||||
|
|
||||||
|
def read(self, path): |
||||||
|
"""set up the acl defaults after reading the file""" |
||||||
|
super().read(path) |
||||||
|
self.original_path = path |
||||||
|
self._extract_user_info_from_config() |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def from_file(cls, path): |
||||||
|
instance = cls() |
||||||
|
instance.read(path) |
||||||
|
return instance |
||||||
|
|
||||||
|
def write_to_file(self, path=None): |
||||||
|
path = path or self.original_path |
||||||
|
if not path: |
||||||
|
raise IOError("No path specified") |
||||||
|
with open(path, "w") as filehandle: |
||||||
|
self.write(filehandle) |
||||||
|
|
||||||
|
def write(self, fp): |
||||||
|
"""Write an .ini-format representation of the configuration state. |
||||||
|
|
||||||
|
this is adapted from the original library file. changes: |
||||||
|
- no default section |
||||||
|
- group-section at top |
||||||
|
- rest of section sorted by name |
||||||
|
""" |
||||||
|
sorted_keys = sorted(self._sections.keys()) |
||||||
|
sorting = ["groups"] |
||||||
|
sorting.extend([k for k in sorted_keys if k != "groups"]) |
||||||
|
for section in sorting: |
||||||
|
fp.write("[%s]\n" % section) |
||||||
|
acls = { |
||||||
|
k: v |
||||||
|
for k, v in self._sections[section].items() |
||||||
|
if k != "__name__" |
||||||
|
} |
||||||
|
if section != "groups": |
||||||
|
for group in (ADMINS, USERS, RESTRICTED, ALUMNI): |
||||||
|
group_id = "@" + group |
||||||
|
acl_value = acls.pop(group_id, GROUP_DEFAULTS[group]) |
||||||
|
key = format_ini_option(group_id, acl_value) |
||||||
|
fp.write("%s\n" % (key)) |
||||||
|
for (key, value) in acls.items(): |
||||||
|
if (value is not None) or (self._optcre == self.OPTCRE): |
||||||
|
key = format_ini_option(key, value) |
||||||
|
fp.write("%s\n" % (key)) |
||||||
|
fp.write("\n") |
||||||
|
|
||||||
|
def _extract_user_info_from_config(self): |
||||||
|
"""extracts the user information from the config file |
||||||
|
|
||||||
|
the information of the journals can be accessed via get_journal_info |
||||||
|
""" |
||||||
|
# first parse the group definitions |
||||||
|
self._extract_group_definitions() |
||||||
|
# walk through the sections to get individual acl information |
||||||
|
self._extract_individual_acls() |
||||||
|
|
||||||
|
def _extract_group_definitions(self): |
||||||
|
"""extracts the group information from the config file""" |
||||||
|
# first parse the group definitions |
||||||
|
for group, userlist in self.items("groups"): |
||||||
|
if group not in GROUP_DEFAULTS: |
||||||
|
raise KeyError(f"Undefined group {group} in authz file") |
||||||
|
for username in RE_LIST_SEPARATORS.split(userlist): |
||||||
|
if username in self.elab_users: |
||||||
|
raise Exception( |
||||||
|
( |
||||||
|
f"Found duplicate entry for user " |
||||||
|
f"{username} in authz file" |
||||||
|
) |
||||||
|
) |
||||||
|
self.elab_users[username] = ElabUser(username, group) |
||||||
|
|
||||||
|
def _extract_individual_acls(self): |
||||||
|
"""extracts the acl information from the elab section""" |
||||||
|
elabs = (item for item in self.sections() if item.endswith(SVN_SUFFIX)) |
||||||
|
for elab in elabs: |
||||||
|
for (user_or_group, acl) in self.items(elab): |
||||||
|
if user_or_group in self.elab_users: |
||||||
|
# a nicer name for the lab journal |
||||||
|
belongs_to = elab[: -len(SVN_SUFFIX)] |
||||||
|
# a acl entry for a user |
||||||
|
if acl.lower() == WRITE_ACL: |
||||||
|
self.elab_users[user_or_group].write_acl.add( |
||||||
|
belongs_to |
||||||
|
) |
||||||
|
elif acl.lower() == READ_ACL: |
||||||
|
self.elab_users[user_or_group].read_acl.add(belongs_to) |
||||||
|
|
||||||
|
def group_users(self): |
||||||
|
"""uses the list of users to group them by their group name""" |
||||||
|
groups = {key: [] for key in GROUP_DEFAULTS.keys()} |
||||||
|
for user in self.elab_users.values(): |
||||||
|
if user.group not in groups: |
||||||
|
raise KeyError( |
||||||
|
f"found unknown group {user.group} for user {user.name}" |
||||||
|
) |
||||||
|
groups[user.group].append(user.name) |
||||||
|
return groups |
||||||
|
|
||||||
|
def add_journal_acl_for(self, username, group): |
||||||
|
"""sets the acls for a new user an the corresponding journal""" |
||||||
|
self.elab_users[username] = ElabUser(username, group) |
||||||
|
journal_path = username + SVN_SUFFIX |
||||||
|
self.add_section(journal_path) |
||||||
|
self.set(journal_path, username, WRITE_ACL) |
||||||
|
for group, acl in GROUP_DEFAULTS.items(): |
||||||
|
self.set(journal_path, f"@{group}", acl) |
||||||
|
self._update_user_group_config() |
||||||
|
return self.elab_users[username] |
||||||
|
|
||||||
|
def move_user_to_alumni(self, name): |
||||||
|
"""moves a user to the alumni group and removes the acl privileges""" |
||||||
|
user = self.elab_users[name] |
||||||
|
user.group = ALUMNI |
||||||
|
for access_to in user.write_acl: |
||||||
|
self.remove_option(access_to + SVN_SUFFIX, user.name) |
||||||
|
for access_to in user.read_acl: |
||||||
|
self.remove_option(access_to + SVN_SUFFIX, user.name) |
||||||
|
user.write_acl = set() |
||||||
|
user.read_acl = set() |
||||||
|
self._update_user_group_config() |
||||||
|
return user |
||||||
|
|
||||||
|
def _update_user_group_config(self): |
||||||
|
"""updates the config settings of the groups section""" |
||||||
|
groups = self.group_users() |
||||||
|
for group, userlist in groups.items(): |
||||||
|
self.set("groups", group, ", ".join(sorted(userlist))) |
||||||
|
|
||||||
|
def get_journal_info(self, elab): |
||||||
|
"""returns read and write access info of an lab journal""" |
||||||
|
if not elab.endswith(SVN_SUFFIX): |
||||||
|
elab = elab + SVN_SUFFIX |
||||||
|
if not self.has_section(elab): |
||||||
|
return None |
||||||
|
info = {WRITE_ACL: [], READ_ACL: []} |
||||||
|
for (user_or_group, acl) in self.items(elab): |
||||||
|
if acl in (WRITE_ACL, READ_ACL): |
||||||
|
info[acl].append(user_or_group) |
||||||
|
return info |
@ -0,0 +1,20 @@ |
|||||||
|
AUTHZ_FILE_NAME = "authz" |
||||||
|
HTPWD_FILE_NAME = ".htpasswd" |
||||||
|
|
||||||
|
ADMINS = "administrators" |
||||||
|
USERS = "users" |
||||||
|
RESTRICTED = "restricted" |
||||||
|
ALUMNI = "alumni" |
||||||
|
|
||||||
|
NO_ACL = "" |
||||||
|
READ_ACL = "r" |
||||||
|
WRITE_ACL = "rw" |
||||||
|
|
||||||
|
GROUP_DEFAULTS = { |
||||||
|
ADMINS: WRITE_ACL, |
||||||
|
USERS: READ_ACL, |
||||||
|
RESTRICTED: NO_ACL, |
||||||
|
ALUMNI: NO_ACL, |
||||||
|
} |
||||||
|
|
||||||
|
SVN_SUFFIX = ":/" |
@ -0,0 +1,79 @@ |
|||||||
|
import os |
||||||
|
import random |
||||||
|
import string |
||||||
|
import tempfile |
||||||
|
import subprocess # noqa: S404 |
||||||
|
from typing import Set |
||||||
|
from pathlib import Path |
||||||
|
from datetime import datetime |
||||||
|
from dataclasses import field, dataclass |
||||||
|
|
||||||
|
|
||||||
|
@dataclass |
||||||
|
class ElabUser: |
||||||
|
name: str |
||||||
|
group: str |
||||||
|
write_acl: Set = field(default_factory=set) |
||||||
|
read_acl: Set = field(default_factory=set) |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
"""return a string representation""" |
||||||
|
return self.name |
||||||
|
|
||||||
|
def set_new_password(self, htpasswd_path, length=10, handler=subprocess): |
||||||
|
"""sets a new random password for a user""" |
||||||
|
characters = string.ascii_letters + string.digits |
||||||
|
password = "".join( |
||||||
|
random.choice(characters) for i in range(length) # noqa: S311 |
||||||
|
) |
||||||
|
handler.check_call( |
||||||
|
["htpasswd", "-b", htpasswd_path, self.name, password] |
||||||
|
) |
||||||
|
return password |
||||||
|
|
||||||
|
def delete_password(self, htpasswd_path, handler=subprocess): |
||||||
|
"""deletes a password for a user""" |
||||||
|
# if the user was not added to the password db, the removal will show |
||||||
|
# an error message that is confusing to the user - at least it |
||||||
|
# confused me - so redirect this to /dev/null |
||||||
|
with open(os.devnull, "wb") as devnull: |
||||||
|
handler.check_call( |
||||||
|
["htpasswd", "-D", htpasswd_path, self.name], stderr=devnull |
||||||
|
) |
||||||
|
|
||||||
|
def create_new_repository(self, data_dir, handler=subprocess): |
||||||
|
"""creates a repository for a user and checks in some stuff""" |
||||||
|
# create the new repository |
||||||
|
data_dir = Path(data_dir) |
||||||
|
new_repo = data_dir / self.name |
||||||
|
handler.check_call( |
||||||
|
["svnadmin", "create", str(new_repo)], stderr=handler.STDOUT |
||||||
|
) |
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir: |
||||||
|
tmpdir = Path(tmpdir) |
||||||
|
# check out a temporary working copy |
||||||
|
handler.check_call( |
||||||
|
["svn", "checkout", f"file://{new_repo}", str(tmpdir)] |
||||||
|
) |
||||||
|
# create subfolders |
||||||
|
today = datetime.now() |
||||||
|
year_path = tmpdir / f"{today.year:0>4}" |
||||||
|
year_path.mkdir() |
||||||
|
for month in range(today.month, 13): |
||||||
|
month_path = year_path / f"{month:0>2}" |
||||||
|
month_path.mkdir() |
||||||
|
handler.check_call(["touch", str(month_path / ".empty")]) |
||||||
|
# copy some examples |
||||||
|
for temp in ("experiment", "synthesis", "toc"): |
||||||
|
filename = f"template-{temp}.doc" |
||||||
|
in_file = data_dir / filename |
||||||
|
out_file = tmpdir / filename |
||||||
|
handler.check_call(["cp", str(in_file), str(out_file)]) |
||||||
|
# add and commit the changes |
||||||
|
handler.check_call( |
||||||
|
["svn", "add", "--force", str(tmpdir) + "/"] # noqa: S604 |
||||||
|
) |
||||||
|
handler.check_call( |
||||||
|
["svn", "commit", "-m", f"New User: {self.name}", str(tmpdir)] |
||||||
|
) |
@ -1,6 +0,0 @@ |
|||||||
foo:$apr1$SzJRyvJU$U3luHwCA6xHfKowizE.Gl. |
|
||||||
FOO:$apr1$LSPDdLqg$tiGbDGgNEXcRA/oyadYSw1 |
|
||||||
AndreasEvers:$apr1$n0Oaok6e$wyHcUg6Upm9sE2AoYlVMO/ |
|
||||||
FOOBar:$apr1$pZCbClF5$smEDwhMJIVmPsNmMEkRPd1 |
|
||||||
FooBar:$apr1$24r9zF2e$9q30fNOqSlvn6itdhZMpc1 |
|
||||||
UrmilShh:$apr1$WxMGE8Wb$H0xWao6KZGqBJoXj7fJ420 |
|
@ -0,0 +1,78 @@ |
|||||||
|
|
||||||
|
|
||||||
|
[build-system] |
||||||
|
requires = ["flit"] |
||||||
|
build-backend = "flit.buildapi" |
||||||
|
|
||||||
|
[tool.flit.metadata] |
||||||
|
module = "elab_users" |
||||||
|
dist-name = "elab_users" |
||||||
|
author = "Holger Frey" |
||||||
|
author-email = "frey@imtek.de" |
||||||
|
home-page = "https://git.cpi.imtek.uni-freiburg.de/CPI/elab-users.git" |
||||||
|
description-file = "README.md" |
||||||
|
license = "Beerware" |
||||||
|
|
||||||
|
# see https://pypi.org/classifiers/ |
||||||
|
classifiers = [ |
||||||
|
"Development Status :: 2 - Pre-Alpha", |
||||||
|
"Intended Audience :: Developers", |
||||||
|
"Programming Language :: Python :: 3.7", |
||||||
|
"Programming Language :: Python :: 3.8", |
||||||
|
"Programming Language :: Python :: 3 :: Only", |
||||||
|
"License :: Freely Distributable", |
||||||
|
] |
||||||
|
|
||||||
|
requires = [ |
||||||
|
|
||||||
|
] |
||||||
|
requires-python = ">=3.7" |
||||||
|
|
||||||
|
[tool.flit.metadata.requires-extra] |
||||||
|
test = [ |
||||||
|
"pytest >=4.0.0", |
||||||
|
"pytest-cov", |
||||||
|
"pytest-mock", |
||||||
|
"pytest-randomly", |
||||||
|
"tox", |
||||||
|
] |
||||||
|
dev = [ |
||||||
|
"black", |
||||||
|
"flake8", |
||||||
|
"flake8-comprehensions", |
||||||
|
"flake8-bandit", |
||||||
|
"isort >= 5.0.0", |
||||||
|
"keyring", |
||||||
|
"pre-commit", |
||||||
|
] |
||||||
|
|
||||||
|
[tool.flit.scripts] |
||||||
|
elab-users = "elab_users:main" |
||||||
|
|
||||||
|
[tool.black] |
||||||
|
line-length = 79 |
||||||
|
py37 = true |
||||||
|
include = '\.pyi?$' |
||||||
|
exclude = ''' |
||||||
|
/( |
||||||
|
\.git |
||||||
|
| \.tox |
||||||
|
| \.venv |
||||||
|
| build |
||||||
|
| dist |
||||||
|
)/ |
||||||
|
''' |
||||||
|
|
||||||
|
[tool.isort] |
||||||
|
line_length=79 |
||||||
|
multi_line_output=3 |
||||||
|
length_sort="True" |
||||||
|
include_trailing_comma="True" |
||||||
|
|
||||||
|
[tool.pytest.ini_options] |
||||||
|
markers = [ |
||||||
|
"fun: marks tests as functional (deselect with '-m \"not fun\"')", |
||||||
|
] |
||||||
|
addopts = [ |
||||||
|
"--strict-markers", |
||||||
|
] |
@ -0,0 +1,9 @@ |
|||||||
|
import elab_users.authz |
||||||
|
|
||||||
|
f = "test-data/authz" |
||||||
|
p = elab_users.authz.AuthzConfigParser() |
||||||
|
p.read(f) |
||||||
|
print(p.group_users()) |
||||||
|
|
||||||
|
print(p.get_journal_info("CamillaOestevold")) |
||||||
|
print(p.get_journal_info("AlexanderDietz")) |
@ -0,0 +1,39 @@ |
|||||||
|
[groups] |
||||||
|
administrators = JuergenRuehe, OswaldPrucker |
||||||
|
alumni = AlexeyKopyshev, AndreasBoenisch, AndreasEvers, AnkeWoerz, AnneLoesche, ArthurMartens, ArulGeetha, CamillaOestevold, CanerKaganaslan, ChristianSchuh, ChristineBunte, CkPandiyarajan, CleoStannard, FanWu, GerhardBaaken, GinoRodriguez, GuillermoBenites, HeikeHaller, IrenaEipert, JacobBelardi, JenniferPfau, JoachimLauterwasser, JohannesBaader, KatrinMoosmann, KeLi, KerstinSchuh, KimberlySimancas, MarcoArmbruster, MariaVoehringer, MariaVohringer, MartinRendl, MartinVellinger, MartinaAuerswald, MatthiasLischka, MessRechner, MichaelaFrase, MiriamScheckenbach, MonicaPerez, NinoLomadze, Nongluck, OliverDornfeld, PeterZahn, PhilippDiefenthaler, PhilippWollermann, RebeccaBlell, RodrigoNavarro, SaraFuchs, SebastianBoehmer, SebastianSebald, SimonBodendorfer, SimonSchuster, SirasaYodmongkol, ThidaratWangkam, TobiasHeitzler, TobiasKoenig, TristanBourrel, UlrikeRiehle, ViVek, VinicioCarias, WolfgangEhm, YnSekhar, ZouStaarter |
||||||
|
users = AlexanderDietz, AliciaMalekLuz, AndreasMader, AnnaSchuler, AnneBuderer, ChristophScheibelein, CrispinAmiriNaini, DanielaMoessner, DavidBoschert, DavidSchwaerzle, EstherRiga, FrankScherag, FranziskaDorner, GregorOsterwinter, HeidiPerez, HolgerFrey, JanNiklasSchoenberg, JonGreen, KarenLienkamp, KatyaSergeeva, LauraHerrera, MalwinaPajestka, MaraFlorea, MarcZinggeler, MarcelHoffmann, MarcelRothfelder, MartinKoerner, MartinSchoenstein, MatthiasMenzel, MelanieEichhorn, MichaelHenze, MonikaKurowska, MostafaMahmoud, NataliaSchatz, NicoleBirsner, NilsKorf, PengZou, PetraHettich, PhilipKotrade, RaduCristianMutihac, RomanErath, SamarKazan, SaschaEngel, SebastianBonaus, ShararehSahneh, SureshReddyBanda, ThananthornKanokwijitsilp, ThomasBrandstetter, TianyangZheng, VanessaWeiss, VitaliyKondrashov, WibkeHartleb, XiaoqiangHou, ZhuolingDeng |
||||||
|
restricted = BeniPrasser, JuliaSaar, SimonZunker, UrmilShah, YongZhou |
||||||
|
|
||||||
|
[AlexanderDietz:/] |
||||||
|
@administrators = rw |
||||||
|
@users = r |
||||||
|
@restricted = |
||||||
|
@alumni = |
||||||
|
AlexanderDietz = rw |
||||||
|
|
||||||
|
[AlexeyKopyshev:/] |
||||||
|
@administrators = rw |
||||||
|
@users = r |
||||||
|
@restricted = |
||||||
|
@alumni = |
||||||
|
|
||||||
|
[AndreasEvers:/] |
||||||
|
@administrators = rw |
||||||
|
@users = r |
||||||
|
@restricted = |
||||||
|
@alumni = |
||||||
|
UrmilShah = r |
||||||
|
|
||||||
|
[OswaldPrucker:/] |
||||||
|
@administrators = rw |
||||||
|
@users = r |
||||||
|
@restricted = |
||||||
|
@alumni = |
||||||
|
OswaldPrucker = rw |
||||||
|
|
||||||
|
[UrmilShah:/] |
||||||
|
@administrators = rw |
||||||
|
@users = r |
||||||
|
@restricted = |
||||||
|
@alumni = |
||||||
|
UrmilShah = rw |
@ -0,0 +1,2 @@ |
|||||||
|
AlexanderDietz:$apr1$n0Oaok6e$wyHcUg6Upm9sE2AoYlVMO/ |
||||||
|
UrmilShh:$apr1$WxMGE8Wb$H0xWao6KZGqBJoXj7fJ420 |
@ -0,0 +1,66 @@ |
|||||||
|
import shutil |
||||||
|
import tempfile |
||||||
|
from typing import Dict, List |
||||||
|
from pathlib import Path |
||||||
|
from dataclasses import dataclass |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
|
||||||
|
@dataclass |
||||||
|
class StubCall: |
||||||
|
func: str |
||||||
|
args: List |
||||||
|
kargs: Dict |
||||||
|
|
||||||
|
|
||||||
|
class StubShell: |
||||||
|
|
||||||
|
STDOUT = "STDOUT" |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self.stack = [] |
||||||
|
|
||||||
|
def _add(self, func, args, kargs): |
||||||
|
sc = StubCall(func, args, kargs) |
||||||
|
self.stack.append(sc) |
||||||
|
|
||||||
|
def check_call(self, args, **kargs): |
||||||
|
self._add("check_call", args, kargs) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def stub_handler(): |
||||||
|
return StubShell() |
||||||
|
|
||||||
|
|
||||||
|
def temporary_data_file(src_data_dir, file_name): |
||||||
|
source = src_data_dir / file_name |
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname: |
||||||
|
destination = Path(tmpdirname) / file_name |
||||||
|
shutil.copy(source, destination) |
||||||
|
yield destination |
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def example_data_dir(): |
||||||
|
this_file = Path(__file__).absolute() |
||||||
|
src_dir = this_file.parent.parent |
||||||
|
return src_dir / "test-data" |
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def example_authz(example_data_dir): |
||||||
|
yield from temporary_data_file(example_data_dir, "authz") |
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def example_htpasswd(example_data_dir): |
||||||
|
yield from temporary_data_file(example_data_dir, "htpasswd") |
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def example_empty_file(): |
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname: |
||||||
|
destination = Path(tmpdirname) / "empty" |
||||||
|
yield destination |
@ -0,0 +1,386 @@ |
|||||||
|
""" Stub file for testing the project |
||||||
|
|
||||||
|
There are three predefined ways to run tests: |
||||||
|
|
||||||
|
make test: |
||||||
|
runs only unit tests, that are not marked with "fun" (for functional test) |
||||||
|
in a random order. If a test failed before, only the failed tests will be |
||||||
|
run. This is intended to be the default testing method while developing. |
||||||
|
|
||||||
|
make testall: |
||||||
|
runs unit tests and functional tests in random order. Will give a complete |
||||||
|
overview of the test suite. |
||||||
|
|
||||||
|
make coverage: |
||||||
|
runs only tests marked with "fun" (for functional tests) and generates a |
||||||
|
coverage report for the test run. The idea is to check the test coverage |
||||||
|
only on functinal tests to see if a) everything is as much covered as |
||||||
|
possible and b) to find dead code that is not called in end-to-end tests. |
||||||
|
|
||||||
|
all three test strategies will run "make lint" before to catch easily made |
||||||
|
mistakes. |
||||||
|
""" |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
INFO_RESULT_AD = """ |
||||||
|
User AlexanderDietz is in group 'users': |
||||||
|
Write access is granted to: AlexanderDietz:/ |
||||||
|
Read access is granted to (nearly) all journals. |
||||||
|
Labjournal AlexanderDietz:/ |
||||||
|
Write access granted to: @administrators, AlexanderDietz |
||||||
|
Read access granted to: @users |
||||||
|
""" |
||||||
|
|
||||||
|
INFO_RESULT_OP = """ |
||||||
|
User OswaldPrucker is in group 'administrators': |
||||||
|
Write access is granted to all journals. |
||||||
|
Read access is granted to all journals. |
||||||
|
Labjournal OswaldPrucker:/ |
||||||
|
Write access granted to: @administrators, OswaldPrucker |
||||||
|
Read access granted to: @users |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
INFO_RESULT_AE = """ |
||||||
|
User AndreasEvers is in group 'alumni': |
||||||
|
Write access is NOT granted to any journals |
||||||
|
Read access is NOT granted to any journals |
||||||
|
Labjournal AndreasEvers:/ |
||||||
|
Write access granted to: @administrators |
||||||
|
Read access granted to: @users, UrmilShah |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
INFO_RESULT_US = """ |
||||||
|
User UrmilShah is in group 'restricted': |
||||||
|
Write access is granted to: UrmilShah:/ |
||||||
|
Read access is granted to: AndreasEvers:/ |
||||||
|
Labjournal UrmilShah:/ |
||||||
|
Write access granted to: @administrators, UrmilShah |
||||||
|
Read access granted to: @users |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_get_config(example_authz): |
||||||
|
from elab_users import get_config |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
tmp_dir = example_authz.parent |
||||||
|
parser = get_config(tmp_dir, "authz") |
||||||
|
|
||||||
|
assert isinstance(parser, AuthzConfigParser) |
||||||
|
assert parser.elab_users != {} |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_get_config_missing_file(example_authz): |
||||||
|
from elab_users import get_config |
||||||
|
|
||||||
|
tmp_dir = example_authz.parent |
||||||
|
with pytest.raises(SystemExit): |
||||||
|
get_config(tmp_dir, "not existent") |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_list_users(example_authz, capsys): |
||||||
|
from elab_users import list_users |
||||||
|
|
||||||
|
list_users(example_authz.parent, example_authz.name) |
||||||
|
captured = capsys.readouterr() |
||||||
|
|
||||||
|
admins = "Users in group 'administrators':\n JuergenRuehe\n" |
||||||
|
assert admins in captured.out |
||||||
|
users = "Users in group 'users':\n AlexanderDietz\n" |
||||||
|
assert users in captured.out |
||||||
|
restricted = "Users in group 'restricted':\n BeniPrasser\n" |
||||||
|
assert restricted in captured.out |
||||||
|
alumni = "Users in group 'alumni':\n AlexeyKopyshev\n" |
||||||
|
assert alumni in captured.out |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_show_group_info(example_authz, capsys): |
||||||
|
from elab_users import show_group_info |
||||||
|
|
||||||
|
show_group_info("alumni", example_authz.parent, example_authz.name) |
||||||
|
captured = capsys.readouterr() |
||||||
|
|
||||||
|
alumni = "Users in group 'alumni':\n AlexeyKopyshev\n" |
||||||
|
assert captured.out.startswith(alumni) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_show_group_info_unknown_group(example_authz): |
||||||
|
from elab_users import show_group_info |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
show_group_info("unknown", example_authz.parent, example_authz.name) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_add_new_user(example_authz, example_htpasswd, stub_handler, capsys): |
||||||
|
from elab_users import get_config, add_new_user |
||||||
|
|
||||||
|
add_new_user( |
||||||
|
"JaneDoe", |
||||||
|
"users", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
captured = capsys.readouterr() |
||||||
|
|
||||||
|
assert captured.out.startswith("New password for :") |
||||||
|
assert "username: JaneDoe" in captured.out |
||||||
|
url = "https://svn.cpi.imtek.uni-freiburg.de/JaneDoe" |
||||||
|
assert f"url: {url}" in captured.out |
||||||
|
|
||||||
|
assert stub_handler.stack[0].args[:2] == ["svnadmin", "create"] |
||||||
|
assert stub_handler.stack[0].args[2].endswith("/JaneDoe") |
||||||
|
|
||||||
|
config = get_config(example_authz.parent, example_authz.name) |
||||||
|
user = config.elab_users["JaneDoe"] |
||||||
|
assert user.group == "users" |
||||||
|
assert user.read_acl == set() |
||||||
|
assert user.write_acl == {"JaneDoe"} |
||||||
|
|
||||||
|
items = config.items("JaneDoe:/") |
||||||
|
assert sorted(items) == [ |
||||||
|
("@administrators", "rw"), |
||||||
|
("@alumni", ""), |
||||||
|
("@restricted", ""), |
||||||
|
("@users", "r"), |
||||||
|
("JaneDoe", "rw"), |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_add_new_user_error_on_existing_user( |
||||||
|
example_authz, example_htpasswd, stub_handler |
||||||
|
): |
||||||
|
from elab_users import add_new_user |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
add_new_user( |
||||||
|
"AlexanderDietz", |
||||||
|
"users", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_add_new_user_error_on_forbidden_name( |
||||||
|
example_authz, example_htpasswd, stub_handler |
||||||
|
): |
||||||
|
from elab_users import add_new_user |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
add_new_user( |
||||||
|
"authz", |
||||||
|
"users", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_retire_user(example_authz, example_htpasswd, stub_handler, capsys): |
||||||
|
|
||||||
|
from elab_users import get_config, retire_user |
||||||
|
|
||||||
|
retire_user( |
||||||
|
"AlexanderDietz", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
captured = capsys.readouterr() |
||||||
|
assert captured.out.startswith("Moved user AlexanderDietz to alumni") |
||||||
|
|
||||||
|
config = get_config(example_authz.parent, example_authz.name) |
||||||
|
user = config.elab_users["AlexanderDietz"] |
||||||
|
assert user.group == "alumni" |
||||||
|
assert user.read_acl == set() |
||||||
|
assert user.write_acl == set() |
||||||
|
|
||||||
|
assert stub_handler.stack[-1].args[:2] == ["htpasswd", "-D"] |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_retire_user_error_unknown_user( |
||||||
|
example_authz, example_htpasswd, stub_handler |
||||||
|
): |
||||||
|
|
||||||
|
from elab_users import retire_user |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
retire_user( |
||||||
|
"Unknown", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_retire_user_error_already_alumni( |
||||||
|
example_authz, example_htpasswd, stub_handler |
||||||
|
): |
||||||
|
|
||||||
|
from elab_users import retire_user |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
retire_user( |
||||||
|
"CamillaOestevold", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_retire_user_error_admin( |
||||||
|
example_authz, example_htpasswd, stub_handler |
||||||
|
): |
||||||
|
|
||||||
|
from elab_users import retire_user |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
retire_user( |
||||||
|
"OswaldPrucker", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_change_password( |
||||||
|
example_authz, example_htpasswd, stub_handler, capsys |
||||||
|
): |
||||||
|
|
||||||
|
from elab_users import change_password |
||||||
|
|
||||||
|
change_password( |
||||||
|
"AlexanderDietz", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
captured = capsys.readouterr() |
||||||
|
assert captured.out.startswith("New password for :") |
||||||
|
|
||||||
|
assert stub_handler.stack[-1].args[:2] == ["htpasswd", "-b"] |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_change_password_error_unknown_user( |
||||||
|
example_authz, example_htpasswd, stub_handler |
||||||
|
): |
||||||
|
|
||||||
|
from elab_users import change_password |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
change_password( |
||||||
|
"Unknown", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
@pytest.mark.parametrize( |
||||||
|
"user, result", |
||||||
|
[ |
||||||
|
("OswaldPrucker", INFO_RESULT_OP), |
||||||
|
("AlexanderDietz", INFO_RESULT_AD), |
||||||
|
("AndreasEvers", INFO_RESULT_AE), |
||||||
|
("UrmilShah", INFO_RESULT_US), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_show_user_info(user, result, example_authz, capsys): |
||||||
|
from elab_users import show_user_info |
||||||
|
|
||||||
|
show_user_info( |
||||||
|
user, |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
) |
||||||
|
captured = capsys.readouterr() |
||||||
|
|
||||||
|
assert captured.out.strip() == result.strip() |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_show_user_info_error_unknown_user(example_authz): |
||||||
|
from elab_users import show_user_info |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
show_user_info( |
||||||
|
"Unknown", |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
@pytest.mark.parametrize( |
||||||
|
"commands, result", |
||||||
|
[ |
||||||
|
([], "Users in group 'restricted':"), |
||||||
|
(["OswaldPrucker"], "granted to all journals"), |
||||||
|
(["user", "OswaldPrucker"], "granted to all journals"), |
||||||
|
(["group", "alumni"], "Users in group 'alumni':"), |
||||||
|
(["add", "JaneDoe"], "url:"), |
||||||
|
(["restricted", "JaneDoe"], "url:"), |
||||||
|
(["retire", "AlexanderDietz"], "to alumni"), |
||||||
|
(["password", "AlexanderDietz"], "username: AlexanderDietz"), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_main( |
||||||
|
commands, result, example_authz, example_htpasswd, stub_handler, capsys |
||||||
|
): |
||||||
|
from elab_users import main |
||||||
|
|
||||||
|
main( |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
cli_args=commands, |
||||||
|
) |
||||||
|
captured = capsys.readouterr() |
||||||
|
|
||||||
|
assert result in captured.out |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.fun |
||||||
|
def test_main_help(example_authz, example_htpasswd, stub_handler, capsys): |
||||||
|
from elab_users import main |
||||||
|
|
||||||
|
with pytest.raises(SystemExit): |
||||||
|
main( |
||||||
|
example_authz.parent, |
||||||
|
example_authz.name, |
||||||
|
example_htpasswd.name, |
||||||
|
handler=stub_handler, |
||||||
|
cli_args=["--help"], |
||||||
|
) |
||||||
|
captured = capsys.readouterr() |
||||||
|
|
||||||
|
assert "usage: elab-users [-h] [command] [name]" in captured.out |
@ -0,0 +1,258 @@ |
|||||||
|
import pytest |
||||||
|
|
||||||
|
|
||||||
|
def read_lines(path): |
||||||
|
with path.open("r") as fh: |
||||||
|
content = fh.read().strip() |
||||||
|
lines = content.splitlines() |
||||||
|
return [line.strip() for line in lines] |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"value, expected", |
||||||
|
[ |
||||||
|
("no line break", "KEY = no line break"), |
||||||
|
("with\nline\nbreak", "KEY = with\n\tline\n\tbreak"), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_authz_format_ini_option(value, expected): |
||||||
|
from elab_users.authz import format_ini_option |
||||||
|
|
||||||
|
result = format_ini_option("KEY", value) |
||||||
|
|
||||||
|
assert result == expected |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_init(): |
||||||
|
import configparser |
||||||
|
|
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
|
||||||
|
assert isinstance(parser, configparser.ConfigParser) |
||||||
|
assert parser.elab_users == {} |
||||||
|
assert parser.original_path is None |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_optionxfrom(): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
|
||||||
|
assert parser.optionxform(123) == "123" |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_read(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
parser.read(example_authz) |
||||||
|
|
||||||
|
assert parser.original_path == example_authz |
||||||
|
assert parser.elab_users != {} |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_from_file(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
|
||||||
|
assert isinstance(parser, AuthzConfigParser) |
||||||
|
assert parser.original_path == example_authz |
||||||
|
assert parser.elab_users != {} |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_write_to_file_raises_error(): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
|
||||||
|
with pytest.raises(IOError): |
||||||
|
parser.write_to_file(path=None) |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_write_to_file_uses_original_path( |
||||||
|
example_authz, example_empty_file |
||||||
|
): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
parser.original_path = example_empty_file |
||||||
|
parser.write_to_file(path=None) |
||||||
|
|
||||||
|
assert example_empty_file.is_file() |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_write_to_file_custom_path( |
||||||
|
example_authz, example_empty_file |
||||||
|
): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
parser.write_to_file(path=example_empty_file) |
||||||
|
|
||||||
|
assert example_empty_file.is_file() |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_write(example_authz, example_empty_file): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
with open(example_empty_file, "w") as fh: |
||||||
|
parser.write(fh) |
||||||
|
|
||||||
|
original = read_lines(example_authz) |
||||||
|
created = read_lines(example_empty_file) |
||||||
|
assert original == created |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_extract_user_info_from_config(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
|
||||||
|
super(type(parser), parser).read(example_authz) |
||||||
|
assert parser.elab_users == {} |
||||||
|
|
||||||
|
parser._extract_user_info_from_config() |
||||||
|
assert parser.elab_users != {} |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"name, group", |
||||||
|
[ |
||||||
|
("OswaldPrucker", "administrators"), |
||||||
|
("AlexanderDietz", "users"), |
||||||
|
("UrmilShah", "restricted"), |
||||||
|
("CamillaOestevold", "alumni"), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_authz_parser_extract_group_definitions(name, group, example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
|
||||||
|
super(type(parser), parser).read(example_authz) |
||||||
|
parser._extract_group_definitions() |
||||||
|
|
||||||
|
user = parser.elab_users[name] |
||||||
|
assert user.group == group |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"name, read, write", |
||||||
|
[ |
||||||
|
("OswaldPrucker", [], ["OswaldPrucker"]), |
||||||
|
("AlexanderDietz", [], ["AlexanderDietz"]), |
||||||
|
("UrmilShah", ["AndreasEvers"], ["UrmilShah"]), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_authz_parser_extract_individual_acls( |
||||||
|
name, read, write, example_authz |
||||||
|
): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser() |
||||||
|
|
||||||
|
super(type(parser), parser).read(example_authz) |
||||||
|
parser._extract_group_definitions() |
||||||
|
parser._extract_individual_acls() |
||||||
|
|
||||||
|
user = parser.elab_users[name] |
||||||
|
assert user.read_acl == set(read) |
||||||
|
assert user.write_acl == set(write) |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_group_users(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
|
||||||
|
groups = parser.group_users() |
||||||
|
|
||||||
|
assert len(groups) == 4 |
||||||
|
assert len(groups["administrators"]) == 2 |
||||||
|
assert len(groups["users"]) == 54 |
||||||
|
assert len(groups["restricted"]) == 5 |
||||||
|
assert len(groups["alumni"]) == 62 |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_add_journal_acl_for(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
|
||||||
|
user = parser.add_journal_acl_for("JaneDoe", "users") |
||||||
|
|
||||||
|
assert user.name == "JaneDoe" |
||||||
|
assert user.group == "users" |
||||||
|
assert parser.elab_users["JaneDoe"] == user |
||||||
|
assert "JaneDoe:/" in parser.sections() |
||||||
|
items = parser.items("JaneDoe:/") |
||||||
|
assert sorted(items) == [ |
||||||
|
("@administrators", "rw"), |
||||||
|
("@alumni", ""), |
||||||
|
("@restricted", ""), |
||||||
|
("@users", "r"), |
||||||
|
("JaneDoe", "rw"), |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_move_user_to_alumni(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
|
||||||
|
user = parser.move_user_to_alumni("UrmilShah") |
||||||
|
|
||||||
|
assert user.name == "UrmilShah" |
||||||
|
assert user.group == "alumni" |
||||||
|
assert user.write_acl == set() |
||||||
|
assert user.read_acl == set() |
||||||
|
|
||||||
|
for group, userlist in parser.items("groups"): |
||||||
|
if group == "alumni": |
||||||
|
assert "UrmilShah" in userlist |
||||||
|
else: |
||||||
|
assert "UrmilShah" not in userlist |
||||||
|
|
||||||
|
|
||||||
|
def test_authz_parser_update_user_group_config(example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
parser.elab_users["UrmilShah"].group = "alumni" |
||||||
|
|
||||||
|
parser._update_user_group_config() |
||||||
|
|
||||||
|
for group, userlist in parser.items("groups"): |
||||||
|
if group == "alumni": |
||||||
|
assert "UrmilShah" in userlist |
||||||
|
else: |
||||||
|
assert "UrmilShah" not in userlist |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"elab, read, write", |
||||||
|
[ |
||||||
|
("AlexeyKopyshev:/", ["@users"], ["@administrators"]), |
||||||
|
( |
||||||
|
"AndreasEvers:/", |
||||||
|
["@users", "UrmilShah"], |
||||||
|
["@administrators"], |
||||||
|
), |
||||||
|
( |
||||||
|
"UrmilShah:/", |
||||||
|
["@users"], |
||||||
|
["@administrators", "UrmilShah"], |
||||||
|
), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_authz_parser_get_journal_info(elab, read, write, example_authz): |
||||||
|
from elab_users.authz import AuthzConfigParser |
||||||
|
|
||||||
|
parser = AuthzConfigParser.from_file(example_authz) |
||||||
|
|
||||||
|
info = parser.get_journal_info(elab) |
||||||
|
assert info == {"r": read, "rw": write} |
@ -0,0 +1,104 @@ |
|||||||
|
# import pytest |
||||||
|
from datetime import datetime |
||||||
|
|
||||||
|
|
||||||
|
def test_elabuser_string_representation(): |
||||||
|
from elab_users.users import ElabUser |
||||||
|
|
||||||
|
eu = ElabUser("John Doe", "Some Group") |
||||||
|
|
||||||
|
assert str(eu) == "John Doe" |
||||||
|
|
||||||
|
|
||||||
|
def test_elabuser_set_new_password(stub_handler): |
||||||
|
from elab_users.users import ElabUser |
||||||
|
|
||||||
|
eu = ElabUser("John Doe", "Some Group") |
||||||
|
|
||||||
|
password = eu.set_new_password("some path", 12, handler=stub_handler) |
||||||
|
|
||||||
|
assert len(password) == 12 |
||||||
|
assert len(stub_handler.stack) == 1 |
||||||
|
called = stub_handler.stack[0] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args == [ |
||||||
|
"htpasswd", |
||||||
|
"-b", |
||||||
|
"some path", |
||||||
|
"John Doe", |
||||||
|
password, |
||||||
|
] |
||||||
|
assert called.kargs == {} |
||||||
|
|
||||||
|
|
||||||
|
def test_elabuser_delete_password(stub_handler): |
||||||
|
from elab_users.users import ElabUser |
||||||
|
|
||||||
|
eu = ElabUser("John Doe", "Some Group") |
||||||
|
|
||||||
|
eu.delete_password("some path", handler=stub_handler) |
||||||
|
|
||||||
|
assert len(stub_handler.stack) == 1 |
||||||
|
called = stub_handler.stack[0] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args == [ |
||||||
|
"htpasswd", |
||||||
|
"-D", |
||||||
|
"some path", |
||||||
|
"John Doe", |
||||||
|
] |
||||||
|
assert list(called.kargs.keys()) == ["stderr"] |
||||||
|
|
||||||
|
|
||||||
|
def test_elabuser_create_new_repo(stub_handler): |
||||||
|
from elab_users.users import ElabUser |
||||||
|
|
||||||
|
eu = ElabUser("John Doe", "Some Group") |
||||||
|
|
||||||
|
eu.create_new_repository("some path", handler=stub_handler) |
||||||
|
|
||||||
|
today = datetime.now() |
||||||
|
current_month = today.month |
||||||
|
current_year = today.year |
||||||
|
|
||||||
|
assert len(stub_handler.stack) == 8 + (12 - current_month) |
||||||
|
|
||||||
|
called = stub_handler.stack[0] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args == ["svnadmin", "create", "some path/John Doe"] |
||||||
|
assert called.kargs == {"stderr": stub_handler.STDOUT} |
||||||
|
|
||||||
|
called = stub_handler.stack[1] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args[:3] == ["svn", "checkout", "file://some path/John Doe"] |
||||||
|
assert called.args[3].startswith("/tmp/") # noqa: S108 |
||||||
|
assert called.kargs == {} |
||||||
|
|
||||||
|
called = stub_handler.stack[2] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args[0] == "touch" |
||||||
|
assert called.args[1].startswith("/tmp/") # noqa: S108 |
||||||
|
assert called.args[1].endswith( |
||||||
|
f"/{current_year:0>4}/{current_month:0>2}/.empty" |
||||||
|
) |
||||||
|
assert called.kargs == {} |
||||||
|
|
||||||
|
called = stub_handler.stack[-3] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args[0] == "cp" |
||||||
|
assert called.args[1] == "some path/template-toc.doc" |
||||||
|
assert called.args[2].startswith("/tmp/") # noqa: S108 |
||||||
|
assert called.args[2].endswith("/template-toc.doc") |
||||||
|
assert called.kargs == {} |
||||||
|
|
||||||
|
called = stub_handler.stack[-2] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args[:3] == ["svn", "add", "--force"] |
||||||
|
assert called.args[3].startswith("/tmp/") # noqa: S108 |
||||||
|
assert called.args[3].endswith("/") |
||||||
|
assert called.kargs == {} |
||||||
|
|
||||||
|
called = stub_handler.stack[-1] |
||||||
|
assert called.func == "check_call" |
||||||
|
assert called.args[:4] == ["svn", "commit", "-m", "New User: John Doe"] |
||||||
|
assert called.args[4].startswith("/tmp/") # noqa: S108 |
Loading…
Reference in new issue