diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121de9e9..93abec22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: services: postgres: - image: postgres:10 + image: postgres:12 env: POSTGRES_HOST_AUTH_METHOD: trust ports: @@ -28,11 +28,11 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - - uses: actions/setup-node@v2-beta + - uses: actions/setup-node@v4 with: node-version: '18' @@ -61,7 +61,7 @@ jobs: DB_PASSWORD: '' - name: Publish coverage report - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 docker: needs: tests @@ -70,7 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set tag id: vars diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 3a0a2b90..4e99398e 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - - uses: isort/isort-action@v0.1.0 + - uses: isort/isort-action@v1 with: requirementsFiles: requirements/dev.txt sortPaths: "src" @@ -23,8 +23,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies diff --git a/.github/workflows/generate-postman-collection.yml b/.github/workflows/generate-postman-collection.yml index fb4a1452..7aa8d6f8 100644 --- a/.github/workflows/generate-postman-collection.yml +++ b/.github/workflows/generate-postman-collection.yml @@ -19,9 +19,9 @@ jobs: name: Run with version ${{ matrix.version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: '18' - name: Install dependencies diff --git a/.github/workflows/generate-sdks.yml b/.github/workflows/generate-sdks.yml index 77b523c1..7109e0de 100644 --- a/.github/workflows/generate-sdks.yml +++ b/.github/workflows/generate-sdks.yml @@ -19,9 +19,9 @@ jobs: name: Run with version ${{ matrix.version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: '18' - name: Install dependencies diff --git a/.github/workflows/lint-oas.yml b/.github/workflows/lint-oas.yml index f058475c..d9749b55 100644 --- a/.github/workflows/lint-oas.yml +++ b/.github/workflows/lint-oas.yml @@ -19,9 +19,9 @@ jobs: name: Run with version ${{ matrix.version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: '18' - name: Install spectral diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8a936c54..3b6eb728 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ Change history ============== +2.2.0 (WIP) +----------- + +**Bugfixes and QOL** + +* updated to Django 4.2 (objects-api#385) + +.. warning:: + + Two-factor authentication is enabled by default. The ``DISABLE_2FA`` environment variable + can be used to disable it if needed. + 2.1.2 (2024-02-06) ------------------ @@ -10,7 +22,6 @@ Change history * added ``USE_X_FORWARDED_HOST`` environment variable (#353) * added email environment variables (#366) - 2.1.1 (2024-02-06) ------------------ diff --git a/INSTALL.rst b/INSTALL.rst index 33af5a00..1868653b 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -20,7 +20,7 @@ You need the following libraries and/or programs: * `Python`_ 3.10 or above * Python `Virtualenv`_ and `Pip`_ -* `PostgreSQL`_ 10 or above +* `PostgreSQL`_ 12 or above * `Node.js`_ * `npm`_ diff --git a/requirements/base.in b/requirements/base.in index 9491acfd..e4489695 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -7,17 +7,13 @@ python-decouple # processing of envvar configs jsonschema # Framework libraries -django +django~=4.2 django-admin-index django-axes -django-choices -django-hijack django-jsonsuit django-redis django-rosetta -django-sniplates -maykin-django-two-factor-auth -maykin-django-two-factor-auth[phonenumbers] +maykin-2fa mozilla-django-oidc-db sharing-configs diff --git a/requirements/base.txt b/requirements/base.txt index 44012b56..03cda47f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,6 +8,8 @@ amqp==5.1.1 # via kombu asgiref==3.6.0 # via django +asn1crypto==1.5.1 + # via webauthn async-timeout==4.0.2 # via redis attrs==23.1.0 @@ -20,6 +22,8 @@ boltons==23.0.0 # via # face # glom +cbor2==5.6.1 + # via webauthn celery==5.2.7 # via notifications-api-common certifi==2023.5.7 @@ -44,30 +48,27 @@ click-plugins==1.1.1 # via celery click-repl==0.2.0 # via celery -commonground-api-common==1.10.2 +commonground-api-common==1.13.0 # via -r requirements/base.in coreapi==2.3.3 - # via drf-yasg + # via commonground-api-common coreschema==0.0.4 - # via - # coreapi - # drf-yasg -cryptography==40.0.2 + # via coreapi +cryptography==42.0.2 # via # django-simple-certmanager # josepy # mozilla-django-oidc # pyopenssl -django==3.2.20 + # webauthn +django==4.2.11 # via # -r requirements/base.in # commonground-api-common # django-admin-index # django-axes - # django-choices # django-filter # django-formtools - # django-hijack # django-jsonform # django-jsonsuit # django-otp @@ -77,14 +78,15 @@ django==3.2.20 # django-relativedelta # django-rest-framework-condition # django-rosetta + # django-sendfile2 # django-simple-certmanager - # django-sniplates # 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 @@ -94,18 +96,12 @@ django-admin-index==3.0.0 # via -r requirements/base.in django-axes==5.41.1 # via -r requirements/base.in -django-choices==1.7.2 - # via - # -r requirements/base.in - # commonground-api-common django-filter==23.2 # via # -r requirements/base.in # commonground-api-common django-formtools==2.4.1 - # via maykin-django-two-factor-auth -django-hijack==3.3.0 - # via -r requirements/base.in + # via django-two-factor-auth django-ipware==6.0.3 # via django-axes django-jsonform==2.21.5 @@ -115,9 +111,9 @@ django-jsonsuit==0.5.0 django-ordered-model==3.7.4 # via django-admin-index django-otp==1.2.0 - # 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.2.0 @@ -132,8 +128,6 @@ django-sendfile2==0.7.0 # via django-privates django-simple-certmanager==1.3.0 # via zgw-consumers -django-sniplates==0.7.1 - # via -r requirements/base.in django-solo==2.0.0 # via # commonground-api-common @@ -141,7 +135,9 @@ django-solo==2.0.0 # notifications-api-common # sharing-configs # zgw-consumers -djangorestframework==3.12.4 +django-two-factor-auth[phonenumberslite,webauthn]==1.16.0 + # via maykin-2fa +djangorestframework==3.14.0 # via # -r requirements/base.in # commonground-api-common @@ -157,9 +153,9 @@ drf-nested-routers==0.93.4 # via # -r requirements/base.in # commonground-api-common -drf-spectacular==0.16.0 +drf-spectacular==0.26.5 # via -r requirements/base.in -drf-yasg==1.21.5 +drf-yasg==1.21.7 # via commonground-api-common elastic-apm==6.15.1 # via -r requirements/base.in @@ -194,11 +190,11 @@ jsonschema==4.17.3 # via # -r requirements/base.in # drf-spectacular -kombu==5.2.4 +kombu==5.3.5 # via celery markupsafe==2.1.2 # via jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.4 +maykin-2fa==1.0.0 # via -r requirements/base.in mozilla-django-oidc==4.0.0 # via mozilla-django-oidc-db @@ -210,8 +206,8 @@ oyaml==1.0 # via commonground-api-common packaging==23.1 # via drf-yasg -phonenumbers==8.13.11 - # via maykin-django-two-factor-auth +phonenumberslite==8.13.30 + # via django-two-factor-auth pillow==9.5.0 # via -r requirements/base.in polib==1.2.0 @@ -226,10 +222,11 @@ pyjwt==2.7.0 # via # commonground-api-common # gemma-zds-client -pyopenssl==23.1.1 +pyopenssl==24.0.0 # via # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyrsistent==0.19.3 # via jsonschema @@ -247,16 +244,16 @@ pytz==2023.3 # via # -r requirements/base.in # celery - # django + # djangorestframework # drf-yasg pyyaml==6.0 # via - # commonground-api-common # drf-spectacular + # drf-yasg # gemma-zds-client # oyaml qrcode==6.1 - # via maykin-django-two-factor-auth + # via django-two-factor-auth redis==4.5.5 # via django-redis requests==2.31.0 @@ -271,10 +268,6 @@ requests==2.31.0 # zgw-consumers requests-mock==1.10.0 # via zgw-consumers -ruamel-yaml==0.17.26 - # via drf-yasg -ruamel-yaml-clib==0.2.7 - # via ruamel-yaml sentry-sdk==1.23.1 # via -r requirements/base.in sharing-configs==0.1.2 @@ -282,7 +275,6 @@ sharing-configs==0.1.2 six==1.16.0 # via # click-repl - # django-choices # isodate # python-dateutil # qrcode @@ -308,6 +300,8 @@ vine==5.0.0 # kombu wcwidth==0.2.6 # via prompt-toolkit +webauthn==2.0.0 + # via django-two-factor-auth wrapt==1.15.0 # via elastic-apm zgw-consumers==0.26.1 diff --git a/requirements/ci.txt b/requirements/ci.txt index 95606130..ff6d6606 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -12,6 +12,10 @@ asgiref==3.6.0 # via # -r requirements/base.txt # django +asn1crypto==1.5.1 + # via + # -r requirements/base.txt + # webauthn async-timeout==4.0.2 # via # -r requirements/base.txt @@ -32,6 +36,10 @@ boltons==23.0.0 # -r requirements/base.txt # face # glom +cbor2==5.6.1 + # via + # -r requirements/base.txt + # webauthn celery==5.2.7 # via # -r requirements/base.txt @@ -70,38 +78,36 @@ click-repl==0.2.0 # via # -r requirements/base.txt # celery -commonground-api-common==1.10.2 +commonground-api-common==1.13.0 # via -r requirements/base.txt coreapi==2.3.3 # via # -r requirements/base.txt - # drf-yasg + # commonground-api-common coreschema==0.0.4 # via # -r requirements/base.txt # coreapi - # drf-yasg coverage==4.5.4 # via -r requirements/test-tools.in -cryptography==40.0.2 +cryptography==42.0.2 # via # -r requirements/base.txt # django-simple-certmanager # josepy # mozilla-django-oidc # pyopenssl + # webauthn cssselect==1.2.0 # via pyquery -django==3.2.20 +django==4.2.11 # via # -r requirements/base.txt # commonground-api-common # django-admin-index # django-axes - # django-choices # django-filter # django-formtools - # django-hijack # django-jenkins # django-jsonform # django-jsonsuit @@ -114,13 +120,13 @@ django==3.2.20 # django-rosetta # django-sendfile2 # django-simple-certmanager - # django-sniplates # 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 @@ -130,10 +136,6 @@ django-admin-index==3.0.0 # via -r requirements/base.txt django-axes==5.41.1 # via -r requirements/base.txt -django-choices==1.7.2 - # via - # -r requirements/base.txt - # commonground-api-common django-filter==23.2 # via # -r requirements/base.txt @@ -141,9 +143,7 @@ django-filter==23.2 django-formtools==2.4.1 # via # -r requirements/base.txt - # maykin-django-two-factor-auth -django-hijack==3.3.0 - # via -r requirements/base.txt + # django-two-factor-auth django-ipware==6.0.3 # via # -r requirements/base.txt @@ -163,11 +163,11 @@ django-ordered-model==3.7.4 django-otp==1.2.0 # 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 @@ -192,8 +192,6 @@ django-simple-certmanager==1.3.0 # via # -r requirements/base.txt # zgw-consumers -django-sniplates==0.7.1 - # via -r requirements/base.txt django-solo==2.0.0 # via # -r requirements/base.txt @@ -202,9 +200,14 @@ django-solo==2.0.0 # notifications-api-common # sharing-configs # 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.10 # via -r requirements/test-tools.in -djangorestframework==3.12.4 +djangorestframework==3.14.0 # via # -r requirements/base.txt # commonground-api-common @@ -221,9 +224,9 @@ drf-nested-routers==0.93.4 # via # -r requirements/base.txt # commonground-api-common -drf-spectacular==0.16.0 +drf-spectacular==0.26.5 # via -r requirements/base.txt -drf-yasg==1.21.5 +drf-yasg==1.21.7 # via # -r requirements/base.txt # commonground-api-common @@ -285,7 +288,7 @@ jsonschema==4.17.3 # via # -r requirements/base.txt # drf-spectacular -kombu==5.2.4 +kombu==5.3.5 # via # -r requirements/base.txt # celery @@ -295,7 +298,7 @@ markupsafe==2.1.2 # via # -r requirements/base.txt # jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.4 +maykin-2fa==1.0.0 # via -r requirements/base.txt mozilla-django-oidc==4.0.0 # via @@ -315,10 +318,10 @@ packaging==23.1 # via # -r requirements/base.txt # drf-yasg -phonenumbers==8.13.11 +phonenumberslite==8.13.30 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth pillow==9.5.0 # via -r requirements/base.txt polib==1.2.0 @@ -340,11 +343,12 @@ pyjwt==2.7.0 # -r requirements/base.txt # commonground-api-common # gemma-zds-client -pyopenssl==23.1.1 +pyopenssl==24.0.0 # via # -r requirements/base.txt # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyquery==2.0.0 # via -r requirements/test-tools.in @@ -370,19 +374,19 @@ pytz==2023.3 # via # -r requirements/base.txt # celery - # django + # djangorestframework # drf-yasg pyyaml==6.0 # via # -r requirements/base.txt - # commonground-api-common # drf-spectacular + # drf-yasg # gemma-zds-client # oyaml qrcode==6.1 # via # -r requirements/base.txt - # maykin-django-two-factor-auth + # django-two-factor-auth redis==4.5.5 # via # -r requirements/base.txt @@ -403,14 +407,6 @@ requests-mock==1.10.0 # -r requirements/base.txt # -r requirements/test-tools.in # zgw-consumers -ruamel-yaml==0.17.26 - # via - # -r requirements/base.txt - # drf-yasg -ruamel-yaml-clib==0.2.7 - # via - # -r requirements/base.txt - # ruamel-yaml sentry-sdk==1.23.1 # via -r requirements/base.txt sharing-configs==0.1.2 @@ -419,7 +415,6 @@ six==1.16.0 # via # -r requirements/base.txt # click-repl - # django-choices # isodate # python-dateutil # qrcode @@ -458,6 +453,10 @@ wcwidth==0.2.6 # 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==3.0.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index 84059270..fc8143c7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -14,6 +14,10 @@ asgiref==3.6.0 # via # -r requirements/ci.txt # django +asn1crypto==1.5.1 + # via + # -r requirements/ci.txt + # webauthn async-timeout==4.0.2 # via # -r requirements/ci.txt @@ -46,6 +50,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.7 # via # -r requirements/ci.txt @@ -86,42 +94,40 @@ click-repl==0.2.0 # via # -r requirements/ci.txt # celery -commonground-api-common==1.10.2 +commonground-api-common==1.13.0 # via -r requirements/ci.txt coreapi==2.3.3 # via # -r requirements/ci.txt - # drf-yasg + # commonground-api-common coreschema==0.0.4 # via # -r requirements/ci.txt # coreapi - # drf-yasg coverage==4.5.4 # via -r requirements/ci.txt -cryptography==40.0.2 +cryptography==42.0.2 # via # -r requirements/ci.txt # django-simple-certmanager # josepy # mozilla-django-oidc # pyopenssl + # webauthn cssselect==1.2.0 # via # -r requirements/ci.txt # pyquery -django==3.2.20 +django==4.2.11 # via # -r requirements/ci.txt # commonground-api-common # django-admin-index # django-axes - # django-choices # django-debug-toolbar # django-extensions # django-filter # django-formtools - # django-hijack # django-jenkins # django-jsonform # django-jsonsuit @@ -134,13 +140,13 @@ django==3.2.20 # django-rosetta # django-sendfile2 # django-simple-certmanager - # django-sniplates # 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 @@ -150,10 +156,6 @@ django-admin-index==3.0.0 # via -r requirements/ci.txt django-axes==5.41.1 # via -r requirements/ci.txt -django-choices==1.7.2 - # via - # -r requirements/ci.txt - # commonground-api-common django-debug-toolbar==4.1.0 # via -r requirements/dev.in django-extensions==3.2.1 @@ -165,9 +167,7 @@ django-filter==23.2 django-formtools==2.4.1 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth -django-hijack==3.3.0 - # via -r requirements/ci.txt + # django-two-factor-auth django-ipware==6.0.3 # via # -r requirements/ci.txt @@ -187,11 +187,11 @@ django-ordered-model==3.7.4 django-otp==1.2.0 # 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 @@ -216,8 +216,6 @@ django-simple-certmanager==1.3.0 # via # -r requirements/ci.txt # zgw-consumers -django-sniplates==0.7.1 - # via -r requirements/ci.txt django-solo==2.0.0 # via # -r requirements/ci.txt @@ -226,9 +224,14 @@ django-solo==2.0.0 # notifications-api-common # sharing-configs # 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.10 # via -r requirements/ci.txt -djangorestframework==3.12.4 +djangorestframework==3.14.0 # via # -r requirements/ci.txt # commonground-api-common @@ -249,9 +252,9 @@ drf-nested-routers==0.93.4 # via # -r requirements/ci.txt # commonground-api-common -drf-spectacular==0.16.0 +drf-spectacular==0.26.5 # via -r requirements/ci.txt -drf-yasg==1.21.5 +drf-yasg==1.21.7 # via # -r requirements/ci.txt # commonground-api-common @@ -320,7 +323,7 @@ jsonschema==4.17.3 # via # -r requirements/ci.txt # drf-spectacular -kombu==5.2.4 +kombu==5.3.5 # via # -r requirements/ci.txt # celery @@ -332,7 +335,7 @@ markupsafe==2.1.2 # via # -r requirements/ci.txt # jinja2 -maykin-django-two-factor-auth[phonenumbers]==2.0.4 +maykin-2fa==1.0.0 # via -r requirements/ci.txt mccabe==0.7.0 # via flake8 @@ -361,10 +364,10 @@ packaging==23.1 # sphinx pathspec==0.11.1 # via black -phonenumbers==8.13.11 +phonenumberslite==8.13.30 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth pillow==9.5.0 # via -r requirements/ci.txt pip-tools==6.13.0 @@ -396,11 +399,12 @@ pyjwt==2.7.0 # -r requirements/ci.txt # commonground-api-common # gemma-zds-client -pyopenssl==23.1.1 +pyopenssl==24.0.0 # via # -r requirements/ci.txt # django-simple-certmanager # josepy + # webauthn # zgw-consumers pyproject-hooks==1.0.0 # via build @@ -428,19 +432,19 @@ pytz==2023.3 # via # -r requirements/ci.txt # celery - # django + # djangorestframework # drf-yasg pyyaml==6.0 # via # -r requirements/ci.txt - # commonground-api-common # drf-spectacular + # drf-yasg # gemma-zds-client # oyaml qrcode==6.1 # via # -r requirements/ci.txt - # maykin-django-two-factor-auth + # django-two-factor-auth redis==4.5.5 # via # -r requirements/ci.txt @@ -461,14 +465,6 @@ requests-mock==1.10.0 # via # -r requirements/ci.txt # zgw-consumers -ruamel-yaml==0.17.26 - # via - # -r requirements/ci.txt - # drf-yasg -ruamel-yaml-clib==0.2.7 - # via - # -r requirements/ci.txt - # ruamel-yaml sentry-sdk==1.23.1 # via -r requirements/ci.txt sharing-configs==0.1.2 @@ -477,7 +473,6 @@ six==1.16.0 # via # -r requirements/ci.txt # click-repl - # django-choices # isodate # python-dateutil # qrcode @@ -520,6 +515,7 @@ tomli==2.0.1 # via # black # build + # pyproject-hooks uritemplate==4.1.1 # via # -r requirements/ci.txt @@ -548,6 +544,10 @@ wcwidth==0.2.6 # 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/setup.cfg b/setup.cfg index cf5b7f91..1bd47879 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,18 +1,15 @@ [pycodestyle] -[pep8] ignore=W293,W291,E501,E261 max-line-length=88 exclude=migrations,static,media [isort] +profile = black combine_as_imports = true -default_section = THIRDPARTY -include_trailing_comma = true -line_length = 88 -multi_line_output = 3 -skip = env,node_modules +skip = + env + node_modules skip_glob = **/migrations/** -not_skip = __init__.py known_django=django known_first_party=objecttypes sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER diff --git a/src/objecttypes/accounts/__init__.py b/src/objecttypes/accounts/__init__.py index 3ef458c7..e69de29b 100644 --- a/src/objecttypes/accounts/__init__.py +++ b/src/objecttypes/accounts/__init__.py @@ -1 +0,0 @@ -default_app_config = "objecttypes.accounts.apps.AccountsConfig" diff --git a/src/objecttypes/accounts/models.py b/src/objecttypes/accounts/models.py index 53911146..dfbca325 100644 --- a/src/objecttypes/accounts/models.py +++ b/src/objecttypes/accounts/models.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from .managers import UserManager diff --git a/src/objecttypes/api/serializers.py b/src/objecttypes/api/serializers.py index 3b2285af..4014b4b9 100644 --- a/src/objecttypes/api/serializers.py +++ b/src/objecttypes/api/serializers.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework_nested.relations import NestedHyperlinkedRelatedField diff --git a/src/objecttypes/api/v1/views.py b/src/objecttypes/api/v1/views.py index 76fce8de..3fe5469b 100644 --- a/src/objecttypes/api/v1/views.py +++ b/src/objecttypes/api/v1/views.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import viewsets diff --git a/src/objecttypes/api/v2/views.py b/src/objecttypes/api/v2/views.py index f2325e07..57966164 100644 --- a/src/objecttypes/api/v2/views.py +++ b/src/objecttypes/api/v2/views.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import viewsets diff --git a/src/objecttypes/api/validators.py b/src/objecttypes/api/validators.py index ccfc47db..7bd692be 100644 --- a/src/objecttypes/api/validators.py +++ b/src/objecttypes/api/validators.py @@ -1,5 +1,5 @@ from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers @@ -10,21 +10,14 @@ class VersionUpdateValidator: message = _("Only draft versions can be changed") code = "non-draft-version-update" + requires_context = True - def set_context(self, serializer): - """ - This hook is called by the serializer instance, - prior to the validation call being made. - """ - # Determine the existing instance, if this is an update operation. - self.instance = getattr(serializer, "instance", None) - self.request = serializer.context["request"] - - def __call__(self, attrs): - if not self.instance: + def __call__(self, attrs, serializer): + instance = getattr(serializer, "instance", None) + if not instance: return - if self.instance.status != ObjectVersionStatus.draft: + if instance.status != ObjectVersionStatus.draft: raise serializers.ValidationError(self.message, code=self.code) @@ -45,22 +38,15 @@ class IsImmutableValidator: message = _("This field can't be changed") code = "immutable-field" + requires_context = True - def set_context(self, serializer_field): - """ - This hook is called by the serializer instance, - prior to the validation call being made. - """ - # Determine the existing instance, if this is an update operation. - self.serializer_field = serializer_field - self.instance = getattr(serializer_field.parent, "instance", None) - - def __call__(self, new_value): + def __call__(self, new_value, serializer_field): # no instance -> it's not an update - if not self.instance: + instance = getattr(serializer_field.parent, "instance", None) + if not instance: return - current_value = getattr(self.instance, self.serializer_field.source) + current_value = getattr(instance, serializer_field.source) if new_value != current_value: raise serializers.ValidationError(self.message, code=self.code) diff --git a/src/objecttypes/conf/base.py b/src/objecttypes/conf/base.py index 1439eb18..135f89a7 100644 --- a/src/objecttypes/conf/base.py +++ b/src/objecttypes/conf/base.py @@ -1,6 +1,5 @@ import os -# Django-hijack (and Django-hijack-admin) from django.urls import reverse_lazy from sentry_sdk.integrations import django, redis @@ -71,9 +70,6 @@ # External applications. "axes", "jsonsuit.apps.JSONSuitConfig", - "sniplates", - "hijack", - "hijack.contrib.admin", "mozilla_django_oidc", "mozilla_django_oidc_db", "django_jsonform", @@ -81,11 +77,12 @@ "solo", "drf_spectacular", "vng_api_common", - # 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", "sharing_configs", # Project applications. "objecttypes.accounts", @@ -102,12 +99,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", - "hijack.middleware.HijackUserMiddleware", - "django_otp.middleware.OTPMiddleware", ] ROOT_URLCONF = "objecttypes.urls" @@ -172,8 +168,6 @@ USE_I18N = True -USE_L10N = True - USE_TZ = True USE_THOUSAND_SEPARATOR = True @@ -358,22 +352,12 @@ "REMOTE_ADDR", ) -# Django-Hijack -HIJACK_LOGIN_REDIRECT_URL = "/" -HIJACK_LOGOUT_REDIRECT_URL = reverse_lazy("admin:accounts_user_changelist") -# The Admin mixin is used because we use a custom User-model. -HIJACK_REGISTER_ADMIN = False -# This is a CSRF-security risk. -# See: http://django-hijack.readthedocs.io/en/latest/configuration/#allowing-get-method-for-hijack-views -HIJACK_ALLOW_GET_REQUESTS = True - # # Sending EMAIL # EMAIL_HOST = config("EMAIL_HOST", default="localhost") -EMAIL_PORT = config( - "EMAIL_PORT", default=25 -) # disabled on Google Cloud, use 487 instead +# disabled on Google Cloud, use 487 instead: +EMAIL_PORT = config("EMAIL_PORT", default=25) EMAIL_HOST_USER = config("EMAIL_HOST_USER", default="") EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD", default="") EMAIL_USE_TLS = config("EMAIL_USE_TLS", default=False) @@ -422,10 +406,18 @@ # -# 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 @@ -433,3 +425,6 @@ OIDC_AUTHENTICATE_CLASS = "mozilla_django_oidc_db.views.OIDCAuthenticationRequestView" MOZILLA_DJANGO_OIDC_DB_CACHE = "oidc" MOZILLA_DJANGO_OIDC_DB_CACHE_TIMEOUT = 5 * 60 + +if config("DISABLE_2FA", default=False): # pragma: no cover + MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = AUTHENTICATION_BACKENDS diff --git a/src/objecttypes/conf/ci.py b/src/objecttypes/conf/ci.py index 756a6fe1..e8c8c3ff 100644 --- a/src/objecttypes/conf/ci.py +++ b/src/objecttypes/conf/ci.py @@ -25,6 +25,3 @@ # Django-axes # AXES_BEHIND_REVERSE_PROXY = False - -# Maykin fork of DJANGO-TWO-FACTOR-AUTH -TWO_FACTOR_FORCE_OTP_ADMIN = False diff --git a/src/objecttypes/conf/dev.py b/src/objecttypes/conf/dev.py index c2e0000c..a467ddab 100644 --- a/src/objecttypes/conf/dev.py +++ b/src/objecttypes/conf/dev.py @@ -107,8 +107,9 @@ r"django\.db\.models\.fields", ) -if "test" in sys.argv: - TWO_FACTOR_FORCE_OTP_ADMIN = False +# None of the authentication backends require two-factor authentication. +if config("DISABLE_2FA", default=True): # pragma: no cover + MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = AUTHENTICATION_BACKENDS # Override settings with local settings. try: diff --git a/src/objecttypes/core/admin.py b/src/objecttypes/core/admin.py index 3aa797aa..0d4b4af9 100644 --- a/src/objecttypes/core/admin.py +++ b/src/objecttypes/core/admin.py @@ -6,7 +6,7 @@ from django.shortcuts import redirect, render from django.urls import path, reverse from django.utils.html import format_html -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from jsonsuit.widgets import READONLY_WIDGET_MEDIA_CSS, READONLY_WIDGET_MEDIA_JS from sharing_configs.admin import SharingConfigsExportMixin, SharingConfigsImportMixin diff --git a/src/objecttypes/core/constants.py b/src/objecttypes/core/constants.py index a77fbe20..d795b06f 100644 --- a/src/objecttypes/core/constants.py +++ b/src/objecttypes/core/constants.py @@ -1,28 +1,25 @@ -from django.utils.translation import ugettext_lazy as _ +from django.db import models +from django.utils.translation import gettext_lazy as _ -from djchoices import ChoiceItem, DjangoChoices +class ObjectVersionStatus(models.TextChoices): + published = "published", _("Published") + draft = "draft", _("Draft") + deprecated = "deprecated", _("Deprecated") -class ObjectVersionStatus(DjangoChoices): - published = ChoiceItem("published", _("Published")) - draft = ChoiceItem("draft", _("Draft")) - deprecated = ChoiceItem("deprecated", _("Deprecated")) +class DataClassificationChoices(models.TextChoices): + open = "open", _("Open") + intern = "intern", _("Intern") + confidential = "confidential", _("Confidential") + strictly_confidential = "strictly_confidential", _("Strictly confidential") -class DataClassificationChoices(DjangoChoices): - open = ChoiceItem("open", _("Open")) - intern = ChoiceItem("intern", _("Intern")) - confidential = ChoiceItem("confidential", _("Confidential")) - strictly_confidential = ChoiceItem( - "strictly_confidential", _("Strictly confidential") - ) - -class UpdateFrequencyChoices(DjangoChoices): - real_time = ChoiceItem("real_time", _("Real-time")) - hourly = ChoiceItem("hourly", _("Hourly")) - daily = ChoiceItem("daily", _("Daily")) - weekly = ChoiceItem("weekly", _("Weekly")) - monthly = ChoiceItem("monthly", _("Monthly")) - yearly = ChoiceItem("yearly", _("Yearly")) - unknown = ChoiceItem("unknown", _("Unknown")) +class UpdateFrequencyChoices(models.TextChoices): + real_time = "real_time", _("Real-time") + hourly = "hourly", _("Hourly") + daily = "daily", _("Daily") + weekly = "weekly", _("Weekly") + monthly = "monthly", _("Monthly") + yearly = "yearly", _("Yearly") + unknown = "unknown", _("Unknown") diff --git a/src/objecttypes/core/forms.py b/src/objecttypes/core/forms.py index 88bd0cd2..a48b5519 100644 --- a/src/objecttypes/core/forms.py +++ b/src/objecttypes/core/forms.py @@ -2,7 +2,7 @@ from django import forms from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ import requests from rest_framework import exceptions diff --git a/src/objecttypes/core/models.py b/src/objecttypes/core/models.py index e96958d8..06f8c227 100644 --- a/src/objecttypes/core/models.py +++ b/src/objecttypes/core/models.py @@ -2,7 +2,7 @@ from datetime import date from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from .constants import ( DataClassificationChoices, diff --git a/src/objecttypes/templates/admin/base_site.html b/src/objecttypes/templates/admin/base_site.html index 45d313da..d8df6af0 100644 --- a/src/objecttypes/templates/admin/base_site.html +++ b/src/objecttypes/templates/admin/base_site.html @@ -23,9 +23,9 @@

{{ settings.PROJECT_NAME }} {% if site_url %} {{ settings.SITE_TITLE }} / {% endif %} - {% url 'admin:two_factor:profile' as 2fa_profile_url %} - {% if 2fa_profile_url %} - {% trans "View 2fa profile" %} / + {% url 'maykin_2fa:account_security' as 2fa_account_security_url %} + {% if 2fa_account_security_url %} + {% trans "Account security" %} / {% endif %} {% if user.has_usable_password %} {% trans 'Change password' %} / diff --git a/src/objecttypes/templates/admin/login.html b/src/objecttypes/templates/admin/login.html deleted file mode 100644 index d408af04..00000000 --- a/src/objecttypes/templates/admin/login.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "two_factor/admin/login.html" %} -{% load solo_tags i18n %} - - -{% block content %} -{{ block.super }} - -{% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %} -{% if oidc_config.enabled %} -
{% trans "or" %}
-
- {% trans "Login with organization account" %} -
-{% endif %} - -{% endblock %} diff --git a/src/objecttypes/templates/maykin_2fa/base.html b/src/objecttypes/templates/maykin_2fa/base.html new file mode 100644 index 00000000..68fa4301 --- /dev/null +++ b/src/objecttypes/templates/maykin_2fa/base.html @@ -0,0 +1,9 @@ +{% extends "maykin_2fa/base.html" %} + +{# Django 3.2 #} +{% block breadcrumbs %}{% endblock %} + +{# Do not show any version information #} +{% block footer %} + +{% endblock %} diff --git a/src/objecttypes/templates/maykin_2fa/login.html b/src/objecttypes/templates/maykin_2fa/login.html new file mode 100644 index 00000000..51987a80 --- /dev/null +++ b/src/objecttypes/templates/maykin_2fa/login.html @@ -0,0 +1,23 @@ +{% extends "maykin_2fa/login.html" %} +{% load solo_tags i18n %} + +{% block extra_login_options %} + {% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %} + {% if oidc_config.enabled %} +
{% trans "or" %}
+
+ {% trans "Login with organization account" %} +
+ {% endif %} +{% endblock %} + +{% block extra_recovery_options %} +
  • + {% trans 'Contact support to start the account recovery process' %} +
  • +{% endblock extra_recovery_options %} + +{# Do not show any version information #} +{% block footer %} + +{% endblock %} diff --git a/src/objecttypes/templates/two_factor/admin/login.html b/src/objecttypes/templates/two_factor/admin/login.html deleted file mode 100644 index afdff9b3..00000000 --- a/src/objecttypes/templates/two_factor/admin/login.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "admin/login.html" %} diff --git a/src/objecttypes/tests/test_objecttype_admin.py b/src/objecttypes/tests/test_objecttype_admin.py index f9fb9739..3d24f1f4 100644 --- a/src/objecttypes/tests/test_objecttype_admin.py +++ b/src/objecttypes/tests/test_objecttype_admin.py @@ -6,6 +6,7 @@ import requests_mock from django_webtest import WebTest from freezegun import freeze_time +from maykin_2fa.test import disable_admin_mfa from objecttypes.accounts.tests.factories import SuperUserFactory from objecttypes.core.constants import ( @@ -27,6 +28,7 @@ @freeze_time("2020-01-01") +@disable_admin_mfa() class AdminAddTests(WebTest): url = reverse_lazy("admin:core_objecttype_add") import_from_url = reverse_lazy("admin:import_from_url") @@ -244,6 +246,7 @@ def test_create_objecttype_from_url_with_nonexistent_url(self): self.assertEqual(ObjectType.objects.count(), 0) +@disable_admin_mfa() class AdminDetailTests(WebTest): @classmethod def setUpTestData(cls): diff --git a/src/objecttypes/tests/test_sharing_configs.py b/src/objecttypes/tests/test_sharing_configs.py index c88af9ce..e877f291 100644 --- a/src/objecttypes/tests/test_sharing_configs.py +++ b/src/objecttypes/tests/test_sharing_configs.py @@ -8,6 +8,7 @@ import requests_mock from django_webtest import WebTest from freezegun import freeze_time +from maykin_2fa.test import disable_admin_mfa from sharing_configs.models import SharingConfigsConfig from objecttypes.accounts.tests.factories import SuperUserFactory @@ -19,6 +20,7 @@ SHARING_CONFIGS_API_ROOT = "https://sharing-configs-api.example.org/api/v1/" +@disable_admin_mfa() @freeze_time("2020-01-01") class SharingConfigsTests(WebTest): def setUp(self) -> None: diff --git a/src/objecttypes/tests/v1/test_schema.py b/src/objecttypes/tests/v1/test_schema.py new file mode 100644 index 00000000..e0b07335 --- /dev/null +++ b/src/objecttypes/tests/v1/test_schema.py @@ -0,0 +1,10 @@ +from rest_framework import status +from rest_framework.test import APITestCase + +from .utils import reverse + + +class APISchemaTest(APITestCase): + def test_schema_endoint(self): + response = self.client.get(reverse("schema-redoc")) + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/objecttypes/tests/v2/test_schema.py b/src/objecttypes/tests/v2/test_schema.py new file mode 100644 index 00000000..e0b07335 --- /dev/null +++ b/src/objecttypes/tests/v2/test_schema.py @@ -0,0 +1,10 @@ +from rest_framework import status +from rest_framework.test import APITestCase + +from .utils import reverse + + +class APISchemaTest(APITestCase): + def test_schema_endoint(self): + response = self.client.get(reverse("schema-redoc")) + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/objecttypes/token/authentication.py b/src/objecttypes/token/authentication.py index 925d108a..c5c8bf71 100644 --- a/src/objecttypes/token/authentication.py +++ b/src/objecttypes/token/authentication.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from rest_framework import exceptions from rest_framework.authentication import TokenAuthentication as _TokenAuthentication diff --git a/src/objecttypes/token/management/commands/generate_token.py b/src/objecttypes/token/management/commands/generate_token.py index acdc56fe..418a0a4f 100644 --- a/src/objecttypes/token/management/commands/generate_token.py +++ b/src/objecttypes/token/management/commands/generate_token.py @@ -1,5 +1,5 @@ from django.core.management import BaseCommand -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from objecttypes.token.models import TokenAuth diff --git a/src/objecttypes/token/models.py b/src/objecttypes/token/models.py index 3a73a552..cb8818c7 100644 --- a/src/objecttypes/token/models.py +++ b/src/objecttypes/token/models.py @@ -2,7 +2,7 @@ import os from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class TokenAuth(models.Model): diff --git a/src/objecttypes/urls.py b/src/objecttypes/urls.py index c26a583c..a9c70b5f 100644 --- a/src/objecttypes/urls.py +++ b/src/objecttypes/urls.py @@ -7,6 +7,8 @@ from django.urls import include, path from django.views.generic.base import TemplateView +from maykin_2fa import monkeypatch_admin +from maykin_2fa.urls import urlpatterns as maykin_2fa_urlpatterns from rest_framework.settings import api_settings handler500 = "objecttypes.utils.views.server_error" @@ -14,6 +16,8 @@ admin.site.site_title = "objecttypes admin" admin.site.index_title = "Welcome to the objecttypes admin" +monkeypatch_admin() + urlpatterns = [ path( "admin/password_reset/", @@ -25,7 +29,7 @@ auth_views.PasswordResetDoneView.as_view(), name="password_reset_done", ), - path("admin/hijack/", include("hijack.urls")), + path("admin/", include((maykin_2fa_urlpatterns, "maykin_2fa"))), path("admin/", admin.site.urls), path( "reset///", diff --git a/src/objecttypes/utils/__init__.py b/src/objecttypes/utils/__init__.py index ef53cf91..e69de29b 100644 --- a/src/objecttypes/utils/__init__.py +++ b/src/objecttypes/utils/__init__.py @@ -1 +0,0 @@ -default_app_config = "objecttypes.utils.apps.UtilsConfig" diff --git a/src/objecttypes/utils/autoschema.py b/src/objecttypes/utils/autoschema.py index c5a32ffc..9faa4d7e 100644 --- a/src/objecttypes/utils/autoschema.py +++ b/src/objecttypes/utils/autoschema.py @@ -1,6 +1,6 @@ from uuid import UUID -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from drf_spectacular.openapi import AutoSchema as _AutoSchema from drf_spectacular.utils import OpenApiParameter @@ -24,9 +24,13 @@ def get_override_parameters(self): parent_path_headers = self.get_parent_path_headers() return content_type_headers + parent_path_headers - def _get_response_for_code(self, serializer, status_code, media_types=None): + def _get_response_for_code( + self, serializer, status_code, media_types=None, direction="response" + ): """add default description to the response""" - response = super()._get_response_for_code(serializer, status_code, media_types) + response = super()._get_response_for_code( + serializer, status_code, media_types, direction + ) if not response.get("description"): response["description"] = HTTP_STATUS_CODE_TITLES.get(int(status_code))