wip: Add KC-client and basic methods, signal handler

This commit is contained in:
Christian Cueni 2024-06-19 13:26:50 +02:00
parent a14a09f1ca
commit aa3f222112
16 changed files with 562 additions and 282 deletions

View File

@ -631,6 +631,12 @@ OAUTH_SIGNIN_REDIRECT_URI = env(
"OAUTH_SIGNIN_REDIRECT_URI", default="http://localhost:8000/sso/callback"
)
OAUTH_SIGNIN_URL = env("OAUTH_SIGNIN_URL", default="")
OAUTH_SIGNIN_REALM = env("OAUTH_SIGNIN_REALM", default="vbv")
OAUTH_SIGNIN_ADMIN_CLIENT_ID = env("OAUTH_SIGNIN_ADMIN_CLIENT_ID", default="")
OAUTH_SIGNIN_ADMIN_CLIENT_SECRET = env("OAUTH_SIGNIN_ADMIN_CLIENT_SECRET", default="")
OAUTH_SYNC_ROLES = env.bool("OAUTH_SYNC_ROLES", default=False)
GRAPHENE = {
"SCHEMA": "vbv_lernwelt.core.schema.schema",
"SCHEMA_OUTPUT": "../client/src/gql/schema.graphql",

View File

@ -2,79 +2,84 @@
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --output-file=requirements-dev.txt requirements-dev.in
# pip-compile requirements-dev.in
#
aniso8601==9.0.1
# via graphene
anyascii==0.3.2
# via wagtail
anyio==3.7.1
# via watchfiles
appnope==0.1.3
# via ipython
argon2-cffi==21.3.0
anyio==4.4.0
# via
# httpx
# watchfiles
argon2-cffi==23.1.0
# via -r requirements.in
argon2-cffi-bindings==21.2.0
# via argon2-cffi
asgiref==3.7.2
# via django
astroid==2.15.6
asgiref==3.8.1
# via
# django
# django-cors-headers
# django-stubs
astroid==3.2.2
# via pylint
asttokens==2.2.1
asttokens==2.4.1
# via stack-data
async-timeout==4.0.2
async-property==0.2.2
# via python-keycloak
async-timeout==4.0.3
# via redis
attrs==23.1.0
attrs==23.2.0
# via
# jsonschema
# referencing
# usort
authlib==1.2.1
authlib==1.3.1
# via -r requirements.in
azure-core==1.29.1
azure-core==1.30.2
# via
# azure-identity
# azure-storage-blob
azure-identity==1.14.0
azure-identity==1.17.0
# via -r requirements.in
azure-storage-blob==12.17.0
azure-storage-blob==12.20.0
# via -r requirements.in
backcall==0.2.0
# via ipython
bcrypt==4.0.1
bcrypt==4.1.3
# via paramiko
beautifulsoup4==4.11.2
# via wagtail
black==23.7.0
black==24.4.2
# via
# -r requirements-dev.in
# ufmt
boto3==1.28.23
boto3==1.34.129
# via -r requirements.in
botocore==1.31.23
botocore==1.34.129
# via
# boto3
# s3transfer
brotli==1.0.9
brotli==1.1.0
# via whitenoise
build==0.10.0
build==1.2.1
# via pip-tools
caprover-api @ git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530
# via -r requirements-dev.in
certifi==2023.7.22
certifi==2024.6.2
# via
# httpcore
# httpx
# requests
# sentry-sdk
cffi==1.15.1
cffi==1.16.0
# via
# argon2-cffi-bindings
# cryptography
# pynacl
cfgv==3.3.1
cfgv==3.4.0
# via pre-commit
charset-normalizer==3.2.0
charset-normalizer==3.3.2
# via requests
click==8.1.6
click==8.1.7
# via
# -r requirements.in
# black
@ -84,17 +89,18 @@ click==8.1.6
# ufmt
# usort
# uvicorn
concurrent-log-handler==0.9.24
concurrent-log-handler==0.9.25
# via -r requirements.in
coverage==7.2.7
coverage==7.5.3
# via
# -r requirements-dev.in
# django-coverage-plugin
cryptography==41.0.3
cryptography==42.0.8
# via
# authlib
# azure-identity
# azure-storage-blob
# jwcrypto
# msal
# paramiko
# pyjwt
@ -104,13 +110,15 @@ decorator==5.1.1
# ipython
defusedxml==0.7.1
# via willow
dill==0.3.7
deprecation==2.1.0
# via python-keycloak
dill==0.3.8
# via pylint
distlib==0.3.7
distlib==0.3.8
# via virtualenv
dj-database-url==2.0.0
dj-database-url==2.2.0
# via -r requirements.in
django==3.2.20
django==3.2.25
# via
# -r requirements.in
# dj-database-url
@ -137,96 +145,99 @@ django==3.2.20
# jsonfield
# wagtail
# wagtail-localize
django-click==2.3.0
django-click==2.4.0
# via -r requirements.in
django-constance==3.1.0
# via -r requirements.in
django-cors-headers==4.2.0
django-cors-headers==4.3.1
# via -r requirements.in
django-coverage-plugin==3.1.0
# via -r requirements-dev.in
django-csp==3.7
django-csp==3.8
# via -r requirements.in
django-debug-toolbar==4.1.0
django-debug-toolbar==4.3.0
# via -r requirements-dev.in
django-extensions==3.2.3
# via -r requirements-dev.in
django-filter==23.2
django-filter==23.5
# via wagtail
django-ipware==5.0.0
django-ipware==7.0.1
# via -r requirements.in
django-jsonform==2.18.0
django-jsonform==2.22.0
# via -r requirements.in
django-model-utils==4.3.1
django-model-utils==4.5.1
# via
# -r requirements.in
# django-notifications-hq
django-modelcluster==6.0
django-modelcluster==6.3
# via wagtail
django-notifications-hq==1.8.2
django-notifications-hq==1.8.3
# via -r requirements.in
django-permissionedforms==0.1
# via wagtail
django-picklefield==3.1
django-picklefield==3.2
# via django-constance
django-ratelimit==4.1.0
# via -r requirements.in
django-redis==5.3.0
django-redis==5.4.0
# via -r requirements.in
django-storages==1.13.2
django-storages==1.14.3
# via -r requirements.in
django-stubs==4.2.3
django-stubs==5.0.2
# via
# -r requirements-dev.in
# djangorestframework-stubs
django-stubs-ext==4.2.2
django-stubs-ext==5.0.2
# via django-stubs
django-taggit==4.0.0
# via wagtail
django-treebeard==4.7
django-treebeard==4.7.1
# via wagtail
djangorestframework==3.14.0
django-watchfiles @ https://github.com/q0w/django-watchfiles/archive/issue-1.zip
# via -r requirements-dev.in
djangorestframework==3.15.1
# via
# -r requirements.in
# drf-spectacular
# wagtail
djangorestframework-stubs==3.14.2
djangorestframework-stubs==3.15.0
# via -r requirements-dev.in
draftjs-exporter==2.1.7
# via wagtail
drf-spectacular==0.26.4
drf-spectacular==0.27.2
# via -r requirements.in
environs==9.5.0
environs==11.0.0
# via -r requirements.in
et-xmlfile==1.1.0
# via openpyxl
exceptiongroup==1.1.2
exceptiongroup==1.2.1
# via
# anyio
# ipython
# pytest
execnet==2.0.2
execnet==2.1.1
# via pytest-xdist
executing==1.2.0
executing==2.0.1
# via stack-data
factory-boy==3.3.0
# via
# -r requirements-dev.in
# wagtail-factories
faker==19.3.0
faker==25.8.0
# via factory-boy
filelock==3.12.2
filelock==3.15.1
# via virtualenv
filetype==1.2.0
# via willow
flake8==6.1.0
flake8==7.1.0
# via
# -r requirements-dev.in
# flake8-isort
flake8-isort==6.0.0
flake8-isort==6.1.1
# via -r requirements-dev.in
freezegun==1.2.2
freezegun==1.5.1
# via -r requirements-dev.in
gitdb==4.0.10
gitdb==4.0.11
# via gitdb2
gitdb2==4.0.2
# via gitpython
@ -234,7 +245,7 @@ gitpython==3.0.6
# via trufflehog
graphene==3.3
# via graphene-django
graphene-django==3.1.5
graphene-django==3.2.2
# via wagtail-grapple
graphql-core==3.2.3
# via
@ -245,19 +256,26 @@ graphql-relay==3.2.0
# via
# graphene
# graphene-django
gunicorn==21.2.0
gunicorn==22.0.0
# via -r requirements.in
h11==0.14.0
# via uvicorn
# via
# httpcore
# uvicorn
html5lib==1.1
# via wagtail
httptools==0.6.0
httpcore==1.0.5
# via httpx
httptools==0.6.1
# via uvicorn
identify==2.5.26
httpx==0.27.0
# via python-keycloak
identify==2.5.36
# via pre-commit
idna==3.4
idna==3.7
# via
# anyio
# httpx
# requests
inflection==0.5.1
# via drf-spectacular
@ -265,15 +283,15 @@ iniconfig==2.0.0
# via pytest
ipdb==0.13.13
# via -r requirements-dev.in
ipython==8.14.0
ipython==8.25.0
# via ipdb
isodate==0.6.1
# via azure-storage-blob
isort==5.12.0
isort==5.13.2
# via
# flake8-isort
# pylint
jedi==0.19.0
jedi==0.19.1
# via ipython
jmespath==1.0.1
# via
@ -281,21 +299,21 @@ jmespath==1.0.1
# botocore
jsonfield==3.1.0
# via django-notifications-hq
jsonschema==4.19.0
jsonschema==4.22.0
# via drf-spectacular
jsonschema-specifications==2023.7.1
jsonschema-specifications==2023.12.1
# via jsonschema
jwcrypto==1.5.6
# via python-keycloak
l18n==2021.3
# via wagtail
lazy-object-proxy==1.9.0
# via astroid
libcst==1.0.1
libcst==1.4.0
# via
# ufmt
# usort
marshmallow==3.20.1
marshmallow==3.21.3
# via environs
matplotlib-inline==0.1.6
matplotlib-inline==0.1.7
# via ipython
mccabe==0.7.0
# via
@ -305,148 +323,166 @@ moreorless==0.4.0
# via
# ufmt
# usort
msal==1.23.0
msal==1.28.1
# via
# azure-identity
# msal-extensions
msal-extensions==1.0.0
msal-extensions==1.1.0
# via azure-identity
mypy==1.4.1
# via
# -r requirements-dev.in
# django-stubs
# djangorestframework-stubs
mypy==1.10.0
# via -r requirements-dev.in
mypy-extensions==1.0.0
# via
# black
# mypy
# typing-inspect
newrelic==8.11.0
newrelic==9.11.0
# via -r requirements.in
nodeenv==1.8.0
nodeenv==1.9.1
# via pre-commit
openpyxl==3.1.2
openpyxl==3.1.4
# via
# -r requirements.in
# wagtail
packaging==23.1
packaging==24.1
# via
# black
# build
# deprecation
# gunicorn
# marshmallow
# msal-extensions
# pytest
# pytest-sugar
<<<<<<< HEAD
paramiko==3.3.1
# via
# -r requirements.in
# sftpserver
parso==0.8.3
=======
paramiko==3.4.0
# via -r requirements.in
parso==0.8.4
>>>>>>> 9e6b9a1e (wip: Add KC-client and basic methods, signal handler)
# via jedi
pathspec==0.11.2
pathspec==0.12.1
# via
# black
# trailrunner
pexpect==4.8.0
pexpect==4.9.0
# via ipython
pickleshare==0.7.5
# via ipython
pillow==10.0.0
pillow==10.3.0
# via
# -r requirements.in
# pillow-heif
# wagtail
pillow-heif==0.13.0
pillow-heif==0.16.0
# via willow
pip-tools==7.3.0
pip-tools==7.4.1
# via -r requirements-dev.in
platformdirs==3.10.0
platformdirs==4.2.2
# via
# black
# pylint
# virtualenv
pluggy==1.2.0
pluggy==1.5.0
# via pytest
polib==1.2.0
# via wagtail-localize
portalocker==2.7.0
portalocker==2.8.2
# via
# concurrent-log-handler
# msal-extensions
pre-commit==3.3.3
pre-commit==3.7.1
# via -r requirements-dev.in
promise==2.3
# via graphene-django
prompt-toolkit==3.0.39
prompt-toolkit==3.0.47
# via ipython
psycopg2-binary==2.9.7
psycopg2-binary==2.9.9
# via -r requirements.in
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pycodestyle==2.11.0
pycodestyle==2.12.0
# via flake8
pycparser==2.21
pycparser==2.22
# via cffi
pycryptodome==3.18.0
pycryptodome==3.20.0
# via -r requirements.in
pyflakes==3.1.0
pyflakes==3.2.0
# via flake8
pygments==2.16.1
pygments==2.18.0
# via ipython
pyjwt[crypto]==2.8.0
<<<<<<< HEAD
# via msal
pylint==2.17.5
=======
# via
# msal
# pyjwt
pylint==3.2.3
>>>>>>> 9e6b9a1e (wip: Add KC-client and basic methods, signal handler)
# via
# pylint-django
# pylint-plugin-utils
pylint-django==2.5.3
pylint-django==2.5.5
# via -r requirements-dev.in
pylint-plugin-utils==0.8.2
# via pylint-django
pynacl==1.5.0
# via paramiko
pyproject-hooks==1.0.0
# via build
pytest==7.4.0
pyproject-hooks==1.1.0
# via
# build
# pip-tools
pytest==8.2.2
# via
# -r requirements-dev.in
# pytest-django
# pytest-order
# pytest-sugar
# pytest-xdist
pytest-django==4.5.2
pytest-django==4.8.0
# via -r requirements-dev.in
<<<<<<< HEAD
pytest-order==1.2.1
# via -r requirements-dev.in
pytest-sugar==0.9.7
=======
pytest-sugar==1.0.0
>>>>>>> 9e6b9a1e (wip: Add KC-client and basic methods, signal handler)
# via -r requirements-dev.in
pytest-xdist==3.5.0
pytest-xdist==3.6.1
# via -r requirements-dev.in
python-dateutil==2.8.2
python-dateutil==2.9.0.post0
# via
# -r requirements.in
# botocore
# faker
# freezegun
python-dotenv==1.0.0
python-dotenv==1.0.1
# via
# environs
# uvicorn
python-http-client==3.3.7
# via sendgrid
python-ipware==3.0.0
# via django-ipware
python-json-logger==2.0.7
# via -r requirements.in
python-slugify==8.0.1
python-keycloak==4.1.0
# via -r requirements.in
pytz==2023.3
python-slugify==8.0.4
# via -r requirements.in
pytz==2024.1
# via
# -r requirements.in
# django
# django-modelcluster
# django-notifications-hq
# djangorestframework
# l18n
pyyaml==6.0.1
# via
@ -455,30 +491,34 @@ pyyaml==6.0.1
# libcst
# pre-commit
# uvicorn
redis==4.6.0
redis==5.0.6
# via
# -r requirements.in
# django-redis
referencing==0.30.2
referencing==0.35.1
# via
# jsonschema
# jsonschema-specifications
requests==2.31.0
requests==2.32.3
# via
# azure-core
# caprover-api
# djangorestframework-stubs
# msal
# python-keycloak
# requests-toolbelt
# wagtail
rpds-py==0.9.2
requests-toolbelt==1.0.0
# via python-keycloak
rpds-py==0.18.1
# via
# jsonschema
# referencing
s3transfer==0.6.1
s3transfer==0.10.1
# via boto3
sendgrid==6.10.0
sendgrid==6.11.0
# via -r requirements.in
sentry-sdk==1.29.2
sentry-sdk==2.5.1
# via -r requirements.in
sftpserver @ git+https://github.com/lonetwin/sftpserver.git@1d16896d3f0f90d63d1caaf4e199f2a9dde6456f
# via -r requirements-dev.in
@ -491,29 +531,31 @@ six==1.16.0
# l18n
# promise
# python-dateutil
smmap==5.0.0
smmap==5.0.1
# via gitdb
sniffio==1.3.0
# via anyio
soupsieve==2.4.1
sniffio==1.3.1
# via
# anyio
# httpx
soupsieve==2.5
# via beautifulsoup4
sqlparse==0.4.4
sqlparse==0.5.0
# via
# django
# django-debug-toolbar
stack-data==0.6.2
stack-data==0.6.3
# via ipython
starkbank-ecdsa==2.2.0
# via sendgrid
stdlibs==2022.10.9
stdlibs==2024.5.15
# via usort
structlog==23.1.0
structlog==24.2.0
# via -r requirements.in
swapper==1.3.0
# via django-notifications-hq
telepath==0.3.1
# via wagtail
termcolor==2.3.0
termcolor==2.4.0
# via pytest-sugar
text-unidecode==1.3
# via
@ -530,9 +572,8 @@ tomli==2.0.1
# mypy
# pip-tools
# pylint
# pyproject-hooks
# pytest
tomlkit==0.12.1
tomlkit==0.12.5
# via
# pylint
# ufmt
@ -540,7 +581,7 @@ trailrunner==1.4.0
# via
# ufmt
# usort
traitlets==5.9.0
traitlets==5.14.3
# via
# ipython
# matplotlib-inline
@ -548,82 +589,95 @@ trufflehog==2.2.1
# via -r requirements-dev.in
trufflehogregexes==0.0.7
# via trufflehog
types-pytz==2023.3.0.0
# via django-stubs
types-pyyaml==6.0.12.11
types-pyyaml==6.0.12.20240311
# via
# django-stubs
# djangorestframework-stubs
types-requests==2.31.0.2
types-requests==2.32.0.20240602
# via djangorestframework-stubs
types-urllib3==1.26.25.14
# via types-requests
typing-extensions==4.7.1
typing-extensions==4.12.2
# via
# anyio
# asgiref
# astroid
# azure-core
# azure-identity
# azure-storage-blob
# black
# dj-database-url
# django-stubs
# django-stubs-ext
# djangorestframework-stubs
# libcst
# ipython
# jwcrypto
# mypy
# typing-inspect
# ufmt
# uvicorn
# wagtail-localize
typing-inspect==0.9.0
# via libcst
ufmt==2.2.0
ufmt==2.7.0
# via -r requirements-dev.in
uritemplate==4.1.1
# via drf-spectacular
urllib3==1.26.16
urllib3==2.2.2
# via
# botocore
# requests
# sentry-sdk
usort==1.0.7
# types-requests
usort==1.0.8.post1
# via ufmt
uvicorn[standard]==0.23.2
uvicorn[standard]==0.30.1
# via -r requirements.in
uvloop==0.17.0
uvloop==0.19.0
# via uvicorn
virtualenv==20.24.2
virtualenv==20.26.2
# via pre-commit
wagtail==5.1
wagtail==5.2.5
# via
# -r requirements.in
# wagtail-factories
# wagtail-grapple
# wagtail-headless-preview
# wagtail-localize
wagtail-factories==4.1.0
wagtail-factories==4.2.1
# via -r requirements.in
wagtail-grapple==0.20.0
wagtail-grapple==0.25.1
# via -r requirements.in
wagtail-headless-preview==0.6.0
wagtail-headless-preview==0.8.0
# via wagtail-grapple
wagtail-localize==1.5.1
wagtail-localize==1.9
# via -r requirements.in
<<<<<<< HEAD
watchfiles==0.19.0
# via uvicorn
wcwidth==0.2.6
=======
watchfiles==0.22.0
# via
# django-watchfiles
# uvicorn
wcwidth==0.2.13
>>>>>>> 9e6b9a1e (wip: Add KC-client and basic methods, signal handler)
# via prompt-toolkit
webencodings==0.5.1
# via html5lib
websockets==11.0.3
websockets==12.0
# via uvicorn
wheel==0.41.1
wheel==0.43.0
# via pip-tools
whitenoise[brotli]==6.5.0
whitenoise[brotli]==6.6.0
# via -r requirements.in
<<<<<<< HEAD
willow[heif]==1.6.1
# via wagtail
wrapt==1.15.0
# via astroid
=======
willow[heif]==1.6.3
# via
# wagtail
# willow
>>>>>>> 9e6b9a1e (wip: Add KC-client and basic methods, signal handler)
# The following packages are considered to be unsafe in a requirements file:
# pip

View File

@ -52,3 +52,5 @@ azure-identity
boto3
openpyxl
newrelic
python-keycloak

View File

@ -2,79 +2,90 @@
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --output-file=requirements.txt requirements.in
# pip-compile requirements.in
#
aniso8601==9.0.1
# via graphene
anyascii==0.3.2
# via wagtail
anyio==3.7.1
# via watchfiles
argon2-cffi==21.3.0
anyio==4.4.0
# via
# httpx
# watchfiles
argon2-cffi==23.1.0
# via -r requirements.in
argon2-cffi-bindings==21.2.0
# via argon2-cffi
asgiref==3.7.2
# via django
async-timeout==4.0.2
asgiref==3.8.1
# via
# django
# django-cors-headers
async-property==0.2.2
# via python-keycloak
async-timeout==4.0.3
# via redis
attrs==23.1.0
attrs==23.2.0
# via
# jsonschema
# referencing
authlib==1.2.1
authlib==1.3.1
# via -r requirements.in
azure-core==1.29.1
azure-core==1.30.2
# via
# azure-identity
# azure-storage-blob
azure-identity==1.14.0
azure-identity==1.17.0
# via -r requirements.in
azure-storage-blob==12.17.0
azure-storage-blob==12.20.0
# via -r requirements.in
bcrypt==4.0.1
bcrypt==4.1.3
# via paramiko
beautifulsoup4==4.11.2
# via wagtail
boto3==1.28.23
boto3==1.34.129
# via -r requirements.in
botocore==1.31.23
botocore==1.34.129
# via
# boto3
# s3transfer
brotli==1.0.9
brotli==1.1.0
# via whitenoise
certifi==2023.7.22
certifi==2024.6.2
# via
# httpcore
# httpx
# requests
# sentry-sdk
cffi==1.15.1
cffi==1.16.0
# via
# argon2-cffi-bindings
# cryptography
# pynacl
charset-normalizer==3.2.0
charset-normalizer==3.3.2
# via requests
click==8.1.6
click==8.1.7
# via
# -r requirements.in
# django-click
# uvicorn
concurrent-log-handler==0.9.24
concurrent-log-handler==0.9.25
# via -r requirements.in
cryptography==41.0.3
cryptography==42.0.8
# via
# authlib
# azure-identity
# azure-storage-blob
# jwcrypto
# msal
# paramiko
# pyjwt
defusedxml==0.7.1
# via willow
dj-database-url==2.0.0
deprecation==2.1.0
# via python-keycloak
dj-database-url==2.2.0
# via -r requirements.in
django==3.2.20
django==3.2.25
# via
# -r requirements.in
# dj-database-url
@ -97,66 +108,66 @@ django==3.2.20
# jsonfield
# wagtail
# wagtail-localize
django-click==2.3.0
django-click==2.4.0
# via -r requirements.in
django-constance==3.1.0
# via -r requirements.in
django-cors-headers==4.2.0
django-cors-headers==4.3.1
# via -r requirements.in
django-csp==3.7
django-csp==3.8
# via -r requirements.in
django-filter==23.2
django-filter==23.5
# via wagtail
django-ipware==5.0.0
django-ipware==7.0.1
# via -r requirements.in
django-jsonform==2.18.0
django-jsonform==2.22.0
# via -r requirements.in
django-model-utils==4.3.1
django-model-utils==4.5.1
# via
# -r requirements.in
# django-notifications-hq
django-modelcluster==6.0
django-modelcluster==6.3
# via wagtail
django-notifications-hq==1.8.2
django-notifications-hq==1.8.3
# via -r requirements.in
django-permissionedforms==0.1
# via wagtail
django-picklefield==3.1
django-picklefield==3.2
# via django-constance
django-ratelimit==4.1.0
# via -r requirements.in
django-redis==5.3.0
django-redis==5.4.0
# via -r requirements.in
django-storages==1.13.2
django-storages==1.14.3
# via -r requirements.in
django-taggit==4.0.0
# via wagtail
django-treebeard==4.7
django-treebeard==4.7.1
# via wagtail
djangorestframework==3.14.0
djangorestframework==3.15.1
# via
# -r requirements.in
# drf-spectacular
# wagtail
draftjs-exporter==2.1.7
# via wagtail
drf-spectacular==0.26.4
drf-spectacular==0.27.2
# via -r requirements.in
environs==9.5.0
environs==11.0.0
# via -r requirements.in
et-xmlfile==1.1.0
# via openpyxl
exceptiongroup==1.1.2
exceptiongroup==1.2.1
# via anyio
factory-boy==3.3.0
# via wagtail-factories
faker==19.3.0
faker==25.8.0
# via factory-boy
filetype==1.2.0
# via willow
graphene==3.3
# via graphene-django
graphene-django==3.1.5
graphene-django==3.2.2
# via wagtail-grapple
graphql-core==3.2.3
# via
@ -167,17 +178,24 @@ graphql-relay==3.2.0
# via
# graphene
# graphene-django
gunicorn==21.2.0
gunicorn==22.0.0
# via -r requirements.in
h11==0.14.0
# via uvicorn
# via
# httpcore
# uvicorn
html5lib==1.1
# via wagtail
httptools==0.6.0
httpcore==1.0.5
# via httpx
httptools==0.6.1
# via uvicorn
idna==3.4
httpx==0.27.0
# via python-keycloak
idna==3.7
# via
# anyio
# httpx
# requests
inflection==0.5.1
# via drf-spectacular
@ -189,106 +207,119 @@ jmespath==1.0.1
# botocore
jsonfield==3.1.0
# via django-notifications-hq
jsonschema==4.19.0
jsonschema==4.22.0
# via drf-spectacular
jsonschema-specifications==2023.7.1
jsonschema-specifications==2023.12.1
# via jsonschema
jwcrypto==1.5.6
# via python-keycloak
l18n==2021.3
# via wagtail
marshmallow==3.20.1
marshmallow==3.21.3
# via environs
msal==1.23.0
msal==1.28.1
# via
# azure-identity
# msal-extensions
msal-extensions==1.0.0
msal-extensions==1.1.0
# via azure-identity
newrelic==8.11.0
newrelic==9.11.0
# via -r requirements.in
openpyxl==3.1.2
openpyxl==3.1.4
# via
# -r requirements.in
# wagtail
packaging==23.1
packaging==24.1
# via
# deprecation
# gunicorn
# marshmallow
paramiko==3.3.1
# msal-extensions
paramiko==3.4.0
# via -r requirements.in
pillow==10.0.0
pillow==10.3.0
# via
# -r requirements.in
# pillow-heif
# wagtail
pillow-heif==0.13.0
pillow-heif==0.16.0
# via willow
polib==1.2.0
# via wagtail-localize
portalocker==2.7.0
portalocker==2.8.2
# via
# concurrent-log-handler
# msal-extensions
promise==2.3
# via graphene-django
psycopg2-binary==2.9.7
psycopg2-binary==2.9.9
# via -r requirements.in
pycparser==2.21
pycparser==2.22
# via cffi
pycryptodome==3.18.0
pycryptodome==3.20.0
# via -r requirements.in
pyjwt[crypto]==2.8.0
# via msal
# via
# msal
# pyjwt
pynacl==1.5.0
# via paramiko
python-dateutil==2.8.2
python-dateutil==2.9.0.post0
# via
# -r requirements.in
# botocore
# faker
python-dotenv==1.0.0
python-dotenv==1.0.1
# via
# environs
# uvicorn
python-http-client==3.3.7
# via sendgrid
python-ipware==3.0.0
# via django-ipware
python-json-logger==2.0.7
# via -r requirements.in
python-slugify==8.0.1
python-keycloak==4.1.0
# via -r requirements.in
pytz==2023.3
python-slugify==8.0.4
# via -r requirements.in
pytz==2024.1
# via
# -r requirements.in
# django
# django-modelcluster
# django-notifications-hq
# djangorestframework
# l18n
pyyaml==6.0.1
# via
# drf-spectacular
# uvicorn
redis==4.6.0
redis==5.0.6
# via
# -r requirements.in
# django-redis
referencing==0.30.2
referencing==0.35.1
# via
# jsonschema
# jsonschema-specifications
requests==2.31.0
requests==2.32.3
# via
# azure-core
# msal
# python-keycloak
# requests-toolbelt
# wagtail
rpds-py==0.9.2
requests-toolbelt==1.0.0
# via python-keycloak
rpds-py==0.18.1
# via
# jsonschema
# referencing
s3transfer==0.6.1
s3transfer==0.10.1
# via boto3
sendgrid==6.10.0
sendgrid==6.11.0
# via -r requirements.in
sentry-sdk==1.29.2
sentry-sdk==2.5.1
# via -r requirements.in
six==1.16.0
# via
@ -298,15 +329,17 @@ six==1.16.0
# l18n
# promise
# python-dateutil
sniffio==1.3.0
# via anyio
soupsieve==2.4.1
sniffio==1.3.1
# via
# anyio
# httpx
soupsieve==2.5
# via beautifulsoup4
sqlparse==0.4.4
sqlparse==0.5.0
# via django
starkbank-ecdsa==2.2.0
# via sendgrid
structlog==23.1.0
structlog==24.2.0
# via -r requirements.in
swapper==1.3.0
# via django-notifications-hq
@ -316,47 +349,52 @@ text-unidecode==1.3
# via
# graphene-django
# python-slugify
typing-extensions==4.7.1
typing-extensions==4.12.2
# via
# anyio
# asgiref
# azure-core
# azure-identity
# azure-storage-blob
# dj-database-url
# jwcrypto
# uvicorn
# wagtail-localize
uritemplate==4.1.1
# via drf-spectacular
urllib3==1.26.16
urllib3==2.2.2
# via
# botocore
# requests
# sentry-sdk
uvicorn[standard]==0.23.2
uvicorn[standard]==0.30.1
# via -r requirements.in
uvloop==0.17.0
uvloop==0.19.0
# via uvicorn
wagtail==5.1
wagtail==5.2.5
# via
# -r requirements.in
# wagtail-factories
# wagtail-grapple
# wagtail-headless-preview
# wagtail-localize
wagtail-factories==4.1.0
wagtail-factories==4.2.1
# via -r requirements.in
wagtail-grapple==0.20.0
wagtail-grapple==0.25.1
# via -r requirements.in
wagtail-headless-preview==0.6.0
wagtail-headless-preview==0.8.0
# via wagtail-grapple
wagtail-localize==1.5.1
wagtail-localize==1.9
# via -r requirements.in
watchfiles==0.19.0
watchfiles==0.22.0
# via uvicorn
webencodings==0.5.1
# via html5lib
websockets==11.0.3
websockets==12.0
# via uvicorn
whitenoise[brotli]==6.5.0
whitenoise[brotli]==6.6.0
# via -r requirements.in
willow[heif]==1.6.1
# via wagtail
willow[heif]==1.6.3
# via
# wagtail
# willow

View File

@ -13,6 +13,11 @@ from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class
from vbv_lernwelt.files.models import UploadFile
from vbv_lernwelt.sso.role_sync.client import (
add_roles_to_user,
remove_roles_from_user,
update_roles_for_user,
)
class CircleContactType(Enum):
@ -293,6 +298,26 @@ class CourseSessionUser(models.Model):
def __str__(self):
return f"{self.user} ({self.course_session.title})"
def save(self, *args, **kwargs):
if self.created_at is None:
add_roles_to_user(
self.user, [(self.course_session.course.slug, [self.role])]
)
else:
old_csu = CourseSessionUser.objects.get(pk=self.pk)
update_roles_for_user(
self.user,
add_course_roles=[(self.course_session.course.slug, [self.role])],
remove_course_roles=[(self.course_session.course.slug, [old_csu.role])],
)
super().save(*args, **kwargs)
@classmethod
def remove_sso_roles_from_user(cls, instance: "CourseSessionUser"):
remove_roles_from_user(
instance.user, [(instance.course_session.course.slug, [instance.role])]
)
class CircleDocument(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

View File

@ -1,10 +1,15 @@
from django.db.models.signals import post_save
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from vbv_lernwelt.course.models import Course, CourseConfiguration
from vbv_lernwelt.course.models import Course, CourseConfiguration, CourseSessionUser
@receiver(post_save, sender=Course)
def create_course_configuration(sender, instance, created, **kwargs):
if created:
CourseConfiguration.objects.create(course=instance)
@receiver(post_delete, sender=CourseSessionUser)
def after_delete(sender, instance, **kwargs):
CourseSessionUser.remove_sso_roles_from_user(instance)

View File

@ -4,5 +4,4 @@ from vbv_lernwelt.course_session_group.models import CourseSessionGroup
@admin.register(CourseSessionGroup)
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
...
class CourseSessionAssignmentAdmin(admin.ModelAdmin): ...

View File

@ -153,9 +153,9 @@ def fetch_course_session_all_users(courses: List[int], excluded_domains=None):
def generate_export_response(cs_users: List[User]) -> HttpResponse:
response = HttpResponse(content_type="text/csv; charset=utf-8")
response[
"Content-Disposition"
] = f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
response["Content-Disposition"] = (
f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
)
response.write("\ufeff".encode("utf8")) # UTF-8 BOM

View File

@ -125,9 +125,9 @@ def _handle_feedback_export_action(course_seesions, file_name):
response = HttpResponse(
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
response[
"Content-Disposition"
] = f"attachment; filename={make_export_filename(file_name)}"
response["Content-Disposition"] = (
f"attachment; filename={make_export_filename(file_name)}"
)
response.write(excel_bytes)
return response

View File

@ -493,6 +493,7 @@ def create_or_update_user(
sso_id: str = None,
contract_number: str = "",
date_of_birth: str = "",
intermediate_sso_id: str = "", # from keycloak
) -> User:
logger.debug(
"create_or_update_user",
@ -537,6 +538,9 @@ def create_or_update_user(
user.first_name = first_name or user.first_name
user.last_name = last_name or user.last_name
user.username = email
user.additional_json_data = user.additional_json_data | {
"intermediate_sso_id": intermediate_sso_id
}
user.set_unusable_password()
user.save()

View File

@ -65,9 +65,9 @@ class TestNotificationService(TestCase):
self.assertFalse(notification.emailed)
def test_send_notification_with_email(self):
self.recipient.additional_json_data[
"email_notification_categories"
] = json.dumps(["USER_INTERACTION"])
self.recipient.additional_json_data["email_notification_categories"] = (
json.dumps(["USER_INTERACTION"])
)
self.recipient.save()
verb = "Anne hat deinen Auftrag bewertet"
@ -146,9 +146,9 @@ class TestNotificationService(TestCase):
self.assertFalse(notification.emailed)
# when the email was not sent, yet it will still send it afterwards...
self.recipient.additional_json_data[
"email_notification_categories"
] = json.dumps(["USER_INTERACTION"])
self.recipient.additional_json_data["email_notification_categories"] = (
json.dumps(["USER_INTERACTION"])
)
self.recipient.save()
result = self.notification_service._send_notification(
@ -188,9 +188,9 @@ class TestNotificationService(TestCase):
self.assertFalse(self._has_sent_emails())
# Assert mail is sent if corresponding email notification type is enabled
self.recipient.additional_json_data[
"email_notification_categories"
] = json.dumps(["USER_INTERACTION"])
self.recipient.additional_json_data["email_notification_categories"] = (
json.dumps(["USER_INTERACTION"])
)
self.recipient.save()
self.notification_service._send_notification(
sender=self.sender,

View File

@ -39,9 +39,9 @@ class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer):
return obj.learning_unit.get_circle().title
def get_criteria(self, obj):
performance_criteria: List[
PerformanceCriteria
] = obj.learning_unit.performancecriteria_set.all()
performance_criteria: List[PerformanceCriteria] = (
obj.learning_unit.performancecriteria_set.all()
)
criteria = []

View File

@ -0,0 +1,92 @@
import unicodedata
from typing import Dict, List, Tuple
from django.conf import settings
from keycloak import KeycloakAdmin, KeycloakOpenIDConnection
from vbv_lernwelt.core.models import User
from vbv_lernwelt.sso.role_sync.roles import ROLE_IDS, SSO_ROLES
CourseRolesType = List[Tuple[str, List[str]]]
if settings.OAUTH_SYNC_ROLES:
keycloak_connection = KeycloakOpenIDConnection(
server_url=settings.OAUTH_SIGNIN_URL,
realm_name=settings.OAUTH_SIGNIN_REALM,
user_realm_name=settings.OAUTH_SIGNIN_REALM,
client_id=settings.OAUTH_SIGNIN_ADMIN_CLIENT_ID,
client_secret_key=settings.OAUTH_SIGNIN_ADMIN_CLIENT_SECRET,
verify=True,
)
keycloak_admin = KeycloakAdmin(connection=keycloak_connection)
# todo: handle errors
def add_roles_to_user(user: User, course_roles: CourseRolesType):
user_id = user.additional_json_data.get("intermediate_sso_id", "")
if settings.OAUTH_SYNC_ROLES and user_id:
request_roles = _get_role_request_data(course_roles)
keycloak_admin.assign_realm_roles(
user_id=user_id,
roles=request_roles,
)
return True
return False
def remove_roles_from_user(user: User, course_roles: CourseRolesType):
user_id = user.additional_json_data.get("intermediate_sso_id", "")
if settings.OAUTH_SYNC_ROLES and user_id:
request_roles = _get_role_request_data(course_roles)
keycloak_admin.delete_realm_roles_of_user(
user_id=user_id,
roles=request_roles,
)
return True
return False
def update_roles_for_user(
user: User, add_course_roles: CourseRolesType, remove_course_roles: CourseRolesType
):
if settings.OAUTH_SYNC_ROLES:
add_roles_to_user(user, add_course_roles)
remove_roles_from_user(user, remove_course_roles)
return True
return False
def get_roles_for_user(user_id: str):
if settings.OAUTH_SYNC_ROLES:
return keycloak_admin.get_realm_roles_of_user(
user_id=user_id,
)
return []
# create sso-ID user and set roles
# sync
def _get_role_request_data(course_roles: CourseRolesType) -> List[Dict[str, str]]:
request_roles = []
for item in course_roles:
course_slug, roles = item
sanitized_course_slug = _remove_accents(course_slug)
oauth_roles = _create_role_names(sanitized_course_slug, roles)
return request_roles + [
{"id": ROLE_IDS[role], "name": role} for role in oauth_roles
]
return request_roles
def _create_role_names(course_slug: str, roles: list) -> List[str]:
return [SSO_ROLES[course_slug][role] for role in roles]
def _remove_accents(input_str) -> str:
nfkd_form = unicodedata.normalize("NFKD", input_str)
return "".join([char for char in nfkd_form if not unicodedata.combining(char)])

View File

@ -0,0 +1,54 @@
SSO_ROLES = {
"uberbetriebliche-kurse": {
"MEMBER": "myvbv-uberbetriebliche-kurse-member",
"EXPERT": "myvbv-uberbetriebliche-kurse-expert",
"SUPERVISOR": "myvbv-uberbetriebliche-kurse-supervisor",
"LEARNING_MENTOR": "myvbv-uberbetriebliche-kurse-mentor",
},
"cours-interentreprises": {
"MEMBER": "myvbv-cours-interentreprises-member",
"EXPERT": "myvbv-cours-interentreprises-expert",
"SUPERVISOR": "myvbv-cours-interentreprises-supervisor",
"LEARNING_MENTOR": "myvbv-cours-interentreprises-mentor",
},
"corso-interaziendale": {
"MEMBER": "myvbv-corso-interaziendale-member",
"EXPERT": "myvbv-corso-interaziendale-expert",
"SUPERVISOR": "myvbv-corso-interaziendale-supervisor",
"LEARNING_MENTOR": "myvbv-corso-interaziendale-mentor",
},
"versicherungsvermittler-in": {
"MEMBER": "myvbv-versicherungsvermittler-in-member",
"LEARNING_MENTOR": "myvbv-versicherungsvermittler-in-mentor",
},
"intermediaire-dassurance": {
"MEMBER": "myvbv-intermediaire-dassurance-member",
"LEARNING_MENTOR": "myvbv-intermediaire-dassurance-mentor",
},
"intermediarioa-assicurativoa": {
"MEMBER": "myvbv-intermediarioa-assicurativoa-member",
"LEARNING_MENTOR": "myvbv-intermediarioa-assicurativoa-mentor",
},
}
# https://sso.test.b.lernetz.host/auth/admin/vbv/console/#/vbv/roles
ROLE_IDS = {
"myvbv-uberbetriebliche-kurse-member": "0725f2d4-c3f3-48b7-83ec-06acfae630e6",
"myvbv-uberbetriebliche-kurse-expert": "c7e33cb6-d227-4764-9b8e-d42af79fb46d",
"myvbv-uberbetriebliche-kurse-supervisor": "d88a7486-7ff4-475c-b840-2e1e0a9decb8",
"myvbv-uberbetriebliche-kurse-mentor": "db5f0e24-9512-4752-8c51-26b3aa6f7f6a",
"myvbv-cours-interentreprises-member": "458c65f4-e969-4ba7-a546-77948641bc0b",
"myvbv-cours-interentreprises-expert": "2ef51fc6-1e5a-427c-b4a9-314249ea24db",
"myvbv-cours-interentreprises-supervisor": "23e5994e-c499-42e8-b956-bb098be793e1",
"myvbv-cours-interentreprises-mentor": "cb37a093-32a3-479b-981e-0604b6b71f5e",
"myvbv-corso-interaziendale-member": "4d9cfc61-b555-44b1-a52d-76231d12f0cd",
"myvbv-corso-interaziendale-expert": "b2da77bd-c3c8-4d1e-9757-c016eaf219e3",
"myvbv-corso-interaziendale-supervisor": "8e9ea3e4-e814-4704-906e-d39f595811eb",
"myvbv-corso-interaziendale-mentor": "36fae39d-67f0-4ed6-9a1c-7d383be9e463",
"myvbv-versicherungsvermittler-in-member": "3ab4eab2-7d7c-43bb-a927-4cf54f24ccc2",
"myvbv-versicherungsvermittler-in-mentor": "12bf374a-293b-4abe-b255-7899eae31908",
"myvbv-intermediaire-dassurance-member": "5400fdae-2c37-4738-8667-0bcb50ed3609",
"myvbv-intermediaire-dassurance-mentor": "3bd737f9-731a-4548-aaf5-4c80175f2759",
"myvbv-intermediarioa-assicurativoa-member": "9fbaaa0f-cf8c-45f2-93f6-7174cb18a982",
"myvbv-intermediarioa-assicurativoa-mentor": "46b12e54-682e-44c0-b506-eab820138b66",
}

View File

@ -116,6 +116,7 @@ def authorize_signin(request):
sso_id=id_token.get("oid"),
first_name=id_token.get("given_name", ""),
last_name=id_token.get("family_name", ""),
intermediate_sso_id=id_token.get("sub"),
)
dj_login(request, user)