From 9dea2d37148e76c718a48b64f421b8f84a68ce64 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:55:18 +0100 Subject: [PATCH 01/15] Upgrade `django-axes` to `6.3.0` Updated settings from the default project --- requirements/base.txt | 4 +--- requirements/ci.txt | 6 +----- requirements/dev.txt | 6 +----- src/objects/conf/base.py | 10 +++------- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index feaa7bd3..428f4ba0 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -67,7 +67,7 @@ django==3.2.23 # zgw-consumers django-admin-index==3.1.0 # via -r requirements/base.in -django-axes==5.41.1 +django-axes==6.3.0 # via -r requirements/base.in django-choices==2.0.0 # via vng-api-common @@ -77,8 +77,6 @@ django-filter==2.4.0 # vng-api-common django-formtools==2.3 # via maykin-django-two-factor-auth -django-ipware==3.0.2 - # via django-axes django-jsonform==2.21.4 # via mozilla-django-oidc-db django-markup==1.3 diff --git a/requirements/ci.txt b/requirements/ci.txt index 0f320a93..31091ce0 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -86,7 +86,7 @@ django==3.2.23 # zgw-consumers django-admin-index==3.1.0 # via -r requirements/base.txt -django-axes==5.41.1 +django-axes==6.3.0 # via -r requirements/base.txt django-choices==2.0.0 # via @@ -100,10 +100,6 @@ django-formtools==2.3 # via # -r requirements/base.txt # maykin-django-two-factor-auth -django-ipware==3.0.2 - # via - # -r requirements/base.txt - # django-axes django-jsonform==2.21.4 # via # -r requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index 1f2e664a..b17b1b37 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -110,7 +110,7 @@ django==3.2.23 # zgw-consumers django-admin-index==3.1.0 # via -r requirements/ci.txt -django-axes==5.41.1 +django-axes==6.3.0 # via -r requirements/ci.txt django-choices==2.0.0 # via @@ -128,10 +128,6 @@ django-formtools==2.3 # via # -r requirements/ci.txt # maykin-django-two-factor-auth -django-ipware==3.0.2 - # via - # -r requirements/ci.txt - # django-axes django-jsonform==2.21.4 # via # -r requirements/ci.txt diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index d77e8afb..ca8adce0 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -331,7 +331,7 @@ "objects.utils.admin_index.should_display_dropdown_menu" ) -# Django-Axes (4.0+) +# Django-Axes # # The number of login attempts allowed before a record is created for the # failed logins. Default: 3 @@ -340,15 +340,11 @@ # will be forgotten. Can be set to a python timedelta object or an integer. If # an integer, will be interpreted as a number of hours. Default: None AXES_COOLOFF_TIME = 1 -# If True only locks based on user id and never locks by IP if attempts limit -# exceed, otherwise utilize the existing IP and user locking logic Default: -# False -AXES_ONLY_USER_FAILURES = True # If set, specifies a template to render when a user is locked out. Template # receives cooloff_time and failure_limit as context variables. Default: None AXES_LOCKOUT_TEMPLATE = "account_blocked.html" -AXES_USE_USER_AGENT = True # Default: False -AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP = True # Default: False +AXES_LOCKOUT_TEMPLATE = "account_blocked.html" +AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent", "username"]] # The default meta precedence order IPWARE_META_PRECEDENCE_ORDER = ( From c8be30b67413885b0d9adfaeb625d0c94ecf06fe Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:17:17 +0100 Subject: [PATCH 02/15] Upgrade dependencies to be compatible with `4.2` These are the straightforward ones --- requirements/base.txt | 7 +++---- requirements/ci.txt | 7 +++---- requirements/dev.txt | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 428f4ba0..d269bf31 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -71,7 +71,7 @@ django-axes==6.3.0 # via -r requirements/base.in django-choices==2.0.0 # via vng-api-common -django-filter==2.4.0 +django-filter==23.5 # via # -r requirements/base.in # vng-api-common @@ -89,7 +89,7 @@ django-phonenumber-field==5.2.0 # via maykin-django-two-factor-auth django-privates==2.0.0.post0 # via django-simple-certmanager -django-redis==5.2.0 +django-redis==5.4.0 # via -r requirements/base.in django-relativedelta==2.0.0 # via zgw-consumers @@ -154,7 +154,7 @@ jinja2==3.1.3 # via coreschema josepy==1.9.0 # via mozilla-django-oidc -jsonschema==3.2.0 +jsonschema==4.17.3 # via # -r requirements/base.in # drf-spectacular @@ -234,7 +234,6 @@ six==1.15.0 # via # django-markup # isodate - # jsonschema # python-dateutil # qrcode # requests-mock diff --git a/requirements/ci.txt b/requirements/ci.txt index 31091ce0..8de3075a 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -92,7 +92,7 @@ django-choices==2.0.0 # via # -r requirements/base.txt # vng-api-common -django-filter==2.4.0 +django-filter==23.5 # via # -r requirements/base.txt # vng-api-common @@ -122,7 +122,7 @@ django-privates==2.0.0.post0 # via # -r requirements/base.txt # django-simple-certmanager -django-redis==5.2.0 +django-redis==5.4.0 # via -r requirements/base.txt django-relativedelta==2.0.0 # via @@ -227,7 +227,7 @@ josepy==1.9.0 # via # -r requirements/base.txt # mozilla-django-oidc -jsonschema==3.2.0 +jsonschema==4.17.3 # via # -r requirements/base.txt # drf-spectacular @@ -345,7 +345,6 @@ six==1.15.0 # -r requirements/base.txt # django-markup # isodate - # jsonschema # python-dateutil # qrcode # requests-mock diff --git a/requirements/dev.txt b/requirements/dev.txt index b17b1b37..907bbc4c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -120,7 +120,7 @@ django-debug-toolbar==4.2.0 # via -r requirements/dev.in django-extensions==3.2.3 # via -r requirements/dev.in -django-filter==2.4.0 +django-filter==23.5 # via # -r requirements/ci.txt # vng-api-common @@ -150,7 +150,7 @@ django-privates==2.0.0.post0 # via # -r requirements/ci.txt # django-simple-certmanager -django-redis==5.2.0 +django-redis==5.4.0 # via -r requirements/ci.txt django-relativedelta==2.0.0 # via @@ -268,7 +268,7 @@ josepy==1.9.0 # via # -r requirements/ci.txt # mozilla-django-oidc -jsonschema==3.2.0 +jsonschema==4.17.3 # via # -r requirements/ci.txt # drf-spectacular @@ -414,7 +414,6 @@ six==1.15.0 # -r requirements/ci.txt # django-markup # isodate - # jsonschema # python-dateutil # qrcode # requests-mock From 7ed2dd6175176cfeb1e001dc2eb43c79c65446e5 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:32:03 +0100 Subject: [PATCH 03/15] Add Celery configuration This will be needed for the new notifications_api_common library --- Dockerfile | 1 + bin/celery_worker.sh | 23 +++++++++++++++++++++++ docker-compose.yml | 21 ++++++++++++++++++--- src/objects/celery.py | 9 +++++++++ src/objects/conf/base.py | 12 ++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100755 bin/celery_worker.sh create mode 100644 src/objects/celery.py diff --git a/Dockerfile b/Dockerfile index 2d5e1778..906a7155 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,6 +51,7 @@ COPY --from=backend-build /usr/local/bin/uwsgi /usr/local/bin/uwsgi # Stage 3.2 - Copy source code WORKDIR /app COPY ./bin/docker_start.sh /start.sh +COPY ./bin/celery_worker.sh /celery_worker.sh RUN mkdir /app/log /app/config # copy frontend build statics diff --git a/bin/celery_worker.sh b/bin/celery_worker.sh new file mode 100755 index 00000000..33fdf84a --- /dev/null +++ b/bin/celery_worker.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +LOGLEVEL=${CELERY_LOGLEVEL:-INFO} +CONCURRENCY=${CELERY_WORKER_CONCURRENCY:-1} + +QUEUE=${1:-${CELERY_WORKER_QUEUE:=celery}} +WORKER_NAME=${2:-${CELERY_WORKER_NAME:="${QUEUE}"@%n}} + +_binary=$(which celery) + +if [[ "$ENABLE_COVERAGE" ]]; then + _binary="coverage run $_binary" +fi + +echo "Starting celery worker $WORKER_NAME with queue $QUEUE" +exec $_binary --workdir src --app objects.celery worker \ + -Q $QUEUE \ + -n $WORKER_NAME \ + -l $LOGLEVEL \ + -O fair \ + -c $CONCURRENCY diff --git a/docker-compose.yml b/docker-compose.yml index db1a1f51..12a173d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,14 @@ services: - POSTGRES_USER=${DB_USER:-objects} - POSTGRES_PASSWORD=${DB_PASSWORD:-objects} + redis: + image: redis:7 + command: ["redis-server", "--appendonly", "yes"] + web: - build: . - environment: + build: &web_build + context: . + environment: &web_env - DJANGO_SETTINGS_MODULE=objects.conf.docker - SECRET_KEY=${SECRET_KEY:-1(@f(-6s_u(5fd&1sg^uvu2s(c-9sapw)1era8q&)g)h@cwxxg} - OBJECTS_SUPERUSER_USERNAME=admin @@ -22,8 +27,18 @@ services: - 8000:8000 depends_on: - db - volumes: + - redis + volumes: &web_volumes - media:/app/media # Shared media volume to get access to saved OAS files + celery: + build: *web_build + environment: *web_env + command: /celery_worker.sh + depends_on: + - db + - redis + volumes: *web_volumes + volumes: media: diff --git a/src/objects/celery.py b/src/objects/celery.py new file mode 100644 index 00000000..f5c5e0e0 --- /dev/null +++ b/src/objects/celery.py @@ -0,0 +1,9 @@ +from celery import Celery + +from .setup import setup_env + +setup_env() + +app = Celery("objects") +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index ca8adce0..6a60a6b4 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -433,3 +433,15 @@ OIDC_AUTHENTICATE_CLASS = "mozilla_django_oidc_db.views.OIDCAuthenticationRequestView" MOZILLA_DJANGO_OIDC_DB_CACHE = "oidc" MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT = 5 * 60 + +# +# CELERY - async task queue +# +CELERY_BROKER_URL = config("CELERY_BROKER_URL", "redis://localhost:6379/0") +CELERY_RESULT_BACKEND = config("CELERY_RESULT_BACKEND", "redis://localhost:6379/0") + +# Add (by default) 5 (soft), 15 (hard) minute timeouts to all Celery tasks. +CELERY_TASK_TIME_LIMIT = config("CELERY_TASK_HARD_TIME_LIMIT", default=15 * 60) # hard +CELERY_TASK_SOFT_TIME_LIMIT = config( + "CELERY_TASK_SOFT_TIME_LIMIT", default=5 * 60 +) # soft From 51da9af4f5aac76003f32a25785e8e228e1aec27 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:11:48 +0100 Subject: [PATCH 04/15] Switch to commonground-api-common --- requirements/base.in | 4 +- requirements/base.txt | 91 ++++++++++---- requirements/ci.txt | 111 ++++++++++++++---- requirements/dev.txt | 101 ++++++++++++---- src/objects/__init__.py | 3 + src/objects/api/kanalen.py | 2 +- src/objects/api/mixins.py | 10 +- src/objects/conf/base.py | 2 +- src/objects/fixtures/default_admin_index.json | 2 +- .../tests/v1/test_notifications_send.py | 49 +++----- .../tests/v2/test_notifications_kanaal.py | 46 +++++--- .../tests/v2/test_notifications_send.py | 49 +++----- src/objects/urls.py | 2 +- 13 files changed, 311 insertions(+), 161 deletions(-) diff --git a/requirements/base.in b/requirements/base.in index 82102590..14052f5d 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,4 +1,5 @@ # Core python libraries +celery glom # data represenation based on spec Pillow # handle images psycopg2 # database driver @@ -30,5 +31,6 @@ sentry-sdk # error monitoring elastic-apm # Elastic APM integration # Common ground libraries -vng-api-common[markdown_docs]>=1.6.4 +notifications-api-common +commonground-api-common[markdown_docs] zgw-consumers # external api auths diff --git a/requirements/base.txt b/requirements/base.txt index d269bf31..f7245d2e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,16 +4,24 @@ # # pip-compile --no-emit-index-url requirements/base.in # +amqp==5.2.0 + # via kombu asgiref==3.7.2 # via django attrs==20.3.0 # via # glom # jsonschema +billiard==3.6.4.0 + # via celery boltons==21.0.0 # via # face # glom +celery==5.2.2 + # via + # -r requirements/base.in + # notifications-api-common certifi==2020.12.5 # via # django-simple-certmanager @@ -24,8 +32,24 @@ cffi==1.16.0 # via cryptography chardet==4.0.0 # via requests +click==8.1.7 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl +click-didyoumean==0.3.0 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.3.0 + # via celery +commonground-api-common[markdown-docs,markdown_docs]==1.12.1 + # via -r requirements/base.in coreapi==2.3.3 - # via drf-yasg + # via + # commonground-api-common + # drf-yasg coreschema==0.0.4 # via # coreapi @@ -39,9 +63,9 @@ cryptography==41.0.7 django==3.2.23 # via # -r requirements/base.in + # commonground-api-common # django-admin-index # django-axes - # django-choices # django-filter # django-formtools # django-jsonform @@ -63,24 +87,22 @@ django==3.2.23 # maykin-django-two-factor-auth # mozilla-django-oidc # mozilla-django-oidc-db - # vng-api-common + # notifications-api-common # zgw-consumers django-admin-index==3.1.0 # via -r requirements/base.in django-axes==6.3.0 # via -r requirements/base.in -django-choices==2.0.0 - # via vng-api-common django-filter==23.5 # via # -r requirements/base.in - # vng-api-common + # commonground-api-common django-formtools==2.3 # via maykin-django-two-factor-auth django-jsonform==2.21.4 # via mozilla-django-oidc-db django-markup==1.3 - # via vng-api-common + # via commonground-api-common django-ordered-model==3.7.4 # via django-admin-index django-otp==1.0.6 @@ -94,7 +116,7 @@ django-redis==5.4.0 django-relativedelta==2.0.0 # via zgw-consumers django-rest-framework-condition==0.1.1 - # via vng-api-common + # via commonground-api-common django-rosetta==0.9.8 # via -r requirements/base.in django-sendfile2==0.7.0 @@ -103,27 +125,31 @@ django-simple-certmanager==1.4.1 # via zgw-consumers django-solo==2.2.0 # via + # commonground-api-common # mozilla-django-oidc-db - # vng-api-common + # notifications-api-common # zgw-consumers djangorestframework==3.12.4 # via # -r requirements/base.in + # commonground-api-common # djangorestframework-gis # drf-nested-routers # drf-spectacular # drf-yasg - # vng-api-common + # notifications-api-common djangorestframework-camel-case==1.2.0 - # via vng-api-common + # via + # commonground-api-common + # notifications-api-common djangorestframework-gis==1.0 # via -r requirements/base.in drf-nested-routers==0.93.3 - # via vng-api-common + # via commonground-api-common drf-spectacular==0.16.0 # via -r requirements/base.in drf-yasg==1.20.0 - # via vng-api-common + # via commonground-api-common elastic-apm==6.1.1 # via -r requirements/base.in face==20.1.1 @@ -132,7 +158,8 @@ faker==8.1.0 # via zgw-consumers gemma-zds-client==1.0.1 # via - # vng-api-common + # commonground-api-common + # notifications-api-common # zgw-consumers glom==23.5.0 # via @@ -145,9 +172,9 @@ inflection==0.5.1 # drf-spectacular # drf-yasg iso-639==0.4.5 - # via vng-api-common + # via commonground-api-common isodate==0.6.0 - # via vng-api-common + # via commonground-api-common itypes==1.2.0 # via coreapi jinja2==3.1.3 @@ -158,8 +185,10 @@ jsonschema==4.17.3 # via # -r requirements/base.in # drf-spectacular +kombu==5.3.5 + # via celery markdown==3.3.4 - # via vng-api-common + # via commonground-api-common markupsafe==2.1.3 # via jinja2 maykin-django-two-factor-auth[phonenumbers]==2.0.3 @@ -168,8 +197,12 @@ mozilla-django-oidc==4.0.0 # via mozilla-django-oidc-db mozilla-django-oidc-db==0.14.1 # via -r requirements/base.in +notifications-api-common==0.2.2 + # via + # -r requirements/base.in + # commonground-api-common oyaml==1.0 - # via vng-api-common + # via commonground-api-common packaging==23.2 # via drf-yasg phonenumbers==8.12.29 @@ -178,14 +211,16 @@ pillow==10.2.0 # via -r requirements/base.in polib==1.1.1 # via django-rosetta +prompt-toolkit==3.0.43 + # via click-repl psycopg2==2.8.6 # via -r requirements/base.in pycparser==2.20 # via cffi pyjwt==2.4.0 # via + # commonground-api-common # gemma-zds-client - # vng-api-common pyopenssl==23.3.0 # via # django-simple-certmanager @@ -204,25 +239,26 @@ python-dotenv==1.0.0 pytz==2021.1 # via # -r requirements/base.in + # celery # django pyyaml==6.0.1 # via + # commonground-api-common # drf-spectacular # gemma-zds-client # oyaml - # vng-api-common qrcode==6.1 # via maykin-django-two-factor-auth redis==3.5.3 # via django-redis requests==2.25.1 # via + # commonground-api-common # coreapi # django-rosetta # gemma-zds-client # mozilla-django-oidc # requests-mock - # vng-api-common # zgw-consumers requests-mock==1.8.0 # via zgw-consumers @@ -255,12 +291,17 @@ urllib3==1.26.6 # sentry-sdk uwsgi==2.0.21 # via -r requirements/base.in -vng-api-common[markdown-docs]==1.8.0 +vine==5.1.0 # via - # -r requirements/base.in - # vng-api-common + # amqp + # celery + # kombu +wcwidth==0.2.13 + # via prompt-toolkit zgw-consumers==0.27.0 - # via -r requirements/base.in + # via + # -r requirements/base.in + # notifications-api-common # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/ci.txt b/requirements/ci.txt index 8de3075a..f57fd03f 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -4,6 +4,10 @@ # # pip-compile --no-emit-index-url --output-file=requirements/ci.txt requirements/base.txt requirements/test-tools.in # +amqp==5.2.0 + # via + # -r requirements/base.txt + # kombu asgiref==3.7.2 # via # -r requirements/base.txt @@ -15,11 +19,19 @@ attrs==20.3.0 # jsonschema beautifulsoup4==4.9.3 # via webtest +billiard==3.6.4.0 + # via + # -r requirements/base.txt + # celery boltons==21.0.0 # via # -r requirements/base.txt # face # glom +celery==5.2.2 + # via + # -r requirements/base.txt + # notifications-api-common certifi==2020.12.5 # via # -r requirements/base.txt @@ -35,9 +47,33 @@ chardet==4.0.0 # via # -r requirements/base.txt # requests +click==8.1.7 + # via + # -r requirements/base.txt + # celery + # click-didyoumean + # click-plugins + # click-repl +click-didyoumean==0.3.0 + # via + # -r requirements/base.txt + # celery +click-plugins==1.1.1 + # via + # -r requirements/base.txt + # celery +click-repl==0.3.0 + # via + # -r requirements/base.txt + # celery +commonground-api-common[markdown-docs]==1.12.1 + # via + # -r requirements/base.txt + # commonground-api-common coreapi==2.3.3 # via # -r requirements/base.txt + # commonground-api-common # drf-yasg coreschema==0.0.4 # via @@ -58,9 +94,9 @@ cssselect==1.1.0 django==3.2.23 # via # -r requirements/base.txt + # commonground-api-common # django-admin-index # django-axes - # django-choices # django-filter # django-formtools # django-jsonform @@ -82,20 +118,16 @@ django==3.2.23 # maykin-django-two-factor-auth # mozilla-django-oidc # mozilla-django-oidc-db - # vng-api-common + # notifications-api-common # zgw-consumers django-admin-index==3.1.0 # via -r requirements/base.txt django-axes==6.3.0 # via -r requirements/base.txt -django-choices==2.0.0 - # via - # -r requirements/base.txt - # vng-api-common django-filter==23.5 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common django-formtools==2.3 # via # -r requirements/base.txt @@ -105,7 +137,9 @@ django-jsonform==2.21.4 # -r requirements/base.txt # mozilla-django-oidc-db django-markup==1.3 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # commonground-api-common django-ordered-model==3.7.4 # via # -r requirements/base.txt @@ -131,7 +165,7 @@ django-relativedelta==2.0.0 django-rest-framework-condition==0.1.1 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common django-rosetta==0.9.8 # via -r requirements/base.txt django-sendfile2==0.7.0 @@ -145,35 +179,38 @@ django-simple-certmanager==1.4.1 django-solo==2.2.0 # via # -r requirements/base.txt + # commonground-api-common # mozilla-django-oidc-db - # vng-api-common + # notifications-api-common # zgw-consumers django-webtest==1.9.7 # via -r requirements/test-tools.in djangorestframework==3.12.4 # via # -r requirements/base.txt + # commonground-api-common # djangorestframework-gis # drf-nested-routers # drf-spectacular # drf-yasg - # vng-api-common + # notifications-api-common djangorestframework-camel-case==1.2.0 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common + # notifications-api-common djangorestframework-gis==1.0 # via -r requirements/base.txt drf-nested-routers==0.93.3 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common drf-spectacular==0.16.0 # via -r requirements/base.txt drf-yasg==1.20.0 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common elastic-apm==6.1.1 # via -r requirements/base.txt face==20.1.1 @@ -192,7 +229,8 @@ freezegun==1.1.0 gemma-zds-client==1.0.1 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common + # notifications-api-common # zgw-consumers glom==23.5.0 # via @@ -210,11 +248,11 @@ inflection==0.5.1 iso-639==0.4.5 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common isodate==0.6.0 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common itypes==1.2.0 # via # -r requirements/base.txt @@ -231,10 +269,16 @@ jsonschema==4.17.3 # via # -r requirements/base.txt # drf-spectacular +kombu==5.3.5 + # via + # -r requirements/base.txt + # celery lxml==4.7.1 # via pyquery markdown==3.3.4 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # commonground-api-common markupsafe==2.1.3 # via # -r requirements/base.txt @@ -249,10 +293,14 @@ mozilla-django-oidc==4.0.0 # mozilla-django-oidc-db mozilla-django-oidc-db==0.14.1 # via -r requirements/base.txt +notifications-api-common==0.2.2 + # via + # -r requirements/base.txt + # commonground-api-common oyaml==1.0 # via # -r requirements/base.txt - # vng-api-common + # commonground-api-common packaging==23.2 # via # -r requirements/base.txt @@ -267,6 +315,10 @@ polib==1.1.1 # via # -r requirements/base.txt # django-rosetta +prompt-toolkit==3.0.43 + # via + # -r requirements/base.txt + # click-repl psycopg2==2.8.6 # via -r requirements/base.txt pycparser==2.20 @@ -276,8 +328,8 @@ pycparser==2.20 pyjwt==2.4.0 # via # -r requirements/base.txt + # commonground-api-common # gemma-zds-client - # vng-api-common pyopenssl==23.3.0 # via # -r requirements/base.txt @@ -303,14 +355,15 @@ python-dotenv==1.0.0 pytz==2021.1 # via # -r requirements/base.txt + # celery # django pyyaml==6.0.1 # via # -r requirements/base.txt + # commonground-api-common # drf-spectacular # gemma-zds-client # oyaml - # vng-api-common qrcode==6.1 # via # -r requirements/base.txt @@ -322,12 +375,12 @@ redis==3.5.3 requests==2.25.1 # via # -r requirements/base.txt + # commonground-api-common # coreapi # django-rosetta # gemma-zds-client # mozilla-django-oidc # requests-mock - # vng-api-common # zgw-consumers requests-mock==1.8.0 # via @@ -379,18 +432,26 @@ urllib3==1.26.6 # sentry-sdk uwsgi==2.0.21 # via -r requirements/base.txt -vng-api-common[markdown-docs]==1.8.0 +vine==5.1.0 # via # -r requirements/base.txt - # vng-api-common + # amqp + # celery + # kombu waitress==2.1.1 # via webtest +wcwidth==0.2.13 + # via + # -r requirements/base.txt + # prompt-toolkit webob==1.8.7 # via webtest webtest==2.0.35 # via django-webtest zgw-consumers==0.27.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # notifications-api-common # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/dev.txt b/requirements/dev.txt index 907bbc4c..e919ecca 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -6,6 +6,10 @@ # alabaster==0.7.12 # via sphinx +amqp==5.2.0 + # via + # -r requirements/ci.txt + # kombu asgiref==3.7.2 # via # -r requirements/ci.txt @@ -21,6 +25,10 @@ beautifulsoup4==4.9.3 # via # -r requirements/ci.txt # webtest +billiard==3.6.4.0 + # via + # -r requirements/ci.txt + # celery black==23.12.1 # via -r requirements/dev.in boltons==21.0.0 @@ -34,6 +42,10 @@ bump2version==1.0.1 # via bumpversion bumpversion==0.6.0 # via -r requirements/dev.in +celery==5.2.2 + # via + # -r requirements/ci.txt + # notifications-api-common certifi==2020.12.5 # via # -r requirements/ci.txt @@ -51,13 +63,35 @@ chardet==4.0.0 # requests click==8.1.7 # via + # -r requirements/ci.txt # black + # celery + # click-didyoumean + # click-plugins + # click-repl # pip-tools +click-didyoumean==0.3.0 + # via + # -r requirements/ci.txt + # celery +click-plugins==1.1.1 + # via + # -r requirements/ci.txt + # celery +click-repl==0.3.0 + # via + # -r requirements/ci.txt + # celery +commonground-api-common[markdown-docs]==1.12.1 + # via + # -r requirements/ci.txt + # commonground-api-common commonmark==0.9.1 # via recommonmark coreapi==2.3.3 # via # -r requirements/ci.txt + # commonground-api-common # drf-yasg coreschema==0.0.4 # via @@ -80,9 +114,9 @@ cssselect==1.1.0 django==3.2.23 # via # -r requirements/ci.txt + # commonground-api-common # django-admin-index # django-axes - # django-choices # django-debug-toolbar # django-extensions # django-filter @@ -106,16 +140,12 @@ django==3.2.23 # maykin-django-two-factor-auth # mozilla-django-oidc # mozilla-django-oidc-db - # vng-api-common + # notifications-api-common # zgw-consumers django-admin-index==3.1.0 # via -r requirements/ci.txt django-axes==6.3.0 # via -r requirements/ci.txt -django-choices==2.0.0 - # via - # -r requirements/ci.txt - # vng-api-common django-debug-toolbar==4.2.0 # via -r requirements/dev.in django-extensions==3.2.3 @@ -123,7 +153,7 @@ django-extensions==3.2.3 django-filter==23.5 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common django-formtools==2.3 # via # -r requirements/ci.txt @@ -159,7 +189,7 @@ django-relativedelta==2.0.0 django-rest-framework-condition==0.1.1 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common django-rosetta==0.9.8 # via -r requirements/ci.txt django-sendfile2==0.7.0 @@ -173,23 +203,26 @@ django-simple-certmanager==1.4.1 django-solo==2.2.0 # via # -r requirements/ci.txt + # commonground-api-common # mozilla-django-oidc-db - # vng-api-common + # notifications-api-common # zgw-consumers django-webtest==1.9.7 # via -r requirements/ci.txt djangorestframework==3.12.4 # via # -r requirements/ci.txt + # commonground-api-common # djangorestframework-gis # drf-nested-routers # drf-spectacular # drf-yasg - # vng-api-common + # notifications-api-common djangorestframework-camel-case==1.2.0 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common + # notifications-api-common djangorestframework-gis==1.0 # via -r requirements/ci.txt docutils==0.18.1 @@ -201,13 +234,13 @@ docutils==0.18.1 drf-nested-routers==0.93.3 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common drf-spectacular==0.16.0 # via -r requirements/ci.txt drf-yasg==1.20.0 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common elastic-apm==6.1.1 # via -r requirements/ci.txt face==20.1.1 @@ -228,7 +261,8 @@ freezegun==1.1.0 gemma-zds-client==1.0.1 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common + # notifications-api-common # zgw-consumers glom==23.5.0 # via @@ -248,11 +282,11 @@ inflection==0.5.1 iso-639==0.4.5 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common isodate==0.6.0 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common isort==5.13.2 # via -r requirements/dev.in itypes==1.2.0 @@ -272,6 +306,10 @@ jsonschema==4.17.3 # via # -r requirements/ci.txt # drf-spectacular +kombu==5.3.5 + # via + # -r requirements/ci.txt + # celery lxml==4.7.1 # via # -r requirements/ci.txt @@ -296,10 +334,14 @@ mozilla-django-oidc-db==0.14.1 # via -r requirements/ci.txt mypy-extensions==0.4.3 # via black +notifications-api-common==0.2.2 + # via + # -r requirements/ci.txt + # commonground-api-common oyaml==1.0 # via # -r requirements/ci.txt - # vng-api-common + # commonground-api-common packaging==23.2 # via # -r requirements/ci.txt @@ -323,6 +365,10 @@ polib==1.1.1 # via # -r requirements/ci.txt # django-rosetta +prompt-toolkit==3.0.43 + # via + # -r requirements/ci.txt + # click-repl psycopg2==2.8.6 # via -r requirements/ci.txt pycodestyle==2.11.1 @@ -340,8 +386,8 @@ pygments==2.17.2 pyjwt==2.4.0 # via # -r requirements/ci.txt + # commonground-api-common # gemma-zds-client - # vng-api-common pyopenssl==23.3.0 # via # -r requirements/ci.txt @@ -370,14 +416,15 @@ pytz==2021.1 # via # -r requirements/ci.txt # babel + # celery # django pyyaml==6.0.1 # via # -r requirements/ci.txt + # commonground-api-common # drf-spectacular # gemma-zds-client # oyaml - # vng-api-common qrcode==6.1 # via # -r requirements/ci.txt @@ -391,13 +438,13 @@ redis==3.5.3 requests==2.25.1 # via # -r requirements/ci.txt + # commonground-api-common # coreapi # django-rosetta # gemma-zds-client # mozilla-django-oidc # requests-mock # sphinx - # vng-api-common # zgw-consumers requests-mock==1.8.0 # via @@ -485,14 +532,20 @@ urllib3==1.26.6 # sentry-sdk uwsgi==2.0.21 # via -r requirements/ci.txt -vng-api-common[markdown-docs]==1.8.0 +vine==5.1.0 # via # -r requirements/ci.txt - # vng-api-common + # amqp + # celery + # kombu waitress==2.1.1 # via # -r requirements/ci.txt # webtest +wcwidth==0.2.13 + # via + # -r requirements/ci.txt + # prompt-toolkit webob==1.8.7 # via # -r requirements/ci.txt @@ -504,7 +557,9 @@ webtest==2.0.35 wheel==0.42.0 # via pip-tools zgw-consumers==0.27.0 - # via -r requirements/ci.txt + # via + # -r requirements/ci.txt + # notifications-api-common # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/src/objects/__init__.py b/src/objects/__init__.py index 85d26250..77df6d6b 100644 --- a/src/objects/__init__.py +++ b/src/objects/__init__.py @@ -1,3 +1,6 @@ +from .celery import app as celery_app + +__all__ = ("celery_app",) __version__ = "2.2.1" __author__ = "Maykin Media" __homepage__ = "https://github.com/maykinmedia/objects-api" diff --git a/src/objects/api/kanalen.py b/src/objects/api/kanalen.py index 2ac56ccf..d2b29bb1 100644 --- a/src/objects/api/kanalen.py +++ b/src/objects/api/kanalen.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db import models -from vng_api_common.notifications.kanalen import Kanaal +from notifications_api_common.kanalen import Kanaal from objects.core.models import ObjectRecord diff --git a/src/objects/api/mixins.py b/src/objects/api/mixins.py index dad3010d..1731b3bf 100644 --- a/src/objects/api/mixins.py +++ b/src/objects/api/mixins.py @@ -1,5 +1,10 @@ from django.db import models +from notifications_api_common.viewsets import ( + NotificationCreateMixin, + NotificationDestroyMixin, + conditional_atomic, +) from rest_framework.exceptions import NotAcceptable from rest_framework.renderers import BrowsableAPIRenderer from vng_api_common.exceptions import PreconditionFailed @@ -10,11 +15,6 @@ GeoMixin as _GeoMixin, extract_header, ) -from vng_api_common.notifications.viewsets import ( - NotificationCreateMixin, - NotificationDestroyMixin, - conditional_atomic, -) class GeoMixin(_GeoMixin): diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index 6a60a6b4..1972340e 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -79,7 +79,7 @@ "solo", "django_markup", "vng_api_common", - "vng_api_common.notifications", + "notifications_api_common", "simple_certmanager", "zgw_consumers", # 2fa apps diff --git a/src/objects/fixtures/default_admin_index.json b/src/objects/fixtures/default_admin_index.json index b11e8500..48aa5b94 100644 --- a/src/objects/fixtures/default_admin_index.json +++ b/src/objects/fixtures/default_admin_index.json @@ -29,7 +29,7 @@ "appgroup" ], [ - "notifications", + "notifications_api_common", "notificationsconfig" ], [ diff --git a/src/objects/tests/v1/test_notifications_send.py b/src/objects/tests/v1/test_notifications_send.py index 75d4884f..43621815 100644 --- a/src/objects/tests/v1/test_notifications_send.py +++ b/src/objects/tests/v1/test_notifications_send.py @@ -4,9 +4,9 @@ import requests_mock from freezegun import freeze_time +from notifications_api_common.models import NotificationsConfig from rest_framework import status from rest_framework.test import APITestCase -from vng_api_common.notifications.models import NotificationsConfig from zgw_consumers.constants import APITypes from zgw_consumers.models import Service @@ -20,12 +20,7 @@ from objects.utils.test import TokenAuthMixin from ..constants import GEO_WRITE_KWARGS -from ..utils import ( - mock_objecttype, - mock_objecttype_version, - mock_service_oas_get, - notifications_client_mock, -) +from ..utils import mock_objecttype, mock_objecttype_version, mock_service_oas_get from .utils import reverse OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" @@ -49,9 +44,8 @@ def setUpTestData(cls): def setUp(self): super().setUp() - config = NotificationsConfig.get_solo() - Service.objects.update_or_create( - api_root=config.api_root, + service, _ = Service.objects.update_or_create( + api_root="https://notificaties-api.vng.cloud/api/v1/", defaults=dict( api_type=APITypes.nrc, client_id="test", @@ -60,13 +54,15 @@ def setUp(self): user_representation="Test", ), ) + config = NotificationsConfig.get_solo() + config.notifications_api_service = service + config.save() - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_create_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_create_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -95,8 +91,7 @@ def test_send_notif_create_object(self, mocker, mock_client): data = response.json() - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": data["url"], @@ -110,12 +105,11 @@ def test_send_notif_create_object(self, mocker, mock_client): }, ) - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_update_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_update_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -147,8 +141,7 @@ def test_send_notif_update_object(self, mocker, mock_client): data = response.json() - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": data["url"], @@ -162,12 +155,11 @@ def test_send_notif_update_object(self, mocker, mock_client): }, ) - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_partial_update_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_partial_update_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -199,8 +191,7 @@ def test_send_notif_partial_update_object(self, mocker, mock_client): data = response.json() - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": data["url"], @@ -214,12 +205,11 @@ def test_send_notif_partial_update_object(self, mocker, mock_client): }, ) - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_delete_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_delete_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -238,8 +228,7 @@ def test_send_notif_delete_object(self, mocker, mock_client): response.status_code, status.HTTP_204_NO_CONTENT, response.data ) - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": full_url, diff --git a/src/objects/tests/v2/test_notifications_kanaal.py b/src/objects/tests/v2/test_notifications_kanaal.py index 87764024..4b5937f4 100644 --- a/src/objects/tests/v2/test_notifications_kanaal.py +++ b/src/objects/tests/v2/test_notifications_kanaal.py @@ -5,8 +5,11 @@ from django.core.management import call_command from django.test import override_settings +from notifications_api_common.kanalen import KANAAL_REGISTRY, Kanaal +from notifications_api_common.models import NotificationsConfig from rest_framework.test import APITestCase -from vng_api_common.notifications.kanalen import Kanaal +from zgw_consumers.constants import APITypes +from zgw_consumers.models import Service from objects.core.models import Object @@ -20,23 +23,35 @@ def setUpTestData(cls): site.domain = "example.com" site.save() - @patch( - "vng_api_common.notifications.management.commands.register_kanaal.get_client" - ) + kanaal = Kanaal(label="kanaal_test", main_resource=Object) + cls.addClassCleanup(lambda: KANAAL_REGISTRY.remove(kanaal)) + + service, _ = Service.objects.update_or_create( + api_root="https://notificaties-api.vng.cloud/api/v1/", + defaults=dict( + api_type=APITypes.nrc, + client_id="test", + secret="test", + user_id="test", + user_representation="Test", + ), + ) + config = NotificationsConfig.get_solo() + config.notifications_api_service = service + config.save() + + @patch("notifications_api_common.models.NotificationsConfig.get_client") def test_kanaal_create_with_name(self, mock_get_client): """ Test is request to create kanaal is send with specified kanaal name """ client = mock_get_client.return_value client.list.return_value = [] - # ensure this is added to the registry - Kanaal(label="kanaal_test", main_resource=Object) stdout = StringIO() call_command( - "register_kanaal", - "kanaal_test", - notificaties_api_root="https://example.com/api/v1", + "register_kanalen", + kanalen=["kanaal_test"], stdout=stdout, ) @@ -49,9 +64,7 @@ def test_kanaal_create_with_name(self, mock_get_client): }, ) - @patch( - "vng_api_common.notifications.management.commands.register_kanaal.get_client" - ) + @patch("notifications_api_common.models.NotificationsConfig.get_client") @override_settings(NOTIFICATIONS_KANAAL="dummy-kanaal") def test_kanaal_create_without_name(self, mock_get_client): """ @@ -59,21 +72,18 @@ def test_kanaal_create_without_name(self, mock_get_client): """ client = mock_get_client.return_value client.list.return_value = [] - # ensure this is added to the registry - Kanaal(label="dummy-kanaal", main_resource=Object) stdout = StringIO() call_command( - "register_kanaal", - notificaties_api_root="https://example.com/api/v1", + "register_kanalen", stdout=stdout, ) client.create.assert_called_once_with( "kanaal", { - "naam": "dummy-kanaal", - "documentatieLink": "https://example.com/ref/kanalen/#dummy-kanaal", + "naam": "kanaal_test", + "documentatieLink": "https://example.com/ref/kanalen/#kanaal_test", "filters": [], }, ) diff --git a/src/objects/tests/v2/test_notifications_send.py b/src/objects/tests/v2/test_notifications_send.py index 75d4884f..43621815 100644 --- a/src/objects/tests/v2/test_notifications_send.py +++ b/src/objects/tests/v2/test_notifications_send.py @@ -4,9 +4,9 @@ import requests_mock from freezegun import freeze_time +from notifications_api_common.models import NotificationsConfig from rest_framework import status from rest_framework.test import APITestCase -from vng_api_common.notifications.models import NotificationsConfig from zgw_consumers.constants import APITypes from zgw_consumers.models import Service @@ -20,12 +20,7 @@ from objects.utils.test import TokenAuthMixin from ..constants import GEO_WRITE_KWARGS -from ..utils import ( - mock_objecttype, - mock_objecttype_version, - mock_service_oas_get, - notifications_client_mock, -) +from ..utils import mock_objecttype, mock_objecttype_version, mock_service_oas_get from .utils import reverse OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" @@ -49,9 +44,8 @@ def setUpTestData(cls): def setUp(self): super().setUp() - config = NotificationsConfig.get_solo() - Service.objects.update_or_create( - api_root=config.api_root, + service, _ = Service.objects.update_or_create( + api_root="https://notificaties-api.vng.cloud/api/v1/", defaults=dict( api_type=APITypes.nrc, client_id="test", @@ -60,13 +54,15 @@ def setUp(self): user_representation="Test", ), ) + config = NotificationsConfig.get_solo() + config.notifications_api_service = service + config.save() - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_create_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_create_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -95,8 +91,7 @@ def test_send_notif_create_object(self, mocker, mock_client): data = response.json() - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": data["url"], @@ -110,12 +105,11 @@ def test_send_notif_create_object(self, mocker, mock_client): }, ) - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_update_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_update_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -147,8 +141,7 @@ def test_send_notif_update_object(self, mocker, mock_client): data = response.json() - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": data["url"], @@ -162,12 +155,11 @@ def test_send_notif_update_object(self, mocker, mock_client): }, ) - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_partial_update_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_partial_update_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -199,8 +191,7 @@ def test_send_notif_partial_update_object(self, mocker, mock_client): data = response.json() - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": data["url"], @@ -214,12 +205,11 @@ def test_send_notif_partial_update_object(self, mocker, mock_client): }, ) - @patch("zds_client.Client.from_url", side_effect=notifications_client_mock) - def test_send_notif_delete_object(self, mocker, mock_client): + @patch("notifications_api_common.viewsets.send_notification.delay") + def test_send_notif_delete_object(self, mocker, mock_task): """ Check if notifications will be send when Object is created """ - client = mock_client.return_value mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") mocker.get( f"{self.object_type.url}/versions/1", @@ -238,8 +228,7 @@ def test_send_notif_delete_object(self, mocker, mock_client): response.status_code, status.HTTP_204_NO_CONTENT, response.data ) - client.create.assert_called_once_with( - "notificaties", + mock_task.assert_called_once_with( { "kanaal": "objecten", "hoofdObject": full_url, diff --git a/src/objects/urls.py b/src/objects/urls.py index 4413dcb8..42833103 100644 --- a/src/objects/urls.py +++ b/src/objects/urls.py @@ -45,7 +45,7 @@ ), ), path("ref/", include("vng_api_common.urls")), - path("ref/", include("vng_api_common.notifications.urls")), + path("ref/", include("notifications_api_common.urls")), path("oidc/", include("mozilla_django_oidc.urls")), path("api/", include("objects.api.urls")), ] From db7d52303fbb7ece1121d56a5ca3c8f7695241d4 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:53:20 +0100 Subject: [PATCH 05/15] Upgrade to `maykin-2fa` Upgrade is pretty straightforward. Followed documentation --- docs/installation/config.rst | 6 --- requirements/base.in | 3 +- requirements/base.txt | 27 +++++++++---- requirements/ci.txt | 40 ++++++++++++++----- requirements/dev.txt | 40 ++++++++++++++----- src/objects/conf/base.py | 19 ++++++--- src/objects/conf/ci.py | 6 --- src/objects/conf/dev.py | 6 ++- src/objects/templates/admin/base_site.html | 6 +-- src/objects/templates/admin/login.html | 16 -------- src/objects/templates/maykin_2fa/base.html | 9 +++++ src/objects/templates/maykin_2fa/login.html | 23 +++++++++++ .../templates/two_factor/admin/login.html | 1 - .../tests/admin/test_token_permissions.py | 2 + src/objects/urls.py | 5 +++ 15 files changed, 138 insertions(+), 71 deletions(-) delete mode 100644 src/objects/templates/admin/login.html create mode 100644 src/objects/templates/maykin_2fa/base.html create mode 100644 src/objects/templates/maykin_2fa/login.html delete mode 100644 src/objects/templates/two_factor/admin/login.html diff --git a/docs/installation/config.rst b/docs/installation/config.rst index 211826a9..7bee5790 100644 --- a/docs/installation/config.rst +++ b/docs/installation/config.rst @@ -84,12 +84,6 @@ Other settings sent to the Notificaties API for operations on the Object endpoint. Defaults to ``True`` for the ``dev`` environment, otherwise defaults to ``False``. -* ``TWO_FACTOR_FORCE_OTP_ADMIN``: Enforce 2 Factor Authentication in the admin or not. - Default ``True``. You'll probably want to disable this when using OIDC. - -* ``TWO_FACTOR_PATCH_ADMIN``: Whether to use the 2 Factor Authentication login flow for - the admin or not. Default ``True``. You'll probably want to disable this when using OIDC. - * ``USE_X_FORWARDED_HOST``: whether to grab the domain/host from the ``X-Forwarded-Host`` header or not. This header is typically set by reverse proxies (such as nginx, traefik, Apache...). Default ``False`` - this is a header that can be spoofed and you diff --git a/requirements/base.in b/requirements/base.in index 14052f5d..f9f5d4d3 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -14,8 +14,7 @@ django-admin-index django-axes django-redis django-rosetta -maykin-django-two-factor-auth -maykin-django-two-factor-auth[phonenumbers] +maykin-2fa mozilla-django-oidc-db # API libraries diff --git a/requirements/base.txt b/requirements/base.txt index f7245d2e..58fa4f7f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,6 +8,8 @@ amqp==5.2.0 # via kombu asgiref==3.7.2 # via django +asn1crypto==1.5.1 + # via webauthn attrs==20.3.0 # via # glom @@ -18,6 +20,8 @@ boltons==21.0.0 # via # face # glom +cbor2==5.6.1 + # via webauthn celery==5.2.2 # via # -r requirements/base.in @@ -60,6 +64,7 @@ cryptography==41.0.7 # josepy # mozilla-django-oidc # pyopenssl + # webauthn django==3.2.23 # via # -r requirements/base.in @@ -80,11 +85,12 @@ django==3.2.23 # django-sendfile2 # django-simple-certmanager # django-solo + # django-two-factor-auth # djangorestframework # drf-nested-routers # drf-spectacular # drf-yasg - # maykin-django-two-factor-auth + # maykin-2fa # mozilla-django-oidc # mozilla-django-oidc-db # notifications-api-common @@ -98,7 +104,7 @@ django-filter==23.5 # -r requirements/base.in # commonground-api-common django-formtools==2.3 - # via maykin-django-two-factor-auth + # via django-two-factor-auth django-jsonform==2.21.4 # via mozilla-django-oidc-db django-markup==1.3 @@ -106,9 +112,9 @@ django-markup==1.3 django-ordered-model==3.7.4 # via django-admin-index django-otp==1.0.6 - # via maykin-django-two-factor-auth + # via django-two-factor-auth django-phonenumber-field==5.2.0 - # via maykin-django-two-factor-auth + # via django-two-factor-auth django-privates==2.0.0.post0 # via django-simple-certmanager django-redis==5.4.0 @@ -129,6 +135,8 @@ django-solo==2.2.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via maykin-2fa djangorestframework==3.12.4 # via # -r requirements/base.in @@ -191,7 +199,7 @@ markdown==3.3.4 # via commonground-api-common markupsafe==2.1.3 # via jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.3 +maykin-2fa==1.0.0 # via -r requirements/base.in mozilla-django-oidc==4.0.0 # via mozilla-django-oidc-db @@ -205,8 +213,8 @@ oyaml==1.0 # via commonground-api-common packaging==23.2 # via drf-yasg -phonenumbers==8.12.29 - # via maykin-django-two-factor-auth +phonenumberslite==8.13.30 + # via django-two-factor-auth pillow==10.2.0 # via -r requirements/base.in polib==1.1.1 @@ -225,6 +233,7 @@ pyopenssl==23.3.0 # via # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyrsistent==0.17.3 # via jsonschema @@ -248,7 +257,7 @@ pyyaml==6.0.1 # gemma-zds-client # oyaml qrcode==6.1 - # via maykin-django-two-factor-auth + # via django-two-factor-auth redis==3.5.3 # via django-redis requests==2.25.1 @@ -298,6 +307,8 @@ vine==5.1.0 # kombu wcwidth==0.2.13 # via prompt-toolkit +webauthn==2.0.0 + # via django-two-factor-auth zgw-consumers==0.27.0 # via # -r requirements/base.in diff --git a/requirements/ci.txt b/requirements/ci.txt index f57fd03f..36b497cc 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -12,6 +12,10 @@ asgiref==3.7.2 # via # -r requirements/base.txt # django +asn1crypto==1.5.1 + # via + # -r requirements/base.txt + # webauthn attrs==20.3.0 # via # -r requirements/base.txt @@ -28,6 +32,10 @@ boltons==21.0.0 # -r requirements/base.txt # face # glom +cbor2==5.6.1 + # via + # -r requirements/base.txt + # webauthn celery==5.2.2 # via # -r requirements/base.txt @@ -89,6 +97,7 @@ cryptography==41.0.7 # josepy # mozilla-django-oidc # pyopenssl + # webauthn cssselect==1.1.0 # via pyquery django==3.2.23 @@ -111,11 +120,12 @@ django==3.2.23 # django-sendfile2 # django-simple-certmanager # django-solo + # django-two-factor-auth # djangorestframework # drf-nested-routers # drf-spectacular # drf-yasg - # maykin-django-two-factor-auth + # maykin-2fa # mozilla-django-oidc # mozilla-django-oidc-db # notifications-api-common @@ -131,7 +141,7 @@ django-filter==23.5 django-formtools==2.3 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-jsonform==2.21.4 # via # -r requirements/base.txt @@ -147,11 +157,11 @@ django-ordered-model==3.7.4 django-otp==1.0.6 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-phonenumber-field==5.2.0 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-privates==2.0.0.post0 # via # -r requirements/base.txt @@ -183,6 +193,11 @@ django-solo==2.2.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via + # -r requirements/base.txt + # django-two-factor-auth + # maykin-2fa django-webtest==1.9.7 # via -r requirements/test-tools.in djangorestframework==3.12.4 @@ -283,10 +298,8 @@ markupsafe==2.1.3 # via # -r requirements/base.txt # jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.3 - # via - # -r requirements/base.txt - # maykin-django-two-factor-auth +maykin-2fa==1.0.0 + # via -r requirements/base.txt mozilla-django-oidc==4.0.0 # via # -r requirements/base.txt @@ -305,10 +318,10 @@ packaging==23.2 # via # -r requirements/base.txt # drf-yasg -phonenumbers==8.12.29 +phonenumberslite==8.13.30 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth pillow==10.2.0 # via -r requirements/base.txt polib==1.1.1 @@ -335,6 +348,7 @@ pyopenssl==23.3.0 # -r requirements/base.txt # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyquery==1.4.3 # via -r requirements/test-tools.in @@ -367,7 +381,7 @@ pyyaml==6.0.1 qrcode==6.1 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth redis==3.5.3 # via # -r requirements/base.txt @@ -444,6 +458,10 @@ wcwidth==0.2.13 # via # -r requirements/base.txt # prompt-toolkit +webauthn==2.0.0 + # via + # -r requirements/base.txt + # django-two-factor-auth webob==1.8.7 # via webtest webtest==2.0.35 diff --git a/requirements/dev.txt b/requirements/dev.txt index e919ecca..f4315223 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -14,6 +14,10 @@ asgiref==3.7.2 # via # -r requirements/ci.txt # django +asn1crypto==1.5.1 + # via + # -r requirements/ci.txt + # webauthn attrs==20.3.0 # via # -r requirements/ci.txt @@ -42,6 +46,10 @@ bump2version==1.0.1 # via bumpversion bumpversion==0.6.0 # via -r requirements/dev.in +cbor2==5.6.1 + # via + # -r requirements/ci.txt + # webauthn celery==5.2.2 # via # -r requirements/ci.txt @@ -107,6 +115,7 @@ cryptography==41.0.7 # josepy # mozilla-django-oidc # pyopenssl + # webauthn cssselect==1.1.0 # via # -r requirements/ci.txt @@ -133,11 +142,12 @@ django==3.2.23 # django-sendfile2 # django-simple-certmanager # django-solo + # django-two-factor-auth # djangorestframework # drf-nested-routers # drf-spectacular # drf-yasg - # maykin-django-two-factor-auth + # maykin-2fa # mozilla-django-oidc # mozilla-django-oidc-db # notifications-api-common @@ -157,7 +167,7 @@ django-filter==23.5 django-formtools==2.3 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-jsonform==2.21.4 # via # -r requirements/ci.txt @@ -171,11 +181,11 @@ django-ordered-model==3.7.4 django-otp==1.0.6 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-phonenumber-field==5.2.0 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth django-privates==2.0.0.post0 # via # -r requirements/ci.txt @@ -207,6 +217,11 @@ django-solo==2.2.0 # mozilla-django-oidc-db # notifications-api-common # zgw-consumers +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via + # -r requirements/ci.txt + # django-two-factor-auth + # maykin-2fa django-webtest==1.9.7 # via -r requirements/ci.txt djangorestframework==3.12.4 @@ -320,10 +335,8 @@ markupsafe==2.1.3 # via # -r requirements/ci.txt # jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.3 - # via - # -r requirements/ci.txt - # maykin-django-two-factor-auth +maykin-2fa==1.0.0 + # via -r requirements/ci.txt mccabe==0.7.0 # via flake8 mozilla-django-oidc==4.0.0 @@ -351,10 +364,10 @@ packaging==23.2 # sphinx pathspec==0.11.2 # via black -phonenumbers==8.12.29 +phonenumberslite==8.13.30 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth pillow==10.2.0 # via -r requirements/ci.txt pip-tools==7.3.0 @@ -393,6 +406,7 @@ pyopenssl==23.3.0 # -r requirements/ci.txt # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyproject-hooks==1.0.0 # via build @@ -428,7 +442,7 @@ pyyaml==6.0.1 qrcode==6.1 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth recommonmark==0.7.1 # via -r requirements/dev.in redis==3.5.3 @@ -546,6 +560,10 @@ wcwidth==0.2.13 # via # -r requirements/ci.txt # prompt-toolkit +webauthn==2.0.0 + # via + # -r requirements/ci.txt + # django-two-factor-auth webob==1.8.7 # via # -r requirements/ci.txt diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index 1972340e..2f0662a7 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -82,11 +82,12 @@ "notifications_api_common", "simple_certmanager", "zgw_consumers", - # 2fa apps + # Two-factor authentication in the Django admin, enforced. "django_otp", "django_otp.plugins.otp_static", "django_otp.plugins.otp_totp", "two_factor", + "maykin_2fa", # Project applications. "objects.accounts", "objects.api", @@ -102,11 +103,11 @@ "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", + "maykin_2fa.middleware.OTPMiddleware", "mozilla_django_oidc_db.middleware.SessionRefresh", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "axes.middleware.AxesMiddleware", - "django_otp.middleware.OTPMiddleware", ] ROOT_URLCONF = "objects.urls" @@ -422,10 +423,18 @@ NOTIFICATIONS_DISABLED = config("NOTIFICATIONS_DISABLED", False) # -# Maykin fork of DJANGO-TWO-FACTOR-AUTH +# MAYKIN-2FA +# Uses django-two-factor-auth under the hood, so relevant upstream package settings +# apply too. # -TWO_FACTOR_FORCE_OTP_ADMIN = config("TWO_FACTOR_FORCE_OTP_ADMIN", not DEBUG) -TWO_FACTOR_PATCH_ADMIN = config("TWO_FACTOR_PATCH_ADMIN", True) + +# we run the admin site monkeypatch instead. +TWO_FACTOR_PATCH_ADMIN = False +# add entries from AUTHENTICATION_BACKENDS that already enforce their own two-factor +# auth, avoiding having some set up MFA again in the project. +MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = [ + "mozilla_django_oidc_db.backends.OIDCAuthenticationBackend", +] # # Mozilla Django OIDC DB settings diff --git a/src/objects/conf/ci.py b/src/objects/conf/ci.py index 23946766..def866cd 100644 --- a/src/objects/conf/ci.py +++ b/src/objects/conf/ci.py @@ -29,9 +29,3 @@ AXES_BEHIND_REVERSE_PROXY = False NOTIFICATIONS_DISABLED = True - - -# -# Maykin fork of django-two-factor-auth -# -TWO_FACTOR_FORCE_OTP_ADMIN = False diff --git a/src/objects/conf/dev.py b/src/objects/conf/dev.py index f06e07ec..07843250 100644 --- a/src/objects/conf/dev.py +++ b/src/objects/conf/dev.py @@ -102,8 +102,10 @@ if "test" in sys.argv: NOTIFICATIONS_DISABLED = True - TWO_FACTOR_PATCH_ADMIN = False - TWO_FACTOR_FORCE_OTP_ADMIN = False + +# None of the authentication backends require two-factor authentication. +if config("DISABLE_2FA", default=False): # pragma: no cover + MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = AUTHENTICATION_BACKENDS # Override settings with local settings. try: diff --git a/src/objects/templates/admin/base_site.html b/src/objects/templates/admin/base_site.html index 6e1a9e1f..1352afce 100644 --- a/src/objects/templates/admin/base_site.html +++ b/src/objects/templates/admin/base_site.html @@ -23,9 +23,9 @@