Holger Frey
3 years ago
25 changed files with 1817 additions and 902 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
[flake8] |
||||
select = C,E,F,W,S |
||||
ignore = E203,W503 |
||||
per-file-ignores = tests/*:S101 |
@ -0,0 +1,68 @@
@@ -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 @@
@@ -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,4 @@
|
||||
0.0.1 - first version |
||||
---------------------- |
||||
|
||||
- setting up the project |
@ -0,0 +1,117 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,2 @@
|
||||
AlexanderDietz:$apr1$n0Oaok6e$wyHcUg6Upm9sE2AoYlVMO/ |
||||
UrmilShh:$apr1$WxMGE8Wb$H0xWao6KZGqBJoXj7fJ420 |
@ -0,0 +1,66 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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