From f6105cfe30022948c55d9ff464cc1d58ac2b7385 Mon Sep 17 00:00:00 2001 From: Michael Kleimann Date: Mon, 9 Jan 2023 11:35:01 +0100 Subject: [PATCH] implement authentication and authorization with Keycloak gatekeeper --- Dockerfile | 2 +- docker-compose.yml | 22 +- docker/keycloak/Dockerfile | 7 +- docker/keycloak/import/master-realm.json | 1863 ---------------- docker/keycloak/import/master-users-0.json | 59 - docker/keycloak/import/tapir-realm.json | 1982 +++++++++++++++++ ldap_testdata.ldif | 83 - poetry.lock | 926 ++++---- pyproject.toml | 8 +- tapir/accounts/backends.py | 55 - tapir/accounts/fixtures/admin_account.json | 28 - tapir/accounts/forms.py | 4 +- tapir/accounts/middleware.py | 85 +- tapir/accounts/migrations/0001_initial.py | 131 +- tapir/accounts/migrations/0002_initial.py | 9 +- .../migrations/0003_auto_20221117_1841.py | 29 - .../migrations/0003_emailchangerequest.py} | 33 +- tapir/accounts/models.py | 534 ++--- .../static/accounts/silent-refresh.html | 7 + .../accounts/templates/accounts/keycloak.html | 14 + .../templates/accounts/keycloak_script.html | 103 + .../templates/accounts/link_expired.html | 21 + .../templates/accounts/password_update.html | 13 + .../registration/password_update.html | 25 - tapir/accounts/tests/factories/factories.py | 27 +- tapir/accounts/tests/test_integration.py | 42 +- tapir/accounts/tests/test_keycloak.py | 33 - tapir/accounts/tests/test_models.py | 24 - tapir/accounts/tests/test_registration.py | 112 +- .../tests/test_standard_user_detail_page.py | 35 - tapir/accounts/tests/test_views.py | 224 +- tapir/accounts/urls.py | 62 +- tapir/configuration/forms.py | 2 +- tapir/configuration/views.py | 2 +- tapir/core/static/core/css/custom.css | 2 +- tapir/core/templates/core/base.html | 66 +- tapir/core/templatetags/core.py | 88 +- tapir/core/tests/test_project.py | 23 - tapir/finance/__init__.py | 0 tapir/finance/migrations/0001_initial.py | 42 - .../migrations/0002_auto_20210411_0559.py | 18 - tapir/finance/migrations/__init__.py | 0 tapir/finance/models.py | 90 - tapir/log/migrations/0001_initial.py | 2 +- tapir/log/templatetags/log.py | 18 +- tapir/log/urls.py | 6 +- tapir/settings.py | 84 +- tapir/urls.py | 10 +- .../management/commands/populate_functions.py | 73 +- tapir/utils/tests_utils.py | 95 +- tapir/wirgarten/constants.py | 22 + tapir/wirgarten/factories.py | 51 +- tapir/wirgarten/forms/member/forms.py | 28 +- tapir/wirgarten/forms/pickup_location.py | 2 +- .../product_cfg/period_product_cfg_forms.py | 2 +- .../forms/registration/bestellcoop.py | 2 +- .../forms/registration/chicken_shares.py | 2 +- .../wirgarten/forms/registration/consents.py | 2 +- .../forms/registration/coop_shares.py | 2 +- .../forms/registration/harvest_shares.py | 2 +- .../forms/registration/payment_data.py | 3 +- tapir/wirgarten/forms/registration/summary.py | 2 +- tapir/wirgarten/migrations/0001_initial.py | 652 +++++- .../wirgarten/migrations/0002_exportedfile.py | 42 - .../migrations/0003_auto_20221007_1342.py | 23 - .../migrations/0004_auto_20221007_1610.py | 110 - .../migrations/0004_auto_20221019_1000.py | 41 - .../0005_alter_subscription_mandate_ref.py | 22 - .../0006_shareownership_mandate_ref.py | 23 - .../0007_alter_shareownership_mandate_ref.py | 22 - tapir/wirgarten/migrations/0008_payment.py | 37 - .../migrations/0009_payment_status.py | 26 - tapir/wirgarten/migrations/0010_deliveries.py | 36 - .../0011_deliveries_pickup_location.py | 23 - .../0012_alter_deliveries_pickup_location.py | 22 - .../migrations/0013_merge_20221022_1732.py | 13 - .../migrations/0014_auto_20221023_1730.py | 145 -- .../0015_payment_unique_mandate_ref_date.py | 19 - tapir/wirgarten/migrations/0016_taxrate.py | 53 - .../migrations/0017_auto_20221031_1541.py | 27 - .../0017_deliveryexceptionperiod.py | 48 - .../migrations/0018_merge_20221031_1741.py | 13 - .../0019_editfuturepaymentlogentry.py | 38 - .../0020_editfuturepaymentlogentry_comment.py | 18 - ...alter_editfuturepaymentlogentry_comment.py | 18 - .../migrations/0022_auto_20221108_1254.py | 53 - .../migrations/0022_auto_20221108_1312.py | 38 - ...023_alter_paymenttransaction_created_at.py | 22 - .../0024_paymenttransaction_file.py | 23 - .../0025_alter_paymenttransaction_file.py | 22 - .../migrations/0026_merge_20221109_1136.py | 13 - .../migrations/0027_auto_20221109_1136.py | 67 - .../migrations/0028_auto_20221121_1416.py | 54 - .../0029_alter_productprice_product.py | 23 - .../migrations/0030_auto_20221121_1758.py | 24 - .../migrations/0031_auto_20221123_1856.py | 35 - .../migrations/0032_auto_20221127_1418.py | 42 - .../0033_transfercoopshareslogentry.py | 40 - .../migrations/0034_auto_20221201_1139.py | 56 - .../0035_receivedcoopshareslogentry.py | 31 - .../0037_shareownership_created_at.py | 22 - .../0038_subscription_created_at.py | 22 - .../migrations/0039_member_created_at.py | 22 - ...cription_wirgarten_s_period__f679c5_idx.py | 20 - tapir/wirgarten/parameters.py | 23 +- tapir/wirgarten/service/file_export.py | 2 +- tapir/wirgarten/service/member.py | 41 +- tapir/wirgarten/service/payment.py | 2 +- tapir/wirgarten/service/products.py | 2 +- .../static/wirgarten/css/loading-spinner.css | 17 +- .../wirgarten/member/member_detail.html | 40 +- .../wirgarten/member/member_detail_alert.html | 24 +- .../pickup_location_config.html | 2 + tapir/wirgarten/tests.py | 6 - tapir/wirgarten/urls.py | 31 +- tapir/wirgarten/validators.py | 2 +- tapir/wirgarten/views/default_redirect.py | 53 +- tapir/wirgarten/views/member.py | 111 +- tapir/wirgarten/views/mixin.py | 2 +- tapir/wirgarten/views/modal.py | 8 +- tapir/wirgarten/views/registration_view.py | 9 +- 121 files changed, 4267 insertions(+), 5551 deletions(-) delete mode 100644 docker/keycloak/import/master-realm.json delete mode 100644 docker/keycloak/import/master-users-0.json create mode 100644 docker/keycloak/import/tapir-realm.json delete mode 100644 ldap_testdata.ldif delete mode 100644 tapir/accounts/backends.py delete mode 100644 tapir/accounts/fixtures/admin_account.json delete mode 100644 tapir/accounts/migrations/0003_auto_20221117_1841.py rename tapir/{wirgarten/migrations/0036_waitinglistentry.py => accounts/migrations/0003_emailchangerequest.py} (60%) create mode 100644 tapir/accounts/static/accounts/silent-refresh.html create mode 100644 tapir/accounts/templates/accounts/keycloak.html create mode 100644 tapir/accounts/templates/accounts/keycloak_script.html create mode 100644 tapir/accounts/templates/accounts/link_expired.html create mode 100644 tapir/accounts/templates/accounts/password_update.html delete mode 100644 tapir/accounts/templates/registration/password_update.html delete mode 100644 tapir/accounts/tests/test_keycloak.py delete mode 100644 tapir/accounts/tests/test_models.py delete mode 100644 tapir/accounts/tests/test_standard_user_detail_page.py delete mode 100644 tapir/core/tests/test_project.py delete mode 100644 tapir/finance/__init__.py delete mode 100644 tapir/finance/migrations/0001_initial.py delete mode 100644 tapir/finance/migrations/0002_auto_20210411_0559.py delete mode 100644 tapir/finance/migrations/__init__.py delete mode 100644 tapir/finance/models.py delete mode 100644 tapir/wirgarten/migrations/0002_exportedfile.py delete mode 100644 tapir/wirgarten/migrations/0003_auto_20221007_1342.py delete mode 100644 tapir/wirgarten/migrations/0004_auto_20221007_1610.py delete mode 100644 tapir/wirgarten/migrations/0004_auto_20221019_1000.py delete mode 100644 tapir/wirgarten/migrations/0005_alter_subscription_mandate_ref.py delete mode 100644 tapir/wirgarten/migrations/0006_shareownership_mandate_ref.py delete mode 100644 tapir/wirgarten/migrations/0007_alter_shareownership_mandate_ref.py delete mode 100644 tapir/wirgarten/migrations/0008_payment.py delete mode 100644 tapir/wirgarten/migrations/0009_payment_status.py delete mode 100644 tapir/wirgarten/migrations/0010_deliveries.py delete mode 100644 tapir/wirgarten/migrations/0011_deliveries_pickup_location.py delete mode 100644 tapir/wirgarten/migrations/0012_alter_deliveries_pickup_location.py delete mode 100644 tapir/wirgarten/migrations/0013_merge_20221022_1732.py delete mode 100644 tapir/wirgarten/migrations/0014_auto_20221023_1730.py delete mode 100644 tapir/wirgarten/migrations/0015_payment_unique_mandate_ref_date.py delete mode 100644 tapir/wirgarten/migrations/0016_taxrate.py delete mode 100644 tapir/wirgarten/migrations/0017_auto_20221031_1541.py delete mode 100644 tapir/wirgarten/migrations/0017_deliveryexceptionperiod.py delete mode 100644 tapir/wirgarten/migrations/0018_merge_20221031_1741.py delete mode 100644 tapir/wirgarten/migrations/0019_editfuturepaymentlogentry.py delete mode 100644 tapir/wirgarten/migrations/0020_editfuturepaymentlogentry_comment.py delete mode 100644 tapir/wirgarten/migrations/0021_alter_editfuturepaymentlogentry_comment.py delete mode 100644 tapir/wirgarten/migrations/0022_auto_20221108_1254.py delete mode 100644 tapir/wirgarten/migrations/0022_auto_20221108_1312.py delete mode 100644 tapir/wirgarten/migrations/0023_alter_paymenttransaction_created_at.py delete mode 100644 tapir/wirgarten/migrations/0024_paymenttransaction_file.py delete mode 100644 tapir/wirgarten/migrations/0025_alter_paymenttransaction_file.py delete mode 100644 tapir/wirgarten/migrations/0026_merge_20221109_1136.py delete mode 100644 tapir/wirgarten/migrations/0027_auto_20221109_1136.py delete mode 100644 tapir/wirgarten/migrations/0028_auto_20221121_1416.py delete mode 100644 tapir/wirgarten/migrations/0029_alter_productprice_product.py delete mode 100644 tapir/wirgarten/migrations/0030_auto_20221121_1758.py delete mode 100644 tapir/wirgarten/migrations/0031_auto_20221123_1856.py delete mode 100644 tapir/wirgarten/migrations/0032_auto_20221127_1418.py delete mode 100644 tapir/wirgarten/migrations/0033_transfercoopshareslogentry.py delete mode 100644 tapir/wirgarten/migrations/0034_auto_20221201_1139.py delete mode 100644 tapir/wirgarten/migrations/0035_receivedcoopshareslogentry.py delete mode 100644 tapir/wirgarten/migrations/0037_shareownership_created_at.py delete mode 100644 tapir/wirgarten/migrations/0038_subscription_created_at.py delete mode 100644 tapir/wirgarten/migrations/0039_member_created_at.py delete mode 100644 tapir/wirgarten/migrations/0040_subscription_wirgarten_s_period__f679c5_idx.py diff --git a/Dockerfile b/Dockerfile index b522c8f9..5460af81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10 +FROM python:3.11 ARG TAPIR_VERSION ENV TAPIR_VERSION=$TAPIR_VERSION ENV PYTHONUNBUFFERED=1 diff --git a/docker-compose.yml b/docker-compose.yml index c61c80d5..75308442 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,33 +1,16 @@ version: "3.9" services: - keycloak-server: + keycloak: build: context: ./docker/keycloak dockerfile: Dockerfile - ports: - "8080:8080" - volumes: - ./docker/keycloak/import:/opt/keycloak/data/import - environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin - - openldap: - image: "osixia/openldap" - environment: - LDAP_ORGANISATION: "WirGarten Lüneburg" - LDAP_DOMAIN: "lueneburg.wirgarten.com" - LDAP_ADMIN_PASSWORD: "admin" - LDAP_READONLY_USER: "true" - ports: - - "389:389" - volumes: - - ./ldap_testdata.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-testdata.ldif - # Required so that the container doesn't modify the testdata ldif - command: --copy-service web: build: . @@ -40,10 +23,9 @@ services: VIRTUAL_HOST: localhost DEBUG: 1 depends_on: - - openldap - db - selenium - - keycloak-server + - keycloak nginx-proxy: image: jwilder/nginx-proxy diff --git a/docker/keycloak/Dockerfile b/docker/keycloak/Dockerfile index 4f10e208..005ab626 100644 --- a/docker/keycloak/Dockerfile +++ b/docker/keycloak/Dockerfile @@ -1,11 +1,10 @@ -FROM quay.io/keycloak/keycloak:20.0.1 +FROM quay.io/keycloak/keycloak:20.0.3 COPY ./import/*.json /opt/keycloak/data/import/ -#RUN /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import +RUN /opt/keycloak/bin/kc.sh import --dir /opt/keycloak/data/import ENV ROOT_LOGLEVEL=ALL - -ENV KEYCLOAK_LOGLEVEL=DEBUG +ENV KEYCLOAK_LOGLEVEL=ALL CMD ["start-dev"] \ No newline at end of file diff --git a/docker/keycloak/import/master-realm.json b/docker/keycloak/import/master-realm.json deleted file mode 100644 index bbf10e6d..00000000 --- a/docker/keycloak/import/master-realm.json +++ /dev/null @@ -1,1863 +0,0 @@ -{ - "id" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5", - "realm" : "master", - "displayName" : "Keycloak", - "displayNameHtml" : "
Keycloak
", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 60, - "accessTokenLifespanForImplicitFlow" : 900, - "ssoSessionIdleTimeout" : 1800, - "ssoSessionMaxLifespan" : 36000, - "ssoSessionIdleTimeoutRememberMe" : 0, - "ssoSessionMaxLifespanRememberMe" : 0, - "offlineSessionIdleTimeout" : 2592000, - "offlineSessionMaxLifespanEnabled" : false, - "offlineSessionMaxLifespan" : 5184000, - "clientSessionIdleTimeout" : 0, - "clientSessionMaxLifespan" : 0, - "clientOfflineSessionIdleTimeout" : 0, - "clientOfflineSessionMaxLifespan" : 0, - "accessCodeLifespan" : 60, - "accessCodeLifespanUserAction" : 300, - "accessCodeLifespanLogin" : 1800, - "actionTokenGeneratedByAdminLifespan" : 43200, - "actionTokenGeneratedByUserLifespan" : 300, - "oauth2DeviceCodeLifespan" : 600, - "oauth2DevicePollingInterval" : 5, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "loginWithEmailAllowed" : true, - "duplicateEmailsAllowed" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "permanentLockout" : false, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "2e05712d-165a-483b-9d29-8dddf853862a", - "name" : "default-roles-master", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "manage-account", "view-profile" ] - } - }, - "clientRole" : false, - "containerId" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5", - "attributes" : { } - }, { - "id" : "ba049a81-af94-4a34-bdd7-4cd8d8c4be7e", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5", - "attributes" : { } - }, { - "id" : "32b85071-7bf6-4d37-90b7-08d11996401d", - "name" : "admin", - "description" : "${role_admin}", - "composite" : true, - "composites" : { - "realm" : [ "create-realm" ], - "client" : { - "master-realm" : [ "query-users", "query-realms", "manage-users", "query-clients", "query-groups", "view-authorization", "view-users", "view-events", "manage-authorization", "create-client", "view-realm", "impersonation", "view-identity-providers", "manage-events", "view-clients", "manage-clients", "manage-identity-providers", "manage-realm" ] - } - }, - "clientRole" : false, - "containerId" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5", - "attributes" : { } - }, { - "id" : "1fb34fae-5080-40d8-8405-5801e6b2a645", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5", - "attributes" : { } - }, { - "id" : "3634553d-18bd-4359-94a0-add43acf7a23", - "name" : "create-realm", - "description" : "${role_create-realm}", - "composite" : false, - "clientRole" : false, - "containerId" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5", - "attributes" : { } - } ], - "client" : { - "security-admin-console" : [ ], - "tapir-client" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "6f7d5bd3-52b7-4ca4-98a9-55344cb3339e", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "2c4d2777-4fde-405b-864f-b40de6ebf12a", - "attributes" : { } - } ], - "master-realm" : [ { - "id" : "6894f030-597d-4c0b-8758-8cac11f5c0f7", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "ce9156ba-30a7-4968-85f1-ce21f066a2ed", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "e97aaa68-f534-4e47-ac46-cb699c6554f3", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "b22325fa-7e3e-4612-830c-dd9e3919b73f", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "57b02aed-19c1-492e-b1c9-1cdc3a2d4e35", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "17ee0988-2412-4a0c-98d8-015d0e40be16", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "0530c834-336b-41dd-a55b-6ca6ccd66852", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "master-realm" : [ "query-users", "query-groups" ] - } - }, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "8c2e057f-e5a9-4a8d-9591-f50fab82e19e", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "5789d27d-5b7b-40ed-b055-7692d0900c62", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "bcd485b1-90a4-4b97-a7c9-af76ea59af53", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "f7301121-14d0-47a2-b693-2697390218ea", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "5b5d5fb3-06c4-4957-9efc-5a0d80075dff", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "ef363c43-956b-4736-b324-ca4f01c282ab", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "03be3bf7-4031-407f-b0e2-abb293c0a709", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "0d23a015-ffdb-4959-a1b0-bd7b330b2cd8", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "master-realm" : [ "query-clients" ] - } - }, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "147bc54b-f8b8-49ac-ae99-7c86a3489c87", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "b497a3b8-f626-4214-8416-c3574b9755a6", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - }, { - "id" : "c1c251c3-f6b1-4b6e-bec9-156fbc4620f0", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "attributes" : { } - } ], - "account" : [ { - "id" : "560825e6-f475-4f91-9b08-81ab14065511", - "name" : "view-groups", - "description" : "${role_view-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "33d461ee-2894-4105-9a56-f337b2787eb1", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "e10825ff-4c6b-45fe-9690-7a11bf9a8b86", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } - }, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "5944355a-1c08-4037-97ad-3e4232ad6859", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "3653bcc4-05db-43d1-b10c-8c2c86f8e633", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "3b29ba20-376b-4dd2-b426-379572480752", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "5efd8004-a91c-4b74-9959-f41f5875dc14", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } - }, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - }, { - "id" : "fe97aca8-0304-4fe5-ad33-3e1eb535b385", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "attributes" : { } - } ] - } - }, - "groups" : [ ], - "defaultRole" : { - "id" : "2e05712d-165a-483b-9d29-8dddf853862a", - "name" : "default-roles-master", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "d2b4d76c-26b9-42f8-8481-793fa013bfb5" - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName" ], - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account", "view-groups" ] - } ] - }, - "clients" : [ { - "id" : "d237ccb6-5649-4c0e-9ff2-508b2f2761f6", - "clientId" : "account", - "name" : "${client_account}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/master/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/master/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "a0fd9be1-dec2-40fd-85f3-e9749655c389", - "clientId" : "account-console", - "name" : "${client_account-console}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/master/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/master/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "88f76b85-d557-4906-9318-574a3db195e4", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "dfba3e67-a12f-41b8-8738-2f1bfa729a5b", - "clientId" : "admin-cli", - "name" : "${client_admin-cli}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : false, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "2c4d2777-4fde-405b-864f-b40de6ebf12a", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "ec49dcde-8212-41dd-990a-4328a657ccd5", - "clientId" : "master-realm", - "name" : "master Realm", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "bae8092d-0e1d-4b36-8865-933da9e7beeb", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/master/console/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/master/console/*" ], - "webOrigins" : [ "+" ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "047b72f0-c258-4c6f-b5e0-689c541a7ead", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "841da464-1a8a-49ac-9084-123ee5419467", - "clientId" : "tapir-client", - "name" : "", - "description" : "", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "secret" : "xwMpCSQRu7xvIXN6vosGxnA4dreZdwug", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : true, - "publicClient" : false, - "frontchannelLogout" : true, - "protocol" : "openid-connect", - "attributes" : { - "oidc.ciba.grant.enabled" : "false", - "oauth2.device.authorization.grant.enabled" : "false", - "client.secret.creation.time" : "1671732464", - "backchannel.logout.session.required" : "true", - "backchannel.logout.revoke.offline.tokens" : "false" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : -1, - "protocolMappers" : [ { - "id" : "62553958-2f85-4185-b109-3a0389559f1c", - "name" : "Client ID", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientId", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientId", - "jsonType.label" : "String" - } - }, { - "id" : "1259d989-fdb5-4f08-af5a-008c0518ada1", - "name" : "Client Host", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientHost", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientHost", - "jsonType.label" : "String" - } - }, { - "id" : "6f8e13b0-063d-4f44-be51-febbc63c3da3", - "name" : "Client IP Address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientAddress", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientAddress", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "94dd1c81-79f2-4821-8c97-fa5ec0c1d58f", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "f0c5359b-a485-493b-b2b5-b5c0ff0edb7b", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "905fc3a0-349a-4190-82f0-ca8476ecd689", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${emailScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "6624c2b5-dec5-471b-ae66-432597bdbf4b", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - }, { - "id" : "daac461e-ba0d-4e12-8880-9f4efda3e08d", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - } ] - }, { - "id" : "5477a928-7d07-4c3b-89bc-94a1d8faaf53", - "name" : "microprofile-jwt", - "description" : "Microprofile - JWT built-in scope", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "c16d4d8f-5bb5-4a63-892f-3596c6d3c10c", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "multivalued" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - }, { - "id" : "512f4905-6aeb-46ab-9c2c-7c902b74e7a4", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "76ace678-8974-4d91-b506-03ba5d8ac3d3", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${phoneScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "c8189243-c4e2-4a2a-910f-e01fa0c2a7eb", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "8ee3d0d3-cbed-45f3-a76e-0a755d5960fd", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "5396f55f-02c2-4443-91bb-be18e7885a76", - "name" : "offline_access", - "description" : "OpenID Connect built-in scope: offline_access", - "protocol" : "openid-connect", - "attributes" : { - "consent.screen.text" : "${offlineAccessScopeConsentText}", - "display.on.consent.screen" : "true" - } - }, { - "id" : "2970002a-0db4-479a-9e82-70ea4503d8d2", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${addressScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "d863d367-f75c-4845-ac57-23f062ebca3d", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "user.attribute.postal_code" : "postal_code", - "userinfo.token.claim" : "true", - "user.attribute.street" : "street", - "id.token.claim" : "true", - "user.attribute.region" : "region", - "access.token.claim" : "true", - "user.attribute.locality" : "locality" - } - } ] - }, { - "id" : "91149133-9fae-4689-a0b5-d850f52ac980", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${profileScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "91fcbf18-4f3f-4e6b-8e21-4efa8e43865c", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "ad534aaf-bd02-4949-a710-e50e211d0802", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "23094d9f-9290-471e-9628-26d7e8e601d9", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "52a7a00a-a7b1-4177-9945-5afa6da8a296", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "7792fc3f-8ce3-4c28-bf7e-c78b0046e924", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "8824dd67-ee49-4884-9428-7922dd076c1d", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "3b6153dc-3ec6-4df8-8ea4-5f1a5544f4e2", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - }, { - "id" : "4a3d8238-9084-4ebf-8440-292b03a6f26f", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - }, { - "id" : "e60b37a0-294c-4874-9e31-0afa67f779d8", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - }, { - "id" : "73851eac-2bb5-4abc-bdc8-b73e301f0435", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - }, { - "id" : "600bd773-1362-4864-b820-6f1750e2f279", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "537f8dd1-da47-4c46-ab90-ca5c3fb369a2", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "1ce8ffbb-4a1e-46bf-bdac-4079192cc130", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "7e3b4f4e-e671-4e41-b833-47615d0b1bac", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "f9631ba5-56cd-4619-b877-a696e36f912c", - "name" : "acr", - "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "d7ea8374-0d42-48f7-a2cb-d2a124e2b132", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "bdf217ba-514e-4f06-aaed-f24314a937a7", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "true", - "consent.screen.text" : "${rolesScopeConsentText}" - }, - "protocolMappers" : [ { - "id" : "19ba7d9b-1f7a-4b92-b473-c3012945aebd", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "84114101-a767-4f31-931f-016e241fce71", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - }, { - "id" : "d7e3ba00-b74c-49e5-b30b-6179147bd59a", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "e544d5e5-c64b-4b0a-895c-45bf92e4ed39", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false", - "consent.screen.text" : "" - }, - "protocolMappers" : [ { - "id" : "0418a95c-a4bb-454c-8149-9cca1731576e", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], - "browserSecurityHeaders" : { - "contentSecurityPolicyReportOnly" : "", - "xContentTypeOptions" : "nosniff", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "xXSSProtection" : "1; mode=block", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "strictTransportSecurity" : "max-age=31536000; includeSubDomains" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityProviders" : [ ], - "identityProviderMappers" : [ ], - "components" : { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "6f47891d-d30e-4ff7-aad7-287ee2d3e1f2", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "2cad6868-07c6-452a-9414-16b49dfe617d", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - }, { - "id" : "5d435447-77ba-4337-9bdb-31fa721e3119", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "ae08a017-aaa8-4105-8165-0dbb919003ac", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper" ] - } - }, { - "id" : "6000372e-b2e3-4a03-b564-42bd7d316ccb", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "0cf2e493-4a31-4dcc-8906-e59fa5d4e80a", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper" ] - } - }, { - "id" : "a4858855-311c-4a7d-8b81-94a8f3122559", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - }, { - "id" : "8b15ebe3-286f-4ce4-b3e7-e8a158e8102b", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "b1fa9e37-3bc0-408a-8e5b-d3eeeba0cb65", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEpAIBAAKCAQEAm59ZF2LdDBX6dM8M+foI72nP+bY2mT3dYe+Awax/pnLwRWX988vtCZInQsxu+qEXsyyRLe8Lvyujsfs7/iyLwqz2S4k6mCtttAEwQzFwmtS/7CYilhOR0GeMpKT2JuchZnjyzX+GepB5VchIphPQFzgyoILI4Lh1uZD54Yic+uFokC9KZgsWSaXjcGuRI5aAVFfmVyCrSxoTsGzSvK6Pfn4cLZdyG+r28lHWkKUwuVZUKSoyFWm2z+Z1u/A1UHsHuULuCTn1X9V2xcappHmogkkUyeG8MGnL6yAruWLLjEMBRdaZAYYaR4isbZWhMIi+ikXmqP45q9BTiEnY2XA7zQIDAQABAoIBABP91XEWpzTdQe5thso6H5m56xi2Gq927hCdttmqcj+MtmcD4irGgK3hE6AWKQ+TUDsZN5FCCy1EyuObVaqiSFX66Czt9RQFSDN+j5eiK0gImpsYjwreerXeB4hDFMgMmxLR6McTYdAu7RdjAkorc2j4NDDAfFO5/o2XHTKm3RuxU8vd4AEQqrZ0BBDAM7n5f9Moq+tYEljF8/PTYfqPGFqq1ZJVQvV1ApsUV6hWVuiSDwhjL+tj29Hq2X/VdwYH/x4yj6zqEXRGfAfccdjS6+9RKXfKJnDRl/NTtjBbTVc+dPq3l27WYs4ncJHLEt5/DwJHEwXAXotSWlU7diA48fUCgYEA2K7fXwJ0H1Evzzjf88Prjddk4nORWe0q2uhzzfHIg64XjlOHo01f9RQQhsCgomwpr9hoFJCx53xcwJczSq+Faw7bBfDqp5tHhcmCT6081m1Ol+N5ML509MJA0r01jn/eXxcMLhMMar0b3ro8Us42Yb/coPaliyhKlLtviqQr8dsCgYEAt9wnJabFCekk5+PbJWF0dpaZGRzgyegYvHt85tAJBG6qWvmhNQuKjJ1OdK0Ua8qyehiuKKFG/fKb8HpdxqwOuVV83HcBmgTl/e2v+N3ihJi7GFBUOb3KnJEmVU2+r7K6UJgqWTisx/EDHaJ0A6AU1TllT2bkykE0/mUMUJbnHXcCgYEAscL840/6/fo307PgHnZbl5jeXdzboL+uCbrrzkeN7WF0V8H1I6aFk2SrjjEXV0iPBoDuGfj+yl6/JQyl+p52QPcF/iDAEVKVYY+IRV9k6rDv6jGmqbOF9YorbfflqQsD3lTlHCNqa2LLK3TzqiccfCLPmIngE4KxdMU0Y7P9jUMCgYBQm8/5/PT0VGhe1pJVADYsa/hxfyy6uOxnboijNWBOtiowOrPH6uhyn+Eu3FdQrwyy5TEuFQ9n9T8THsMQgIzwCp/0lKR6H12qh9QX+0f9tGyKy2Ux75juLeEtkylgJK70+NHQTj3KZ0tjab8Ne9jGZySYvFbgqdgPFMRibNalHwKBgQDQ6GlCoXC1N1AZpXvfxzAji/XUWdFqw1CAax3wDAJIKFYtGq4fHf3YcvJpAi/9xfZ+uCesuuslMeUv41oKi8uND7NUHWns471xgpyWtARkquevt8xSj0xK890xu2Y2ppx7izSKYX47WwJIOdSQKnGSf0bxgFpxMT2Tc+uXnQI06A==" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIICmzCCAYMCBgGFOwRqlDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjIxMjIyMTgwNTIzWhcNMzIxMjIyMTgwNzAzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbn1kXYt0MFfp0zwz5+gjvac/5tjaZPd1h74DBrH+mcvBFZf3zy+0JkidCzG76oRezLJEt7wu/K6Ox+zv+LIvCrPZLiTqYK220ATBDMXCa1L/sJiKWE5HQZ4ykpPYm5yFmePLNf4Z6kHlVyEimE9AXODKggsjguHW5kPnhiJz64WiQL0pmCxZJpeNwa5EjloBUV+ZXIKtLGhOwbNK8ro9+fhwtl3Ib6vbyUdaQpTC5VlQpKjIVabbP5nW78DVQewe5Qu4JOfVf1XbFxqmkeaiCSRTJ4bwwacvrICu5YsuMQwFF1pkBhhpHiKxtlaEwiL6KReao/jmr0FOISdjZcDvNAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACUcwIdm+ueYwrMuc9ht0h+bsqe+JmszsbUOYAsaFkvJ0ytdN9jlvnPEQ5Zq2vFl0s2nchzUoV0sNMZOLoUxZL78LfB6SRPkmQ9xVJIOe0H7K7Tv1NUEqAkQTP5gTGmnCBRYVBqpn32EOX3qbNtQHpa/d0LOFHw8kjo0415v0K9hnkTUJNOMprdgnLuEahBNWmimKaLGdsy9d6F96DJNHnVKIY4SfwEA2OjphQl4EvYOVfq+PG5WbeJp2z4h8hzLuSZ/xm82jnt2RWdjq3KqiW9rLNUm5OXD0q3jksZ9E1oTjyIgQFavO5y5iiTNLpYAYXxW+4dFSajlHUFnMySgVk0=" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] - } - }, { - "id" : "8831e12f-c974-4b3f-81f3-62157b640b8d", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "4f463c49-5a32-447b-8f09-8340917e5d1e" ], - "secret" : [ "Q69gROLrzMH7uPVfVD42Jg" ], - "priority" : [ "100" ] - } - }, { - "id" : "ee974620-f7d3-421c-b29b-63c003da4351", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEogIBAAKCAQEAziOUS06QNE5jpHdI6K7pr/835SzRuLJUdPnWBbBcXDZF8sGRrNJmh2UIuXpn6vmsInrnJcdt+TZHo12O6MZ1Cqs9SrE35pH3NorDuckAoEqJgv+h1aYPfAdA3eRs3JRveFkGvKYs0PnjFSxGh7XvPKOYcOc87lIA3INiXTWPNCwsAXZYiFToaxN8EJnOy0R/V6jxisDUI70Pwt2ZvJ0Qq2ZGGUAubwkwVsDOrrhGGmsreH9Jj/qdJHl5NL1fRJcnmRFHeFmj21J8N8PO6QbweOVUXetuJ9AWXtJJJNKCOzbgcrsxU0Sv1PKf3CcztnrNjb+qhfSxj7eHCbd3RHjvpQIDAQABAoIBAAV9Gwi6Scqqv6p0ZqOTbLN79zAJ9neVf/wxXsp9/KeT5l7lWoygeYxnW0kdAkFWbofof9kYq0v0bwnhhtmZ1yn2j0Y628Mu5cc1AKskt/iMo4ayacu3hTgHNWnzGBbzeNhafnxtbEoQLUS2Dha0+G7D6F4xOgUad91nPio/L9Xyvtun5nJbVy/dskiDUdqgM+P0HCzbVioWUrKhG/V1ug/7SX0wFxcaKnA/JsFS/lP0juQZvWvBwULzdwymEsvbWQKo1tsJNyQBfAb6LzBcFAy5ddHpaB9SzjELIBMTMWMJhLhn9ffVeq4M6xS+UHpjM2PP7/LcXbuBKm45KLP+MY0CgYEA/FcQv8i0QRcOj6l2/qFLSbDmf5ozgtXwO9JX5hxS670xvdi2RWSZYRCraXXumT4KULVFFeRCU/zmbz9Bd7OH2b3EbApS2O4wP9v+qlhIAWoVYgVlh4KntQf5EzBWHhiqBiv5yhnXmz8CMshPPvk5NIrXhSpSVn+/Ozq3XJMMcx8CgYEA0SD4SVfwPA5/i/yvviCTd6VK3JlVkQ7wXKzrbbYMTgz2QqTyUslosEIuxmrZ3SJfjoaxP+vN2UMMXR+3jFORvoZhUfxR4TFoD/xhgX8aH8r1t3Zq5vpnY0TB3gynKUXF9NdOenkHx1da6h6kSI0vJUJhzKGQ9R8Ybxt+bY8cKLsCgYBU5XH0JDFI6Nn0SX6IYnmy/DpAWf/Ul+ewKK978rGPMGLozV1dcWANAQ+6dnvkwLO1vZbVq6sJGL/qU6zR+evNvQO1ma+P3msLQRen9fWbzDYPeWNUOQf1Zw5Mf2mpzIB/fRIYBaO9TnCN50iKFk1BuSiTW87B3U7YPYPtOGfq3wKBgCCKttgC3RkbUHGiEl7NhuT5TZriKVR3NKPXLJNX3rZOgguAy0xilu3GfqzVd61XafMttmsx8Eg8jLH4+4DUiDtTgdVWJA+ctoq2RR33f0MvO+kefigPEwN5cYlyApkDiHg/ALFCIBm+2CDSP9IiVhHAMKO9CGd8PTsc1iGKQWF9AoGAVgI5dVMo5mNd/1eIijKTDoZpHRoZsMugJkClUaOxihld7sbtFuFO5dxL/zKwgqDrBXdvNq/v41ZaJqZF7LtERyf1rWES+mHZYvpIMK2yx01DHWPQL6ABmzi8EOX2O35pBE2JITcLSeaox9UjmPguXzs0CwZAwhp7YdRC5K+ZgK8=" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIICmzCCAYMCBgGFOwRovzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjIxMjIyMTgwNTIyWhcNMzIxMjIyMTgwNzAyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOI5RLTpA0TmOkd0jorumv/zflLNG4slR0+dYFsFxcNkXywZGs0maHZQi5emfq+awieuclx235NkejXY7oxnUKqz1KsTfmkfc2isO5yQCgSomC/6HVpg98B0Dd5GzclG94WQa8pizQ+eMVLEaHte88o5hw5zzuUgDcg2JdNY80LCwBdliIVOhrE3wQmc7LRH9XqPGKwNQjvQ/C3Zm8nRCrZkYZQC5vCTBWwM6uuEYaayt4f0mP+p0keXk0vV9ElyeZEUd4WaPbUnw3w87pBvB45VRd624n0BZe0kkk0oI7NuByuzFTRK/U8p/cJzO2es2Nv6qF9LGPt4cJt3dEeO+lAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEREPpNGwzfEW0w5ZyXhjFoCO8nn6VJm0o0N06LLtceYcWdhShGAc9ZS6nl4JCR8zOtDG17SuFgygGc19hOCuiZsmID+9KRMy0v2PSi3Inj5jEf/6m4vYeNRZKZJkBpGn15eqi9xtakmyzP9Q9ApuWGy+W1y6kHUcwA0y/BR77ZOcDkqjOc7SLdzyMrB9NPRCpAzVIQ+YlLjZhsbV99vGqHdaCyu1fsxMQFcFP1rg+P58UCer75b3bsRamWh11vglRVvtPMPMKQIMa/jiLFnBOs3yedHQxG7l8EI8FwHjOk6szS464n43y7eqHU+aBXk3cS2PtUjumiLb9Op3D2RA1s=" ], - "priority" : [ "100" ] - } - }, { - "id" : "bb5783ef-2b3b-4bbf-91c8-eaa388f3c15c", - "name" : "hmac-generated", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "4f7990f3-4510-4936-bd20-eabe09fa2c33" ], - "secret" : [ "uMiKbFTS6KC5R-8UAZvQF9c7RrDkb2woR1uABojkW5ATms5JpDnErUhzxCztHiTqxsVGf-lAPPxXDDtK5zFIBw" ], - "priority" : [ "100" ], - "algorithm" : [ "HS256" ] - } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "df9ced71-9c89-4063-ad9c-7e448595f539", - "alias" : "Account verification options", - "description" : "Method with which to verity the existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-email-verification", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Verify Existing Account by Re-authentication", - "userSetupAllowed" : false - } ] - }, { - "id" : "e601bb13-83be-4dd5-a518-58233a2c4e1d", - "alias" : "Authentication Options", - "description" : "Authentication options.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "basic-auth", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "basic-auth-otp", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "c5953b9c-4863-481a-9f50-4b3a20762bf3", - "alias" : "Browser - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "35e8fa9a-a8a7-4a33-b72d-82bdee6074f1", - "alias" : "Direct Grant - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "af4aea3d-4a4a-4e76-aa99-a6c9a9b179c8", - "alias" : "First broker login - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "7ae5bc65-a2a3-4a07-bb0b-7528d4401731", - "alias" : "Handle Existing Account", - "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-confirm-link", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Account verification options", - "userSetupAllowed" : false - } ] - }, { - "id" : "3909e37c-2b6b-463b-b182-911ba607affa", - "alias" : "Reset - Conditional OTP", - "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "5b57f53f-b983-47ec-93a2-ea487143352f", - "alias" : "User creation or linking", - "description" : "Flow for the existing/non-existing user alternatives", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "create unique user config", - "authenticator" : "idp-create-user-if-unique", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Handle Existing Account", - "userSetupAllowed" : false - } ] - }, { - "id" : "32a1f09a-0dea-445e-b043-06f0869b870c", - "alias" : "Verify Existing Account by Re-authentication", - "description" : "Reauthentication of existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "First broker login - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "3f6db694-9982-49a1-a960-1d84089cd192", - "alias" : "browser", - "description" : "browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "identity-provider-redirector", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 25, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "forms", - "userSetupAllowed" : false - } ] - }, { - "id" : "fd082691-524d-41b8-9e3f-793cf553af08", - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-secret-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-x509", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 40, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "1f672cc5-1888-41e5-8395-4a764acab1c1", - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "Direct Grant - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "26b609b2-d876-415d-835b-c9560795ec16", - "alias" : "docker auth", - "description" : "Used by Docker clients to authenticate against the IDP", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "docker-http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "5c9f2861-2888-4159-a6ff-e6607eb8b4ca", - "alias" : "first broker login", - "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "review profile config", - "authenticator" : "idp-review-profile", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "User creation or linking", - "userSetupAllowed" : false - } ] - }, { - "id" : "d0eab3e0-bf28-4f99-870e-4d81ca04b2ae", - "alias" : "forms", - "description" : "Username, password, otp and other auth forms.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "9b9d4483-2558-4298-bbfc-3a1ef79a22a3", - "alias" : "http challenge", - "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "no-cookie-redirect", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Authentication Options", - "userSetupAllowed" : false - } ] - }, { - "id" : "3135f32c-34a7-4d06-86c1-2cda8f8ecbd5", - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "registration form", - "userSetupAllowed" : false - } ] - }, { - "id" : "6b2c4d14-504b-445c-9fdc-be4d73b9cbd7", - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-profile-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 40, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-password-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 50, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-recaptcha-action", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 60, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "bacfafb6-a294-40e9-a070-1ec6509b5c7c", - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-credential-email", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 40, - "autheticatorFlow" : true, - "flowAlias" : "Reset - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "b26497b7-6c36-40ec-ba2d-9a5688a8cc3b", - "alias" : "saml ecp", - "description" : "SAML ECP Profile Authentication Flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - } ], - "authenticatorConfig" : [ { - "id" : "aba0ac1c-330c-4689-aaa0-c0ad31abb27b", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" - } - }, { - "id" : "35636262-9602-4301-bc7f-dd618b7583d2", - "alias" : "review profile config", - "config" : { - "update.profile.on.first.login" : "missing" - } - } ], - "requiredActions" : [ { - "alias" : "CONFIGURE_TOTP", - "name" : "Configure OTP", - "providerId" : "CONFIGURE_TOTP", - "enabled" : true, - "defaultAction" : false, - "priority" : 10, - "config" : { } - }, { - "alias" : "terms_and_conditions", - "name" : "Terms and Conditions", - "providerId" : "terms_and_conditions", - "enabled" : false, - "defaultAction" : false, - "priority" : 20, - "config" : { } - }, { - "alias" : "UPDATE_PASSWORD", - "name" : "Update Password", - "providerId" : "UPDATE_PASSWORD", - "enabled" : true, - "defaultAction" : false, - "priority" : 30, - "config" : { } - }, { - "alias" : "UPDATE_PROFILE", - "name" : "Update Profile", - "providerId" : "UPDATE_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 40, - "config" : { } - }, { - "alias" : "VERIFY_EMAIL", - "name" : "Verify Email", - "providerId" : "VERIFY_EMAIL", - "enabled" : true, - "defaultAction" : false, - "priority" : 50, - "config" : { } - }, { - "alias" : "delete_account", - "name" : "Delete Account", - "providerId" : "delete_account", - "enabled" : false, - "defaultAction" : false, - "priority" : 60, - "config" : { } - }, { - "alias" : "webauthn-register", - "name" : "Webauthn Register", - "providerId" : "webauthn-register", - "enabled" : true, - "defaultAction" : false, - "priority" : 70, - "config" : { } - }, { - "alias" : "webauthn-register-passwordless", - "name" : "Webauthn Register Passwordless", - "providerId" : "webauthn-register-passwordless", - "enabled" : true, - "defaultAction" : false, - "priority" : 80, - "config" : { } - }, { - "alias" : "update_user_locale", - "name" : "Update User Locale", - "providerId" : "update_user_locale", - "enabled" : true, - "defaultAction" : false, - "priority" : 1000, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients", - "dockerAuthenticationFlow" : "docker auth", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", - "cibaAuthRequestedUserHint" : "login_hint", - "parRequestUriLifespan" : "60", - "cibaInterval" : "5", - "realmReusableOtpCode" : "false" - }, - "keycloakVersion" : "20.0.2", - "userManagedAccessAllowed" : false, - "clientProfiles" : { - "profiles" : [ ] - }, - "clientPolicies" : { - "policies" : [ ] - } -} \ No newline at end of file diff --git a/docker/keycloak/import/master-users-0.json b/docker/keycloak/import/master-users-0.json deleted file mode 100644 index 02dd7d15..00000000 --- a/docker/keycloak/import/master-users-0.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "realm" : "master", - "users" : [ { - "id" : "073f4fd6-a973-403e-8924-35bc6cc8ceb3", - "createdTimestamp" : 1671732423773, - "username" : "admin", - "enabled" : true, - "totp" : false, - "emailVerified" : false, - "credentials" : [ { - "id" : "3ca5d6eb-c048-4eea-8011-039b29bb2a33", - "type" : "password", - "createdDate" : 1671732424101, - "secretData" : "{\"value\":\"ziJn4WRT8lKxM2HWO6AlRvp8gs1O2LJH60NXjcc2R0ng8UPuAhxKFM/Iui6oQD28hej4hl+l61g/2OzHCl2MUA==\",\"salt\":\"AwbUzHLaeyiOWU2b0aneVw==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-master", "admin" ], - "notBefore" : 0, - "groups" : [ ] - }, { - "id" : "4b7c6b67-bd17-4f0f-b21b-2d0146501223", - "createdTimestamp" : 1671732847102, - "username" : "example@example.com", - "enabled" : true, - "totp" : false, - "emailVerified" : false, - "firstName" : "Example", - "lastName" : "Example", - "email" : "example@example.com", - "credentials" : [ { - "id" : "8aa7d936-c8a9-4931-88ba-87b92c28022a", - "type" : "password", - "createdDate" : 1671737324631, - "secretData" : "{\"value\":\"4V0wQpyqty9pfz/JbC2bIbux4NtTTxiltIktKEnO/xV6jqAPId/i1s28fZ20hVVWqvGSd8Xh+8ct22N33k5MaQ==\",\"salt\":\"GtEpDo2lfYQ2Qt0VoCR/jA==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-master" ], - "notBefore" : 0, - "groups" : [ ] - }, { - "id" : "3ed34f57-0a3e-4990-b923-629535cbfc8b", - "createdTimestamp" : 1671732464691, - "username" : "service-account-tapir-client", - "enabled" : true, - "totp" : false, - "emailVerified" : false, - "serviceAccountClientId" : "tapir-client", - "credentials" : [ ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-master", "admin" ], - "notBefore" : 0, - "groups" : [ ] - } ] -} \ No newline at end of file diff --git a/docker/keycloak/import/tapir-realm.json b/docker/keycloak/import/tapir-realm.json new file mode 100644 index 00000000..8ee0a6ff --- /dev/null +++ b/docker/keycloak/import/tapir-realm.json @@ -0,0 +1,1982 @@ +{ + "id": "9cefa2cd-b1c8-45e9-a043-636965ebeed1", + "realm": "tapir", + "displayName": "WirGarten Lüneburg", + "displayNameHtml": "

WirGarten Lüneburg

", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": true, + "rememberMe": true, + "verifyEmail": true, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRole": { + "id": "a57e9b19-c927-4212-94fe-72a815d76f83", + "name": "default-roles-tapir", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "9cefa2cd-b1c8-45e9-a043-636965ebeed1" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppGoogleName", + "totpAppFreeOTPName" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "402d136c-d773-4f3a-b74d-200a85397da7", + "createdTimestamp": 1675081967536, + "username": "service-account-tapir-backend", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "tapir-backend", + "disableableCredentialTypes": [], + "requiredActions": [], + "notBefore": 0 + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "02337f40-f869-491c-8bb4-43185511bebb", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/tapir/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/tapir/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a603776e-e064-4ed9-bbdd-3c4b8745ad50", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/tapir/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/tapir/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "cb081e2c-2d7e-4402-984b-bc6fab5f1f09", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ce80af2a-90a2-405e-bf25-c1d066fa55f6", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c01cc669-6079-4d0a-88d2-cfa48f2cc3f2", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ab4d130b-24fd-453d-b2e8-0ebfcc437e9d", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a987511e-28b7-473a-b600-a3bb0d7d2f37", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/tapir/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/tapir/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "16fa60e0-e422-4ead-b719-bff18a5d9a38", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "67990045-1ccd-4782-b3c3-7c0eacf804c0", + "clientId": "tapir-backend", + "name": "tapir-backend", + "description": "Tapir Backend Client for managing users", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "oauth2.device.authorization.grant.enabled": "false", + "client.secret.creation.time": "1675081967", + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "d370e628-d8d2-43f7-9e56-788e0e041fa3", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "id": "9978afe8-1f56-45ce-93e8-c86b55953df8", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "0ea6b62d-833b-44b5-9169-41d2440292ad", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:tapir-backend:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "_id": "960a91a9-2214-4c49-8d18-eab24f7fb902", + "uris": [ + "/*" + ] + } + ], + "policies": [], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } + }, + { + "id": "652095b9-2005-4174-b2a5-9c814a1dfb6e", + "clientId": "tapir-frontend", + "name": "tapir-frontend", + "description": "Tapir Client for user authentication in UI", + "rootUrl": "http://localhost:8000", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "*", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "021b1eef-5465-4a97-95e9-4d4ef564644c", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "34abb4b0-4110-43fd-a9d0-5ef24271e1d8", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "bbe7a2c2-ba8c-4340-b43a-e4d0a36e43f4", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "5ce3df40-e71f-4e39-9bd7-9fb675e1db93", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "ee3283ed-736c-4f0f-b87b-ae2bdb5dc6e5", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "c8e92153-6df8-4d36-8467-ed6ca8e8e0c6", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "cb685a60-539b-4763-b2ed-9492bd0a7fd3", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "85402e5f-3552-4c8f-878b-63f9932a454a", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "89fe530f-cbd1-4a57-a0ac-5d25975e04ff", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "4d18b2c6-2d35-44a5-8802-76406de8a0b1", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "deecb6d0-04ee-4372-be58-6702ca4e6ff6", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "6f98bd98-a298-4e20-b942-21bc4446b693", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "f9c0bab2-3f37-4b12-b58d-921f07eaa0a7", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "a30c5194-68bd-4456-94cb-3ad217971c1f", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "b77a303d-5f10-4e18-ac0c-615e7bab036e", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "6b8087d4-0425-4b79-b9b4-0719238ffdf4", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "5d598c26-e216-4612-aac0-46a8a2b4a389", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "b3c489c2-7a25-4a3c-9f6a-2bd079d99278", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "5312bd36-8c92-49e7-8dfa-ebe31e42ab9e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "8de8e46f-dad1-4c17-8551-aaa2f8c45c0d", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "240bfa8a-ac32-443b-b082-7b22fab076d1", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "3db5d5fe-61de-4c69-ba54-f92afd43ed42", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "ddea02cb-85dd-43ef-b0c2-3e89f9718544", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "04f07495-329f-49e1-84fb-1ef6903abc96", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "215a9c86-4196-4172-95e3-01c840df0fe0", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "50968e5b-5819-41a7-8d10-be8d890adc35", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "faa61c42-e249-4f22-9c55-93a066fc6e0a", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "5f16bc68-aecb-4b55-8a82-c8240a71fb2b", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "09774d3a-927d-4ee1-827b-7b7a2b55ed91", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "522b95e8-7002-4d87-8507-4ab8c714669c", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "88f42f9d-cced-479c-becd-d522dfc6c7c3", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "e42a3ab9-5afc-4f5a-867b-edbd90094010", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "b9596a40-729b-448b-a277-7736a6be79eb", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "42efef3d-c895-48a6-a43f-e56cdc5a6ec3", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "2b816c4a-c1d8-4503-8c3d-784c80656cb8", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "45f1d40b-8042-4862-b8c2-48a97e82305c", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "8e11f0d3-fcdf-4d4b-917a-467cc326bced", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "f375499e-7cd0-4434-89b3-23a458686d5d", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "acfb7d32-de25-4f30-8522-5e80ebcfc084", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "f059100d-4852-47f2-b91d-5d4260b9d4d2", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "5b040844-b270-445c-91b4-c5b5c8ab5e6e", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "5730ab40-788d-42cb-8df7-a685af05f2b2", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "48d85566-f9a4-42a3-87ce-b17e020ad82e", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "2298d210-3b29-452b-a59e-d9e9e66f5006", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "038c5195-d863-40eb-b28b-3ff4ef93ead7", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "bcb64cf1-f786-4d2e-b4b2-e7170a568863", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "b05a4f6a-5443-4347-8439-d7be5ea3f150", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "bc00b1f7-dd96-455c-b21b-b2a4cae16c18", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "b689b9dc-2f16-4b06-b11a-785671f16491", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "5c13c55e-7596-42ae-9ebf-e7b29134edb1", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "35dcaf1c-d72e-4f95-be56-4dca8d819958", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b4191a26-a907-4fe2-9052-0708b7b03dee", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "fe2c62c3-5df9-458c-86b7-9769a23268a4", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "414a3563-b6a3-4a2d-8533-98a50d8c4eb4", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "cad8dd50-6169-4c23-8f89-ed6b39ec2f04", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "4881afd0-8711-4bf3-827f-311c5fcf1f31", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "0758c18d-1751-4c05-b2fd-6bf26d548edd", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "71b054d1-e706-4911-9db1-9614e1ae9f3c", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "4b7eca47-b7d0-4f64-a81a-da2da4dec2a8", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "fae5e558-0f2e-4ac9-94df-0fbb48ec6da6", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4f874080-5e39-4728-b80d-21ec0f93f11e", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "d474dce2-92e5-4ebd-b1cf-cf84da34e267", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "73f0fd7a-504e-4f3c-93a1-f756f5c88a77", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "08a8af08-3a0e-4467-ba4a-bc5258b48855", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "10de12e3-65cb-4f7a-912f-38d128bfccc8", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "41bdd0ea-6df9-4b5a-9685-1d101634314e", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "b30108c5-7c92-41ac-88b2-440c1f504ed6", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "961faaed-dd5c-49f7-8a4a-8479c8bc2508", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "2db4c5c6-d8c5-43ef-9232-4356c71dfc6e", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "130a7014-6bb6-4bd6-8c5e-624dd1790779", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "12472f7a-b2d5-4929-bfbc-dc9bdc140e12", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DevicePollingInterval": "5", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "http://localhost:8080", + "acr.loa.map": "{}" + }, + "keycloakVersion": "20.0.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/ldap_testdata.ldif b/ldap_testdata.ldif deleted file mode 100644 index ed24950b..00000000 --- a/ldap_testdata.ldif +++ /dev/null @@ -1,83 +0,0 @@ -dn: ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -ou: people -objectClass: organizationalUnit -objectClass: top - -dn: uid=admin,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: inetOrgPerson -objectClass: organizationalPerson -objectClass: person -cn: admin -sn: admin -uid: admin - -dn: uid=admin,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: modify -replace: userPassword -userPassword: admin - -dn: uid=ariana.perrin,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: inetOrgPerson -objectClass: organizationalPerson -objectClass: person -cn: ariana.perrin -sn: ariana.perrin -uid: ariana.perrin - -dn: uid=ariana.perrin,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: modify -replace: userPassword -userPassword: ariana.perrin - -dn: uid=roberto.cortes,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: inetOrgPerson -objectClass: organizationalPerson -objectClass: person -cn: roberto.cortes -sn: roberto.cortes -uid: roberto.cortes - -dn: uid=roberto.cortes,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: modify -replace: userPassword -userPassword: roberto.cortes - -dn: uid=nicolas.vicente,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: inetOrgPerson -objectClass: organizationalPerson -objectClass: person -cn: nicolas.vicente -sn: nicolas.vicente -uid: nicolas.vicente - -dn: uid=nicolas.vicente,ou=people,dc=lueneburg,dc=wirgarten,dc=com -changetype: modify -replace: userPassword -userPassword: nicolas.vicente - -dn: ou=groups,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: organizationalUnit -objectClass: top -ou: groups - -dn: cn=vorstand,ou=groups,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: groupOfNames -objectClass: top -cn: vorstand -member: uid=admin,ou=people,dc=lueneburg,dc=wirgarten,dc=com -member: uid=ariana.perrin,ou=people,dc=lueneburg,dc=wirgarten,dc=com - -dn: cn=member-office,ou=groups,dc=lueneburg,dc=wirgarten,dc=com -changetype: add -objectClass: groupOfNames -objectClass: top -cn: member-office -member: uid=admin,ou=people,dc=lueneburg,dc=wirgarten,dc=com -member: uid=roberto.cortes,ou=people,dc=lueneburg,dc=wirgarten,dc=com diff --git a/poetry.lock b/poetry.lock index de9c0d5a..7a8e06a5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -19,14 +19,14 @@ python-versions = "*" [[package]] name = "asgiref" -version = "3.5.2" +version = "3.6.0" description = "ASGI specs, helper code, and adapters" category = "main" optional = false python-versions = ">=3.7" [package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] name = "asttokens" @@ -60,29 +60,30 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "22.1.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs"] +docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["attrs", "zope.interface"] +tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] +tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] [[package]] name = "autopep8" -version = "2.0.0" +version = "2.0.1" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -pycodestyle = ">=2.9.1" -tomli = "*" +pycodestyle = ">=2.10.0" [[package]] name = "backcall" @@ -94,7 +95,7 @@ python-versions = "*" [[package]] name = "beautifulsoup4" -version = "4.11.1" +version = "4.11.2" description = "Screen-scraping library" category = "main" optional = false @@ -128,7 +129,6 @@ click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -148,13 +148,13 @@ python-versions = ">=3.7" cffi = ">=1.1.0" [package.extras] -doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "numpy", "pikepdf", "pytest"] +doc = ["sphinx", "sphinx-rtd-theme"] +test = ["pytest", "flake8", "isort", "numpy", "pikepdf"] xcb = ["xcffib (>=0.3.2)"] [[package]] name = "cairosvg" -version = "2.5.2" +version = "2.6.0" description = "A Simple SVG Converter based on Cairo" category = "main" optional = false @@ -169,7 +169,7 @@ tinycss2 = "*" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] -test = ["pytest-cov", "pytest-flake8", "pytest-isort", "pytest-runner"] +test = ["pytest", "flake8", "isort"] [[package]] name = "celery" @@ -216,7 +216,7 @@ s3 = ["boto3 (>=1.9.125)"] slmq = ["softlayer-messaging (>=1.0.3)"] solar = ["ephem"] sqlalchemy = ["sqlalchemy"] -sqs = ["kombu[sqs]"] +sqs = ["kombu"] tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] @@ -251,14 +251,11 @@ python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.0.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] +python-versions = "*" [[package]] name = "cleo" @@ -343,7 +340,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 [[package]] name = "coverage" -version = "6.5.0" +version = "7.1.0" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -392,20 +389,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "deprecated" -version = "1.2.13" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] - [[package]] name = "distlib" version = "0.3.6" @@ -416,7 +399,7 @@ python-versions = "*" [[package]] name = "django" -version = "3.2.16" +version = "3.2.17" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -444,14 +427,16 @@ django = "*" [[package]] name = "django-bootstrap-datepicker-plus" -version = "3.0.6" -description = "Bootstrap3/Bootstrap4/Bootstrap5 DatePickerInput, TimePickerInput, DateTimePickerInput, MonthPickerInput, YearPickerInput with date-range-picker functionality for django version >= 1.8" +version = "5.0.3" +description = "Bootstrap3/Bootstrap4/Bootstrap5 DatePickerInput, TimePickerInput, DateTimePickerInput, MonthPickerInput, YearPickerInput" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7,<4.0" [package.dependencies] -django = ">=1.8" +Django = ">=2,<5" +pydantic = "*" +typing-extensions = "*" [[package]] name = "django-bootstrap5" @@ -511,21 +496,6 @@ python-versions = ">=3.6" [package.dependencies] Django = ">=2.2" -[[package]] -name = "django-ldapdb" -version = "1.5.1" -description = "A LDAP database backend for Django" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -Django = ">=2.2" -python-ldap = ">=3.0" - -[package.extras] -dev = ["check-manifest", "factory-boy", "flake8", "isort (>=5.0.0)", "tox", "volatildap (>=1.1.0)", "wheel", "zest.releaser[recommended]"] - [[package]] name = "django-localflavor" version = "3.1" @@ -591,14 +561,14 @@ sqlparse = "*" [[package]] name = "django-tables2" -version = "2.4.1" +version = "2.5.1" description = "Table/data-grid framework for Django" category = "main" optional = false python-versions = "*" [package.dependencies] -Django = ">=1.11" +Django = ">=3.2" [package.extras] tablib = ["tablib"] @@ -664,12 +634,12 @@ python-versions = ">=3.6" Faker = ">=0.7.0" [package.extras] -dev = ["Django", "Pillow", "SQLAlchemy", "coverage", "flake8", "isort", "mongoengine", "tox", "wheel (>=0.32.0)", "zest.releaser[recommended]"] -doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] +dev = ["coverage", "django", "flake8", "isort", "pillow", "sqlalchemy", "mongoengine", "wheel (>=0.32.0)", "tox", "zest.releaser"] +doc = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "15.3.4" +version = "16.6.1" description = "Faker is a Python package that generates fake data for you." category = "dev" optional = false @@ -680,15 +650,15 @@ python-dateutil = ">=2.4" [[package]] name = "filelock" -version = "3.8.2" +version = "3.9.0" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2022.12.7)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)", "pytest (>=7.2)"] [[package]] name = "gprof2dot" @@ -706,9 +676,6 @@ category = "main" optional = false python-versions = ">=3.5" -[package.dependencies] -setuptools = ">=3.0" - [package.extras] eventlet = ["eventlet (>=0.24.1)"] gevent = ["gevent (>=1.4.0)"] @@ -728,14 +695,14 @@ six = ">=1.9" webencodings = "*" [package.extras] -all = ["chardet (>=2.2)", "genshi", "lxml"] +all = ["genshi", "chardet (>=2.2)", "lxml"] chardet = ["chardet (>=2.2)"] genshi = ["genshi"] lxml = ["lxml"] [[package]] name = "identify" -version = "2.5.9" +version = "2.5.17" description = "File identification library for Python" category = "dev" optional = false @@ -754,15 +721,15 @@ python-versions = ">=3.5" [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "ipython" -version = "8.7.0" +version = "8.9.0" description = "IPython: Productive Interactive Computing" category = "main" optional = false @@ -777,13 +744,13 @@ jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" -prompt-toolkit = ">=3.0.11,<3.1.0" +prompt-toolkit = ">=3.0.30,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" [package.extras] -all = ["Sphinx (>=1.3)", "black", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "testpath", "trio"] +all = ["black", "ipykernel", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "docrepr", "matplotlib", "stack-data", "pytest (<7)", "typing-extensions", "pytest (<7.1)", "pytest-asyncio", "testpath", "nbconvert", "nbformat", "ipywidgets", "notebook", "ipyparallel", "qtconsole", "curio", "matplotlib (!=3.2.0)", "numpy (>=1.20)", "pandas", "trio"] black = ["black"] doc = ["ipykernel", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "docrepr", "matplotlib", "stack-data", "pytest (<7)", "typing-extensions", "pytest (<7.1)", "pytest-asyncio", "testpath"] kernel = ["ipykernel"] @@ -793,7 +760,7 @@ notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] +test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.20)", "pandas", "trio"] [[package]] name = "jedi" @@ -880,7 +847,7 @@ source = ["Cython (>=0.29.7)"] [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false @@ -921,12 +888,9 @@ category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -[package.dependencies] -setuptools = "*" - [[package]] name = "packaging" -version = "22.0" +version = "23.0" description = "Core utilities for Python packages" category = "dev" optional = false @@ -954,7 +918,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pathspec" -version = "0.10.3" +version = "0.11.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false @@ -973,7 +937,7 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.2" +version = "8.13.5" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." category = "main" optional = false @@ -989,27 +953,27 @@ python-versions = "*" [[package]] name = "pillow" -version = "9.3.0" +version = "9.4.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "platformdirs" -version = "2.6.0" +version = "2.6.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] [[package]] name = "pluggy" @@ -1025,7 +989,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.20.0" +version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -1036,8 +1000,7 @@ cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" +virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" @@ -1093,17 +1056,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "pyasn1-modules" -version = "0.2.8" -description = "A collection of ASN.1-based protocols modules." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.5.0" - [[package]] name = "pycodestyle" version = "2.10.0" @@ -1120,9 +1072,24 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pydantic" +version = "1.10.4" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pygments" -version = "2.13.0" +version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -1132,23 +1099,26 @@ python-versions = ">=3.6" plugins = ["importlib-metadata"] [[package]] -name = "pylev" -version = "1.4.0" -description = "A pure Python Levenshtein implementation that's not freaking GPL'd." +name = "pyjwt" +version = "2.6.0" +description = "JSON Web Token implementation in Python" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.4.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "pre-commit"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"] [[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" +name = "pylev" +version = "1.4.0" +description = "A pure Python Levenshtein implementation that's not freaking GPL'd." category = "main" optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +python-versions = "*" [[package]] name = "pyphen" @@ -1159,8 +1129,8 @@ optional = false python-versions = ">=3.7" [package.extras] -doc = ["sphinx", "sphinx_rtd_theme"] -test = ["coverage[toml]", "flake8 (<5)", "pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "pytest-xdist"] +doc = ["sphinx", "sphinx-rtd-theme"] +test = ["pytest", "pytest-xdist", "pytest-flake8", "pytest-isort", "pytest-cov", "coverage", "flake8 (<5)"] [[package]] name = "pytest" @@ -1197,7 +1167,7 @@ pytest = ">=4.6" toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-django" @@ -1212,7 +1182,7 @@ pytest = ">=5.4.0" [package.extras] docs = ["sphinx", "sphinx-rtd-theme"] -testing = ["Django", "django-configurations (>=2.0)"] +testing = ["django", "django-configurations (>=2.0)"] [[package]] name = "pytest-sugar" @@ -1253,12 +1223,12 @@ rsa = "*" [package.extras] cryptography = ["cryptography (>=3.4.0)"] -pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] -pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] +pycrypto = ["pycrypto (>=2.6.0,<2.7.0)", "pyasn1"] +pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)", "pyasn1"] [[package]] name = "python-keycloak" -version = "2.6.0" +version = "2.9.0" description = "python-keycloak is a Python package providing access to the Keycloak API." category = "main" optional = false @@ -1271,19 +1241,7 @@ requests-toolbelt = ">=0.9.1,<0.10.0" urllib3 = ">=1.26.0,<2.0.0" [package.extras] -docs = ["Sphinx (>=5.0.2,<6.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (>=0.9.1,<0.10.0)", "m2r2 (>=0.3.2,<0.4.0)", "mock (>=4.0.3,<5.0.0)", "readthedocs-sphinx-ext (>=2.1.8,<3.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-autoapi (>=1.8.4,<2.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"] - -[[package]] -name = "python-ldap" -version = "3.4.3" -description = "Python modules for implementing LDAP clients" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyasn1 = ">=0.3.7" -pyasn1_modules = ">=0.1.5" +docs = ["Sphinx (>=5.3.0,<6.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (>=0.9.1,<0.10.0)", "m2r2 (>=0.3.2,<0.4.0)", "mock (>=4.0.3,<5.0.0)", "readthedocs-sphinx-ext (>=2.1.9,<3.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-autoapi (>=2.0.0,<3.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"] [[package]] name = "python-stdnum" @@ -1296,11 +1254,11 @@ python-versions = "*" [package.extras] soap = ["zeep"] soap-alt = ["suds"] -soap-fallback = ["PySimpleSOAP"] +soap-fallback = ["pysimplesoap"] [[package]] name = "pytz" -version = "2022.6" +version = "2022.7.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -1316,7 +1274,7 @@ python-versions = ">=3.6" [[package]] name = "redis" -version = "4.4.0" +version = "4.4.2" description = "Python client for Redis database and key-value store" category = "main" optional = false @@ -1331,7 +1289,7 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "requests" -version = "2.28.1" +version = "2.28.2" description = "Python HTTP for Humans." category = "main" optional = false @@ -1339,13 +1297,13 @@ python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-toolbelt" @@ -1380,19 +1338,6 @@ python-versions = "*" [package.dependencies] urllib3 = "*" -[[package]] -name = "setuptools" -version = "65.5.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -1431,7 +1376,7 @@ executing = ">=1.2.0" pure-eval = "*" [package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] +tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"] [[package]] name = "tablib" @@ -1453,7 +1398,7 @@ yaml = ["pyyaml"] [[package]] name = "termcolor" -version = "2.1.1" +version = "2.2.0" description = "ANSI color formatting for output in terminal" category = "dev" optional = false @@ -1474,8 +1419,8 @@ python-versions = ">=3.7" webencodings = ">=0.4" [package.extras] -doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pytest"] +doc = ["sphinx", "sphinx-rtd-theme"] +test = ["pytest", "isort", "flake8"] [[package]] name = "toml" @@ -1485,14 +1430,6 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "tomlkit" version = "0.10.2" @@ -1503,7 +1440,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "traitlets" -version = "5.7.1" +version = "5.9.0" description = "Traitlets Python configuration system" category = "main" optional = false @@ -1511,21 +1448,27 @@ python-versions = ">=3.7" [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -lint = ["black (>=22.6.0)", "mdformat (>0.7)", "ruff (>=0.0.156)"] -test = ["pre-commit", "pytest"] -typing = ["mypy (>=0.990)"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.13" +version = "1.26.14" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1563,11 +1506,11 @@ python-versions = ">=3.7.0" [package.extras] docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] -testing = ["coverage (>=5.0)", "pytest", "pytest-cover"] +testing = ["pytest", "pytest-cover", "coverage (>=5.0)"] [[package]] name = "wcwidth" -version = "0.2.5" +version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" category = "main" optional = false @@ -1589,12 +1532,11 @@ cssselect2 = ">=0.1" html5lib = ">=0.999999999" Pillow = ">=4.0.0" Pyphen = ">=0.9.1" -setuptools = ">=39.2.0" tinycss2 = ">=1.0.0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] -test = ["pytest-cov", "pytest-flake8", "pytest-isort", "pytest-runner"] +test = ["pytest-runner", "pytest-cov", "pytest-flake8", "pytest-isort"] [[package]] name = "webencodings" @@ -1614,7 +1556,7 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" [package.extras] docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] -testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"] +testing = ["pytest (>=3.1.0)", "coverage", "pytest-cov", "pytest-xdist"] [[package]] name = "webtest" @@ -1630,8 +1572,8 @@ waitress = ">=0.8.5" WebOb = ">=1.2" [package.extras] -docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"] -tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-cov"] +docs = ["docutils", "pylons-sphinx-themes (>=1.0.8)", "Sphinx (>=1.8.1)"] +tests = ["coverage", "pastedeploy", "pyquery", "pytest", "pytest-cov", "wsgiproxy2"] [[package]] name = "werkzeug" @@ -1642,21 +1584,13 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage", "pallets-sphinx-themes", "pytest", "pytest-timeout", "sphinx", "sphinx-issues", "tox"] +dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] -[[package]] -name = "wrapt" -version = "1.14.1" -description = "Module for decorators, wrappers and monkey patching." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - [metadata] lock-version = "1.1" -python-versions = "^3.10" -content-hash = "e9a4ac1b43e7ca9463027419280a2f13bb94d22a72717e74929b3807be0aa08f" +python-versions = "^3.11" +content-hash = "bc6da9b1128e7a07dd88c34b0f693bdac1af2b3517e66b359dd4807c4acc868a" [metadata.files] amqp = [ @@ -1668,8 +1602,8 @@ appnope = [ {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] asgiref = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, + {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, + {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, ] asttokens = [ {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, @@ -1683,20 +1617,20 @@ atomicwrites = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] autopep8 = [ - {file = "autopep8-2.0.0-py2.py3-none-any.whl", hash = "sha256:ad924b42c2e27a1ac58e432166cc4588f5b80747de02d0d35b1ecbd3e7d57207"}, - {file = "autopep8-2.0.0.tar.gz", hash = "sha256:8b1659c7f003e693199f52caffdc06585bb0716900bbc6a7442fd931d658c077"}, + {file = "autopep8-2.0.1-py2.py3-none-any.whl", hash = "sha256:be5bc98c33515b67475420b7b1feafc8d32c1a69862498eda4983b45bffd2687"}, + {file = "autopep8-2.0.1.tar.gz", hash = "sha256:d27a8929d8dcd21c0f4b3859d2d07c6c25273727b98afc984c039df0f0d86566"}, ] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] beautifulsoup4 = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, + {file = "beautifulsoup4-4.11.2-py3-none-any.whl", hash = "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39"}, + {file = "beautifulsoup4-4.11.2.tar.gz", hash = "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"}, ] billiard = [ {file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"}, @@ -1720,8 +1654,8 @@ cairocffi = [ {file = "cairocffi-1.4.0.tar.gz", hash = "sha256:509339b32ccd8d7b00c2204c32736cde78db53a32e6a162d312478d25626cd9a"}, ] cairosvg = [ - {file = "CairoSVG-2.5.2-py3-none-any.whl", hash = "sha256:98c276b7e4f0caf01e5c7176765c104ffa1aa1461d63b2053b04ab663cf7052b"}, - {file = "CairoSVG-2.5.2.tar.gz", hash = "sha256:b0b9929cf5dba005178d746a8036fcf0025550f498ca54db61873322384783bc"}, + {file = "CairoSVG-2.6.0-py3-none-any.whl", hash = "sha256:05069d5316e9f02f33028942f96929e01782db41e6ff07d8454c8d021b5b52f3"}, + {file = "CairoSVG-2.6.0.tar.gz", hash = "sha256:d5ec93e90101b3b6e82aa245d0546ee9b016cfda0b6344675159830d853d5d04"}, ] celery = [ {file = "celery-5.2.7-py3-none-any.whl", hash = "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14"}, @@ -1802,8 +1736,94 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, + {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, + {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, + {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, + {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, + {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, + {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, + {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, + {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, ] cleo = [ {file = "cleo-0.8.1-py2.py3-none-any.whl", hash = "sha256:141cda6dc94a92343be626bb87a0b6c86ae291dfc732a57bf04310d4b4201753"}, @@ -1834,56 +1854,57 @@ colorama = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"}, + {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"}, + {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"}, + {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"}, + {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"}, + {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"}, + {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"}, + {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"}, + {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"}, + {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"}, + {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"}, + {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"}, + {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, + {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, ] crashtest = [ {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, @@ -1906,16 +1927,16 @@ distlib = [ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] django = [ - {file = "Django-3.2.16-py3-none-any.whl", hash = "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121"}, - {file = "Django-3.2.16.tar.gz", hash = "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d"}, + {file = "Django-3.2.17-py3-none-any.whl", hash = "sha256:59c39fc342b242fb42b6b040ad8b1b4c15df438706c1d970d416d63cdd73e7fd"}, + {file = "Django-3.2.17.tar.gz", hash = "sha256:644288341f06ebe4938eec6801b6bd59a6534a78e4aedde2a153075d11143894"}, ] django-appconf = [ {file = "django-appconf-1.0.5.tar.gz", hash = "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4"}, {file = "django_appconf-1.0.5-py3-none-any.whl", hash = "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d"}, ] django-bootstrap-datepicker-plus = [ - {file = "django-bootstrap-datepicker-plus-3.0.6.tar.gz", hash = "sha256:ffe2fa6568b51cdc9f5c6e3a71d8e0914e2f5b5f8f2b1793cbfe1afd704b7de9"}, - {file = "django_bootstrap_datepicker_plus-3.0.6-py3-none-any.whl", hash = "sha256:c590d840994c7ae4f78ab47471e5dd107d6aad456ca34f2f36dffa41eeb8b819"}, + {file = "django_bootstrap_datepicker_plus-5.0.3-py3-none-any.whl", hash = "sha256:f6e05e19f628d4bcb24524709f22e6dd15950c250aaa2fa2bad8ba828e17228f"}, + {file = "django_bootstrap_datepicker_plus-5.0.3.tar.gz", hash = "sha256:154bc0234096b420815bfbb7f0534fc6dea7fef7aefc24d5c3a3e961fd2097e3"}, ] django-bootstrap5 = [ {file = "django-bootstrap5-21.3.tar.gz", hash = "sha256:35086341881780a44b2e27255894f6029fc5ef75e5a0ec8ebd82f47a5184fa73"}, @@ -1937,10 +1958,6 @@ django-formtools = [ {file = "django-formtools-2.4.tar.gz", hash = "sha256:deb932be55b1d9419e37dc4d65dfbfeb8d307b71c8c11fd52f159aba5fc0deed"}, {file = "django_formtools-2.4-py3-none-any.whl", hash = "sha256:f5f32f62ec8192cd1bc55bd929ca7dff5a5f2addf9027db95a5906ecfaa64836"}, ] -django-ldapdb = [ - {file = "django-ldapdb-1.5.1.tar.gz", hash = "sha256:5ea333c3130abbb86f8629766370a7f490878706469fce4e5008e41fb566392a"}, - {file = "django_ldapdb-1.5.1-py2.py3-none-any.whl", hash = "sha256:2daee828a7eb1ce6ad0634ce5311339d77c08128829e575ff14d62a46771b86a"}, -] django-localflavor = [ {file = "django-localflavor-3.1.tar.gz", hash = "sha256:ac2fa377bbcba4cae95e97077d9e77c7f22b3d93e4845e2e133ba7e664043a44"}, {file = "django_localflavor-3.1-py3-none-any.whl", hash = "sha256:6593865dc671333b3edc88e729e6d384d00b6db7891ef5d3a65db831a40050d2"}, @@ -1958,8 +1975,8 @@ django-silk = [ {file = "django_silk-4.4.1-py3-none-any.whl", hash = "sha256:72f20020177e929ca5733dfebf226b4ce8559c5a7bdb1517daf9aaf7916b188a"}, ] django-tables2 = [ - {file = "django-tables2-2.4.1.tar.gz", hash = "sha256:6c72dd208358539e789e4c0efd7d151e43283a4aa4093a35f44c43489e7ddeaa"}, - {file = "django_tables2-2.4.1-py2.py3-none-any.whl", hash = "sha256:50762bf3d7c61a4eb70e763c3e278650d7266bb78d0497fc8fafcf4e507c9a64"}, + {file = "django-tables2-2.5.1.tar.gz", hash = "sha256:25ed2c28d3508558a4246a255c381784b20b439ef175e3954a103ffbc3a63757"}, + {file = "django_tables2-2.5.1-py2.py3-none-any.whl", hash = "sha256:62d65e745073892741ccdb6137bfdbcae7f83142d07494fe865d389af1f2befd"}, ] django-weasyprint = [ {file = "django-weasyprint-1.1.0.post2.tar.gz", hash = "sha256:467830f5a500d6e60fa8aab159b2e45ea28d052021cfb59755d911c02693a2ef"}, @@ -1982,12 +1999,12 @@ factory-boy = [ {file = "factory_boy-3.2.1.tar.gz", hash = "sha256:a98d277b0c047c75eb6e4ab8508a7f81fb03d2cb21986f627913546ef7a2a55e"}, ] faker = [ - {file = "Faker-15.3.4-py3-none-any.whl", hash = "sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76"}, - {file = "Faker-15.3.4.tar.gz", hash = "sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc"}, + {file = "Faker-16.6.1-py3-none-any.whl", hash = "sha256:2375d0bbaf405dc4f1cbc771485a78ad952c776798e5c228eef3e7b337f78868"}, + {file = "Faker-16.6.1.tar.gz", hash = "sha256:b76e5d2405470e3d38d37d1bfaa9d9bbf171bdf41c814f5bbd8117b121f6bccb"}, ] filelock = [ - {file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"}, - {file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"}, + {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, + {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, ] gprof2dot = [ {file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"}, @@ -2002,20 +2019,20 @@ html5lib = [ {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, ] identify = [ - {file = "identify-2.5.9-py2.py3-none-any.whl", hash = "sha256:a390fb696e164dbddb047a0db26e57972ae52fbd037ae68797e5ae2f4492485d"}, - {file = "identify-2.5.9.tar.gz", hash = "sha256:906036344ca769539610436e40a684e170c3648b552194980bb7b617a8daeb9f"}, + {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, + {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, ] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] ipython = [ - {file = "ipython-8.7.0-py3-none-any.whl", hash = "sha256:352042ddcb019f7c04e48171b4dd78e4c4bb67bf97030d170e154aac42b656d9"}, - {file = "ipython-8.7.0.tar.gz", hash = "sha256:882899fe78d5417a0aa07f995db298fa28b58faeba2112d2e3a4c95fe14bb738"}, + {file = "ipython-8.9.0-py3-none-any.whl", hash = "sha256:9c207b0ef2d276d1bfcfeb9a62804336abbe4b170574ea061500952319b1d78c"}, + {file = "ipython-8.9.0.tar.gz", hash = "sha256:71618e82e6d59487bea059626e7c79fb4a5b760d1510d02fab1160db6fdfa1f7"}, ] jedi = [ {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, @@ -2049,7 +2066,6 @@ lxml = [ {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, - {file = "lxml-4.9.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:6943826a0374fb135bb11843594eda9ae150fba9d1d027d2464c713da7c09afe"}, {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, @@ -2060,6 +2076,7 @@ lxml = [ {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, + {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, @@ -2069,6 +2086,7 @@ lxml = [ {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, + {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, @@ -2112,46 +2130,56 @@ lxml = [ {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, ] markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] matplotlib-inline = [ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, @@ -2170,8 +2198,8 @@ nodeenv = [ {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] packaging = [ - {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, - {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, @@ -2182,95 +2210,111 @@ pastel = [ {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, ] pathspec = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, + {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, + {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, ] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] phonenumbers = [ - {file = "phonenumbers-8.13.2-py2.py3-none-any.whl", hash = "sha256:884b26f775205261f4dc861371dce217c1661a4942fb3ec3624e290fb51869bf"}, - {file = "phonenumbers-8.13.2.tar.gz", hash = "sha256:0179f688d48c0e7e161eb7b9d86d587940af1f5174f97c1fdfd893c599c0d94a"}, + {file = "phonenumbers-8.13.5-py2.py3-none-any.whl", hash = "sha256:2e3fd1f3fde226b289489275517c76edf223eafd9f43a2c2c36498a44b73d4b0"}, + {file = "phonenumbers-8.13.5.tar.gz", hash = "sha256:6eb2faf29c19f946baf10f1c977a1f856cab90819fe7735b8e141d5407420c4a"}, ] pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] pillow = [ - {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, - {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, - {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, - {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, - {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, - {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, - {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, - {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, - {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, - {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, - {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, - {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, - {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, - {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, - {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, - {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, - {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, - {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, + {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, + {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, + {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, + {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, + {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, + {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, + {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, + {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, + {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, + {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, + {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, + {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, + {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, + {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, + {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, + {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, + {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, + {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, + {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, + {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, + {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, + {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, + {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, + {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, + {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, + {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, ] platformdirs = [ - {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, - {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, ] prompt-toolkit = [ {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, @@ -2362,13 +2406,20 @@ py = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] -pyasn1-modules = [ - {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, - {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, -] pycodestyle = [ {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, @@ -2377,9 +2428,51 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +pydantic = [ + {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"}, + {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"}, + {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"}, + {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"}, + {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"}, + {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"}, + {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"}, + {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"}, + {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"}, + {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"}, + {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"}, + {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"}, + {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"}, + {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"}, + {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"}, + {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"}, + {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"}, + {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"}, + {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"}, + {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"}, + {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"}, + {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"}, + {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"}, + {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"}, + {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"}, + {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"}, + {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"}, + {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"}, + {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"}, + {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"}, + {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"}, + {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"}, + {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"}, + {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"}, + {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"}, + {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"}, +] pygments = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] +pyjwt = [ + {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, + {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, ] pylev = [ {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, @@ -2414,19 +2507,16 @@ python-jose = [ {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, ] python-keycloak = [ - {file = "python-keycloak-2.6.0.tar.gz", hash = "sha256:08c530ff86f631faccb8033d9d9345cc3148cb2cf132ff7564f025292e4dbd96"}, - {file = "python_keycloak-2.6.0-py3-none-any.whl", hash = "sha256:a1ce102b978beb56d385319b3ca20992b915c2c12d15a2d0c23f1104882f3fb6"}, -] -python-ldap = [ - {file = "python-ldap-3.4.3.tar.gz", hash = "sha256:ab26c519a0ef2a443a2a10391fa3c5cb52d7871323399db949ebfaa9f25ee2a0"}, + {file = "python_keycloak-2.9.0-py3-none-any.whl", hash = "sha256:f2b42fc27b474ac791900eee38049ba9342613118f599e8214e85cfd58d4329e"}, + {file = "python_keycloak-2.9.0.tar.gz", hash = "sha256:a270939000a4431a1f94ccf833ca3884d015635491cfb557405bc78e80a12b28"}, ] python-stdnum = [ {file = "python-stdnum-1.18.tar.gz", hash = "sha256:bcc763d9c49ae23da5d2b7a686d5fd1deec9d9051341160a10d1ac723a26bec0"}, {file = "python_stdnum-1.18-py2.py3-none-any.whl", hash = "sha256:d7f2a3c7ef4635c957b9cbdd9b1993d1f6ee3a2959f03e172c45440d99f296eb"}, ] pytz = [ - {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, - {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, + {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, + {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, @@ -2471,12 +2561,12 @@ pyyaml = [ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] redis = [ - {file = "redis-4.4.0-py3-none-any.whl", hash = "sha256:cae3ee5d1f57d8caf534cd8764edf3163c77e073bdd74b6f54a87ffafdc5e7d9"}, - {file = "redis-4.4.0.tar.gz", hash = "sha256:7b8c87d19c45d3f1271b124858d2a5c13160c4e74d4835e28273400fa34d5228"}, + {file = "redis-4.4.2-py3-none-any.whl", hash = "sha256:e6206448e2f8a432871d07d432c13ed6c2abcf6b74edb436c99752b1371be387"}, + {file = "redis-4.4.2.tar.gz", hash = "sha256:a010f6cb7378065040a02839c3f75c7e0fb37a87116fb4a95be82a95552776c7"}, ] requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, @@ -2490,10 +2580,6 @@ selenium = [ {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, ] -setuptools = [ - {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, - {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, -] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2515,8 +2601,8 @@ tablib = [ {file = "tablib-3.3.0.tar.gz", hash = "sha256:11e02a6f81d256e0666877d8397972d10302307a54c04fd7157e92faf740cb10"}, ] termcolor = [ - {file = "termcolor-2.1.1-py3-none-any.whl", hash = "sha256:fa852e957f97252205e105dd55bbc23b419a70fec0085708fc0515e399f304fd"}, - {file = "termcolor-2.1.1.tar.gz", hash = "sha256:67cee2009adc6449c650f6bcf3bdeed00c8ba53a8cda5362733c53e0a39fb70b"}, + {file = "termcolor-2.2.0-py3-none-any.whl", hash = "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7"}, + {file = "termcolor-2.2.0.tar.gz", hash = "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"}, ] tinycss2 = [ {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, @@ -2526,21 +2612,21 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] tomlkit = [ {file = "tomlkit-0.10.2-py3-none-any.whl", hash = "sha256:905cf92c2111ef80d355708f47ac24ad1b6fc2adc5107455940088c9bbecaedb"}, {file = "tomlkit-0.10.2.tar.gz", hash = "sha256:30d54c0b914e595f3d10a87888599eab5321a2a69abc773bbefff51599b72db6"}, ] traitlets = [ - {file = "traitlets-5.7.1-py3-none-any.whl", hash = "sha256:57ba2ba951632eeab9388fa45f342a5402060a5cc9f0bb942f760fafb6641581"}, - {file = "traitlets-5.7.1.tar.gz", hash = "sha256:fde8f62c05204ead43c2c1b9389cfc85befa7f54acb5da28529d671175bb4108"}, + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] vine = [ {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, @@ -2555,8 +2641,8 @@ waitress = [ {file = "waitress-2.1.2.tar.gz", hash = "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"}, ] wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, ] weasyprint = [ {file = "WeasyPrint-52.5-py3-none-any.whl", hash = "sha256:3433d657049a65d7d63f545fd71f5efa8aae7f05d24e49e01a757973fd3799f1"}, diff --git a/pyproject.toml b/pyproject.toml index 03cd5e8b..d4c23a1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,12 +5,11 @@ description = "" authors = ["Leon Handreke "] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.11" Django = "~3.2.12" -django-ldapdb = "^1.5.1" django-weasyprint = "^1.1.0.post2" django-extensions = "3.1.5" -django-bootstrap-datepicker-plus = "^3.0.6" +django-bootstrap-datepicker-plus = "^5.0.2" psycopg2-binary = "^2.9.3" django-tables2 = "^2.4.1" django-filter = "^2.4.0" @@ -33,9 +32,10 @@ cleo = "^0.8.1" tomlkit = "^0.10.1" nanoid = "^2.0" lxml= "^4.9.2" -python-keycloak = "^2.6.0" +python-keycloak = "^2.8.0" webtest = "^3.0.0" django-webtest = "^1.9.10" +PyJWT = "2.6.0" [tool.poetry.dev-dependencies] ipython = "^8.1.0" diff --git a/tapir/accounts/backends.py b/tapir/accounts/backends.py deleted file mode 100644 index ff2befc8..00000000 --- a/tapir/accounts/backends.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import BaseBackend -from django.conf import settings -from keycloak import KeycloakOpenID, KeycloakAuthenticationError, KeycloakPostError -from tapir.accounts.models import KeycloakUser - - -User = get_user_model() -class KeycloakAuthorizationCredentialsBackend(BaseBackend): - - def get_user(self, user_id): - - try: - user = User.objects.get(pk=user_id) - except User.DoesNotExist: - return None - - # needs to validate token before returning user - return user - - def authenticate(self, request, username=None, password=None): - config = settings.KEYCLOAK_CONFIG - kk = KeycloakOpenID( - server_url=config["SERVER_URL"], - client_id=config["CLIENT_ID"], - realm_name=config["REALM_NAME"], - client_secret_key=config["CLIENT_SECRET_KEY"], - ) - try: - token = kk.token(username, password) - except KeycloakAuthenticationError: - return None - except KeycloakPostError as e: - return None - - remote_user = kk.introspect(token["access_token"]) - user, _ = User.objects.get_or_create( - username=remote_user['sub'], - defaults={ - 'first_name': remote_user.get('given_name', ''), - 'last_name': remote_user.get('family_name', '') - } - ) - if not remote_user["active"]: - if user.is_active: - user.is_active = False - user.save() - return None - keycloakuser, _ = KeycloakUser.objects.get_or_create( - user=user, - sub=remote_user["sub"], - defaults={}, - ) - return keycloakuser.user - \ No newline at end of file diff --git a/tapir/accounts/fixtures/admin_account.json b/tapir/accounts/fixtures/admin_account.json deleted file mode 100644 index 9f3d5471..00000000 --- a/tapir/accounts/fixtures/admin_account.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "model": "accounts.tapiruser", - "pk": 1000, - "fields": { - "password": "!MaAS6mbBl1AtqVYmB0J0835NTpZLyb23JUGV2dAR", - "last_login": "2021-03-23T12:40:54.480Z", - "is_superuser": true, - "username": "admin", - "first_name": "", - "last_name": "", - "email": "admin@example.com", - "is_staff": true, - "is_active": true, - "date_joined": "2021-03-23T12:40:40.989Z", - "phone_number": "", - "birthdate": null, - "street": "", - "street_2": "", - "postcode": "", - "city": "", - "country": "DE", - "preferred_language": "en", - "groups": [], - "user_permissions": [] - } - } -] \ No newline at end of file diff --git a/tapir/accounts/forms.py b/tapir/accounts/forms.py index a8a0650d..8de7b89b 100644 --- a/tapir/accounts/forms.py +++ b/tapir/accounts/forms.py @@ -14,7 +14,7 @@ class Meta: fields = [ "first_name", "last_name", - "username", + # "username", "phone_number", "email", "birthdate", @@ -26,7 +26,7 @@ class Meta: ] widgets = { "birthdate": DateInput(), - "username": TextInput(attrs={"readonly": True}), + # "username": TextInput(attrs={"readonly": True}), } diff --git a/tapir/accounts/middleware.py b/tapir/accounts/middleware.py index 8f60ad62..39e59d13 100644 --- a/tapir/accounts/middleware.py +++ b/tapir/accounts/middleware.py @@ -1,22 +1,77 @@ -import datetime +import logging from django.conf import settings -from django.utils import timezone from django.utils.deprecation import MiddlewareMixin +from jwt import decode +from keycloak import KeycloakOpenID +from tapir.accounts.models import TapirUser -class ClientPermsMiddleware(MiddlewareMixin): - def process_request(self, request): - if request.user.is_anonymous: - return +logger = logging.getLogger(__name__) - request_comes_from_welcome_desk = ( - request.META.get("HTTP_X_SSL_CLIENT_VERIFY") == "SUCCESS" - and request.META["HTTP_X_SSL_CLIENT_S_DN"] - in settings.CLIENT_PERMISSIONS.keys() + +class KeycloakMiddleware(MiddlewareMixin): + """KeyCloak Middleware for authentication and authorization.""" + + def __init__(self, get_response): + """One-time initialization of middleware.""" + self.get_response = get_response # Required by django + try: + self.setup_keycloak() + except Exception: + raise Exception( + f"{__name__}: Failed to set up keycloak connection." + "Please check settings.KEYCLOAK." + ) + + def setup_keycloak(self): + """Set up KeyCloakOpenID with given settings.""" + self.config = settings.KEYCLOAK_ADMIN_CONFIG + self.keycloak = KeycloakOpenID( + server_url=self.config["PUBLIC_URL"], + realm_name=self.config["REALM_NAME"], + client_id=self.config["FRONTEND_CLIENT_ID"], ) - if request_comes_from_welcome_desk: - request.user.client_perms = settings.CLIENT_PERMISSIONS[ - request.META["HTTP_X_SSL_CLIENT_S_DN"] - ] - return + + def __call__(self, request): + """Handle default requests.""" + return self.get_response(request) # Required by django + + def auth_failed(self, log_message, error): + """Return authentication failed message in log and API.""" + logger.debug(f"{log_message}: {repr(error)}") + + def process_view(self, request, view_func, view_args, view_kwargs): + """Check for authentication and try to get user from keycloak.""" + # Return unauthenticated request if no authorization is found + if "token" not in request.COOKIES: + logger.debug(f"No authorization found. Using public user.") + return None + + # Retrieve token and user or return failure message + access_token = request.COOKIES.get("token") + + # Decode token + try: + data = decode(access_token, options={"verify_signature": False}) + except Exception as e: + return self.auth_failed("Could not decode token", e) + + # Add user to request + keycloak_id = data.get("sub", None) + if keycloak_id is None: + return self.auth_failed("Could not get id from token: ", data) + try: + request.user = user = TapirUser.objects.get(keycloak_id=keycloak_id) + user.email_verified = data.get("email_verified", False) + + roles = data.get("realm_access", {}).get("roles", []) + user.roles = [] + for role in roles: + if role not in settings.KEYCLOAK_NON_TAPIR_ROLES: + user.roles.append(role) + except Exception as e: + return self.auth_failed("Could not find matching TapirUser", e) + + # Return authenticated request if no exception is thrown + return None diff --git a/tapir/accounts/migrations/0001_initial.py b/tapir/accounts/migrations/0001_initial.py index 1ae0dff8..04b2c624 100644 --- a/tapir/accounts/migrations/0001_initial.py +++ b/tapir/accounts/migrations/0001_initial.py @@ -1,32 +1,27 @@ -# Generated by Django 3.2.15 on 2022-09-21 11:04 +# Generated by Django 3.2.16 on 2023-01-30 18:56 +from django.contrib.postgres.operations import HStoreExtension +import django.contrib.auth.validators from django.db import migrations, models import django.utils.timezone -import ldapdb.models.fields +import functools import phonenumber_field.modelfields import tapir.accounts.models -import tapir.accounts.validators +import tapir.core.models import tapir.utils.models class Migration(migrations.Migration): + initial = True dependencies = [] operations = [ + HStoreExtension(), migrations.CreateModel( name="TapirUser", fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), ("password", models.CharField(max_length=128, verbose_name="password")), ( "last_login", @@ -43,21 +38,18 @@ class Migration(migrations.Migration): ), ), ( - "first_name", - models.CharField( - blank=True, max_length=150, verbose_name="first name" - ), - ), - ( - "last_name", + "username", models.CharField( - blank=True, max_length=150, verbose_name="last name" - ), - ), - ( - "email", - models.EmailField( - blank=True, max_length=254, verbose_name="email address" + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", ), ), ( @@ -83,18 +75,31 @@ class Migration(migrations.Migration): ), ), ( - "username", + "id", models.CharField( - error_messages={ - "unique": "A user with that username already exists." - }, - help_text="Required. 150 characters or fewer. Letters, digits and ./-/_ only.", - max_length=150, + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, unique=True, - validators=[tapir.accounts.validators.UsernameValidator()], - verbose_name="Username", + verbose_name="ID", ), ), + ( + "keycloak_id", + models.CharField(max_length=64, null=True, unique=True), + ), + ( + "first_name", + models.CharField(max_length=150, verbose_name="First Name"), + ), + ( + "last_name", + models.CharField(max_length=150, verbose_name="Last Name"), + ), + ("email", models.CharField(max_length=150, verbose_name="Email")), ( "phone_number", phonenumber_field.modelfields.PhoneNumberField( @@ -395,63 +400,5 @@ class Migration(migrations.Migration): options={ "abstract": False, }, - managers=[ - ("objects", tapir.accounts.models.TapirUserManager()), - ], - ), - migrations.CreateModel( - name="LdapGroup", - fields=[ - ( - "dn", - ldapdb.models.fields.CharField( - max_length=200, primary_key=True, serialize=False - ), - ), - ( - "cn", - ldapdb.models.fields.CharField( - db_column="cn", max_length=200, serialize=False - ), - ), - ( - "description", - ldapdb.models.fields.CharField( - db_column="description", max_length=200 - ), - ), - ("members", ldapdb.models.fields.ListField(db_column="member")), - ], - options={ - "verbose_name": "LDAP group", - "verbose_name_plural": "LDAP groups", - }, - ), - migrations.CreateModel( - name="LdapPerson", - fields=[ - ( - "dn", - ldapdb.models.fields.CharField( - max_length=200, primary_key=True, serialize=False - ), - ), - ( - "uid", - ldapdb.models.fields.CharField( - db_column="uid", max_length=200, serialize=False - ), - ), - ("cn", ldapdb.models.fields.CharField(db_column="cn", max_length=200)), - ("sn", ldapdb.models.fields.CharField(db_column="sn", max_length=200)), - ( - "mail", - ldapdb.models.fields.CharField(db_column="mail", max_length=200), - ), - ], - options={ - "verbose_name": "LDAP person", - "verbose_name_plural": "LDAP people", - }, ), ] diff --git a/tapir/accounts/migrations/0002_initial.py b/tapir/accounts/migrations/0002_initial.py index e0114dff..111711bb 100644 --- a/tapir/accounts/migrations/0002_initial.py +++ b/tapir/accounts/migrations/0002_initial.py @@ -1,22 +1,21 @@ -# Generated by Django 3.2.15 on 2022-09-21 11:04 +# Generated by Django 3.2.16 on 2023-01-30 18:56 import django.contrib.postgres.fields.hstore -from django.contrib.postgres.operations import HStoreExtension from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): + initial = True dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), - ("accounts", "0001_initial"), ("log", "0001_initial"), + ("accounts", "0001_initial"), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ - HStoreExtension(), migrations.CreateModel( name="UpdateTapirUserLogEntry", fields=[ diff --git a/tapir/accounts/migrations/0003_auto_20221117_1841.py b/tapir/accounts/migrations/0003_auto_20221117_1841.py deleted file mode 100644 index 02e62755..00000000 --- a/tapir/accounts/migrations/0003_auto_20221117_1841.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-17 17:41 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import ldapdb.models.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0002_initial'), - ] - - operations = [ - migrations.CreateModel( - name='KeycloakUser', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sub', models.CharField(max_length=255, unique=True)), - ('realm', models.CharField(max_length=255)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='keycloak_user', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.AddConstraint( - model_name='keycloakuser', - constraint=models.UniqueConstraint(fields=('sub', 'realm'), name='unique_sub_realm'), - ), - ] diff --git a/tapir/wirgarten/migrations/0036_waitinglistentry.py b/tapir/accounts/migrations/0003_emailchangerequest.py similarity index 60% rename from tapir/wirgarten/migrations/0036_waitinglistentry.py rename to tapir/accounts/migrations/0003_emailchangerequest.py index 1c9b6235..f6b7e6a8 100644 --- a/tapir/wirgarten/migrations/0036_waitinglistentry.py +++ b/tapir/accounts/migrations/0003_emailchangerequest.py @@ -1,20 +1,22 @@ -# Generated by Django 3.2.16 on 2022-12-02 11:36 +# Generated by Django 3.2.17 on 2023-02-03 13:44 +from django.conf import settings from django.db import migrations, models import django.db.models.deletion import functools +import tapir.accounts.models import tapir.core.models class Migration(migrations.Migration): dependencies = [ - ("wirgarten", "0035_receivedcoopshareslogentry"), + ("accounts", "0002_initial"), ] operations = [ migrations.CreateModel( - name="WaitingListEntry", + name="EmailChangeRequest", fields=[ ( "id", @@ -29,27 +31,26 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("first_name", models.CharField(max_length=256)), - ("last_name", models.CharField(max_length=256)), - ("email", models.CharField(max_length=256)), ( - "type", + "new_email", + models.CharField(max_length=150, verbose_name="New Email"), + ), + ( + "secret", models.CharField( - choices=[ - ("HARVEST_SHARES", "Ernteanteile"), - ("COOP_SHARES", "Genossenschaftsanteile"), - ], - max_length=32, + default=functools.partial( + tapir.accounts.models.generate_random_secret, *(), **{} + ), + max_length=36, + verbose_name="Secret", ), ), ("created_at", models.DateTimeField(auto_now_add=True)), - ("privacy_consent", models.DateTimeField()), ( - "member", + "user", models.ForeignKey( - null=True, on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.member", + to=settings.AUTH_USER_MODEL, ), ), ], diff --git a/tapir/accounts/models.py b/tapir/accounts/models.py index 3b9f2a42..308a360c 100644 --- a/tapir/accounts/models.py +++ b/tapir/accounts/models.py @@ -1,219 +1,185 @@ +import base64 +import json import logging +from functools import partial -import ldap -import ldapdb.models -import ldapdb.models.fields as ldapdb_fields -import pyasn1.codec.ber.encoder -import pyasn1.type.namedtype -import pyasn1.type.univ from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager -from django.contrib.auth.models import Permission as PermissionModel from django.contrib.auth.tokens import default_token_generator -from django.contrib.auth import get_user_model from django.core.mail import EmailMultiAlternatives -from django.db import connections, router, models, transaction +from django.db import models, transaction from django.template import loader from django.urls import reverse +from django.urls import reverse_lazy from django.utils import translation from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.utils.translation import gettext_lazy as _ +from keycloak import ( + KeycloakAdmin, +) +from nanoid import generate from phonenumber_field.modelfields import PhoneNumberField -from keycloak import KeycloakAdmin, KeycloakOpenID, KeycloakAuthenticationError, KeycloakPostError from tapir import utils -from tapir.accounts import validators +from tapir.configuration.parameter import get_parameter_value +from tapir.core.models import generate_id, ID_LENGTH, TapirModel from tapir.log.models import UpdateModelLogEntry -from tapir.settings import PERMISSIONS from tapir.utils.models import CountryField from tapir.utils.user_utils import UserUtils +from tapir.wirgarten.parameters import Parameter log = logging.getLogger(__name__) -class LdapUser(AbstractUser): - class Meta: - abstract = True - - def get_ldap(self): - return LdapPerson.objects.get(uid=self.get_username()) +class KeycloakUserQuerySet(models.QuerySet): + def delete(self, *args, **kwargs): + for obj in self: + obj.delete() - def has_ldap(self): - result = LdapPerson.objects.filter(uid=self.get_username()) - return len(result) == 1 + super().delete(*args, **kwargs) - def create_ldap(self): - username = self.username - LdapPerson.objects.create(uid=username, sn=username, cn=username) - def set_ldap_password(self, raw_password): - ldap_person = self.get_ldap() - ldap_person.change_password(raw_password) - - def save( - self, force_insert=False, force_update=False, using=None, update_fields=None - ): +class KeycloakUserManager(models.Manager.from_queryset(KeycloakUserQuerySet)): + pass - if self.has_ldap(): - ldap_user = self.get_ldap() - else: - ldap_user = LdapPerson(uid=self.username) - ldap_user.sn = self.last_name or self.username - ldap_user.cn = self.get_full_name() or self.username - ldap_user.mail = self.email - ldap_user.save() +class KeycloakUser(AbstractUser): + objects = KeycloakUserManager() - super(LdapUser, self).save(force_insert, force_update, using, update_fields) + _kk: KeycloakAdmin = None + roles: [str] = [] + email_verified = False - # force null Django password (will use LDAP password instead) - self.set_unusable_password() + id = models.CharField( + "ID", + max_length=ID_LENGTH, + unique=True, + primary_key=True, + default=partial(generate_id), + ) + keycloak_id = models.CharField( + max_length=64, unique=True, primary_key=False, null=True + ) - def delete(self): - self.get_ldap().delete() - super(LdapUser, self).delete() + def get_keycloak_client(self): + # if not self._kk: + config = settings.KEYCLOAK_ADMIN_CONFIG - def set_password(self, raw_password): - # force null Django password (will use LDAP password) - self.set_unusable_password() - if self.has_ldap(): - self.set_ldap_password(raw_password) + # self._kk = KeycloakAdmin( + return KeycloakAdmin( + server_url=config["SERVER_URL"] + "/auth", + client_id=config["CLIENT_ID"], + realm_name=config["REALM_NAME"], + user_realm_name=config["USER_REALM_NAME"], + client_secret_key=config["CLIENT_SECRET_KEY"], + verify=True, + ) - def check_password(self, raw_password): - return self.get_ldap().check_password(raw_password) + # return self._kk def has_perm(self, perm, obj=None): - user_dn = self.get_ldap().build_dn() - # TODO(Leon Handreke): This is a case of very aggressive programming, we require both the perm to - # be defined in settings and the group to exist. Probably a fair expectation, but explode more - # gracefully. - # We use a custom permission system based on statically-defined permissions in settings for - # these reasons: - # 1. Easier to keep an overview of what group is allowed to do what - # 2. Permissions must not be tied to models and can therefore be more broad and simple - # - # TODO(Leon Handreke): Taking the group from LDAP is probably not the smartest move because - # I'm about the only person comfortable to use Apache Directory Studio. Move this into - # out app and build a nice group management interface? - for group_cn in settings.PERMISSIONS.get(perm, []): - if LdapGroup.objects.filter(cn=group_cn).count() == 0: - continue - group = LdapGroup.objects.get(cn=group_cn) - if user_dn in group.members: - return True - return super().has_perm(perm=perm, obj=obj) - + return perm in self.roles + @transaction.atomic - def add_perm(self, perm): - user_dn = self.get_ldap().build_dn() - permissions = settings.PERMISSIONS.get(perm, []) - if not permissions: - if not isinstance(perm, PermissionModel): - perm = PermissionModel.objects.get(codename=perm) - self.user_permissions.add(perm) - - else: - for group_cn in permissions: - group, _ = LdapGroup.objects.get_or_create( - cn=group_cn, - defaults={"dn": f"cn={group_cn},{settings.REG_GROUP_BASE_DN}", "members": [user_dn]} + def save(self, *args, **kwargs): + if self.keycloak_id is None: # Keycloak User does not exist yet --> create + kk = self.get_keycloak_client() + + data = { + "username": self.email, + "email": self.email, + "firstName": self.first_name, + "lastName": self.last_name, + "enabled": True, + "requiredActions": ["VERIFY_EMAIL", "UPDATE_PASSWORD"], + } + print("Creating Keycloak user: ", data) + user_id = kk.create_user(data) + + try: + kk.send_verify_email(user_id=user_id) + except Exception as e: + # FIXME: schedule to try again later? + print( + f"Failed to send verify email to new user: ", + e, + " (email: '{self.email}', id: '{self.id}', keycloak_id: '{user_id}'): ", ) - if user_dn not in group.members: - group.members.append(user_dn) - group.save() - - - -class TapirUserQuerySet(models.QuerySet): - def with_shift_attendance_mode(self, attendance_mode: str): - return self.filter(shift_user_data__attendance_mode=attendance_mode) - - def registered_to_shift_slot_name(self, slot_name: str): - return self.filter( - shift_attendance_templates__slot_template__name=slot_name - ).distinct() - - def registered_to_shift_slot_with_capability(self, capability: str): - return self.filter( - shift_attendance_templates__slot_template__required_capabilities__contains=[ - capability - ] - ).distinct() - - def has_capability(self, capability: str): - return self.filter( - shift_user_data__capabilities__contains=[capability] - ).distinct() - -class TapirUserManager(UserManager.from_queryset(TapirUserQuerySet)): - use_in_migrations = True - - - -class KeycloakUserApiMixin: - - def check_password(self, raw_password): - config = settings.KEYCLOAK_CONFIG - kk = KeycloakOpenID( - server_url=config["SERVER_URL"], - client_id=config["CLIENT_ID"], - realm_name=config["REALM_NAME"], - client_secret_key=config["CLIENT_SECRET_KEY"], - ) - try: - token = kk.token(self.username, raw_password) - except (KeycloakAuthenticationError, KeycloakPostError): - return False - else: - kk.logout(token['refresh_token']) - return True - - def set_password(self, raw_password): - config = settings.KEYCLOAK_ADMIN_CONFIG - kk = KeycloakAdmin( - server_url=config["SERVER_URL"], - client_id=config["CLIENT_ID"], - realm_name=config["REALM_NAME"], - client_secret_key=config["CLIENT_SECRET_KEY"], + self.keycloak_id = user_id + else: # Update --> change of keycloak data if necessary + original = type(self).objects.get(id=self.id) + email_changed = original.email != self.email + first_name_changed = original.first_name != self.first_name + last_name_changed = original.last_name != self.last_name + + if first_name_changed or last_name_changed: + kk = self.get_keycloak_client() + data = {"firstName": self.first_name, "lastName": self.last_name} + kk.update_user(user_id=self.keycloak_id, payload=data) + + if email_changed: + self.start_email_change_process(self.email) + # important: reset the email to the original email before persisting. The actual change happens after the user click the confirmation link + self.email = original.email + + super().save(*args, **kwargs) + + def delete(self, *args, **kwargs): + kk = self.get_keycloak_client() + if self.keycloak_id: + kk.delete_user(self.keycloak_id) + super().delete(*args, **kwargs) + + def change_email(self, new_email: str): + kk = self.get_keycloak_client() + kk.update_user( + user_id=self.keycloak_id, + payload={ + "email": new_email, + }, ) - user_id = kk.get_user_id(self.username) - if user_id is None: - raise ValueError("User does not exists") - kk.set_user_password(user_id=user_id, password=raw_password, temporary=False) - def has_perm(self, perm, obj=None): - raise Exception("Implement using Keycloak's API") - @transaction.atomic - def add_perm(self, perm): - raise Exception("Implement using Keycloak's API") - - def delete(self): - raise Exception("Implement using Keycloak's API") - - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - raise Exception("Implement using Keycloak's API") - + def start_email_change_process(self, new_email: str): + EmailChangeRequest.objects.filter(user_id=self.id).delete() + email_change_request = EmailChangeRequest.objects.create( + new_email=new_email, user_id=self.id + ) + email_change_token = base64.urlsafe_b64encode( + json.dumps( + { + "new_email": email_change_request.new_email, + "secret": email_change_request.secret, + "user": email_change_request.user_id, + } + ).encode() + ).decode() + email = EmailMultiAlternatives( + subject=_("Änderung deiner Email-Adresse"), + body=_( + f"Hallo {self.first_name},

" + f"du hast gerade die Email Adresse für deinen WirGarten Account geändert.

" + f"Bitte klicke den folgenden Link um die Änderung zu bestätigen:
" + f"""Email Adresse bestätigen

""" + f"Falls du das nicht warst, kannst du diese Mail einfach löschen oder ignorieren." + f"

Grüße, dein WirGarten Team" + ), + to=[new_email], + from_email=get_parameter_value(Parameter.SITE_ADMIN_EMAIL), + ) + email.content_subtype = "html" + email.send() + class Meta: + abstract = True -class TapirUser(LdapUser): - username_validator = validators.UsernameValidator() - # Copy-pasted from django/contrib/auth/models.py to override validators - username = models.CharField( - _("Username"), - max_length=150, - unique=True, - help_text=_( - "Required. 150 characters or fewer. Letters, digits and ./-/_ only." - ), - validators=[username_validator], - error_messages={ - "unique": _("A user with that username already exists."), - }, - ) +class TapirUser(KeycloakUser): + first_name = models.CharField(_("First Name"), max_length=150, blank=False) + last_name = models.CharField(_("Last Name"), max_length=150, blank=False) + email = models.CharField(_("Email"), max_length=150, blank=False) phone_number = PhoneNumberField(_("Phone number"), blank=True) birthdate = models.DateField(_("Birthdate"), blank=True, null=True) street = models.CharField(_("Street and house number"), max_length=150, blank=True) @@ -229,7 +195,17 @@ class TapirUser(LdapUser): max_length=16, ) - objects = TapirUserManager() + # objects = TapirUserManager() + + @transaction.atomic + def save(self, *args, **kwargs): + self.username = self.email + super().save(*args, **kwargs) # call the parent save method + + @transaction.atomic + def change_email(self, new_email: str): + TapirUser.objects.filter(id=self.id).update(email=new_email, username=new_email) + super().change_email(new_email) def get_display_name(self): return UserUtils.build_display_name(self.first_name, self.last_name) @@ -240,7 +216,7 @@ def get_display_address(self): ) def get_absolute_url(self): - return reverse("accounts:user_detail", args=[self.pk]) + return reverse("wirgarten:member_detail", args=[self.pk]) def get_email_from_template( self, subject_template_names: list, email_template_names: list @@ -261,189 +237,32 @@ def get_email_from_template( email.content_subtype = "html" return email - def has_perm(self, perm, obj=None): - # This is a hack to allow permissions based on client certificates. ClientPermsMiddleware checks the - # certificate in the request and adds the extra permissions the user object, which is accessible here. - if hasattr(self, "client_perms") and perm in self.client_perms: - return True - return super().has_perm(perm=perm, obj=obj) - - def get_permissions_display(self): - user_perms = [perm for perm in PERMISSIONS if self.has_perm(perm)] - if len(user_perms) == 0: - return _("None") - return ", ".join(user_perms) - - def save(self, *args, **kwargs): - self.username_validator(self.username) - return super().save(*args, **kwargs) - - - -class UpdateTapirUserLogEntry(UpdateModelLogEntry): - template_name = "accounts/log/update_tapir_user_log_entry.html" - excluded_fields = ["password"] - - -# The following LDAP-related models were taken from -# https://source.puri.sm/liberty/host/middleware/-/blob/master/ldapregister/models.py -# https://github.com/django-ldapdb/django-ldapdb/blob/master/ldapdb/backends/ldap/base.py -class LdapPerson(ldapdb.models.Model): - """ - Class for representing an LDAP person entry. - """ - - class Meta: - verbose_name = "LDAP person" - verbose_name_plural = "LDAP people" - - # LDAP meta-data - base_dn = settings.REG_PERSON_BASE_DN - object_classes = settings.REG_PERSON_OBJECT_CLASSES - - # Minimal attributes - uid = ldapdb_fields.CharField(db_column="uid", max_length=200, primary_key=True) - cn = ldapdb_fields.CharField(db_column="cn", max_length=200) - sn = ldapdb_fields.CharField(db_column="sn", max_length=200) - mail = ldapdb_fields.CharField(db_column="mail", max_length=200) - - def __str__(self): - return self.uid - - def __unicode__(self): - return self.uid - - def change_password(self, raw_password, using=None): - # dig into the ldapdb primitives - using = using or router.db_for_write(self.__class__, instance=self) - connection = connections[using] - cursor = connection._cursor() - - # call pyldap_orm password modification - cursor.connection.extop_s(PasswordModify(self.dn, raw_password)) - - def check_password(self, raw_password, using=None): - using = using or router.db_for_write(self.__class__, instance=self) - conn_params = connections[using].get_connection_params() - - # This is copy-pasta from django-ldapdb/ldapdb/backends/ldap/base.py - connection = ldap.ldapobject.ReconnectLDAPObject( - uri=conn_params["uri"], - retry_max=conn_params["retry_max"], - retry_delay=conn_params["retry_delay"], - bytes_mode=False, - ) - options = conn_params["options"] - for opt, value in options.items(): - if opt == "query_timeout": - connection.timeout = int(value) - elif opt == "page_size": - self.page_size = int(value) - else: - connection.set_option(opt, value) - if conn_params["tls"]: - connection.start_tls_s() - - # After setting up the connection, we try to authenticate - try: - connection.simple_bind_s(self.dn, raw_password) - except ldap.INVALID_CREDENTIALS: - return False + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + def has_perms(self, perms): + for perm in perms: + if not self.has_perm(perm, self): + return False return True -# The following code taken from https://github.com/asyd/pyldap_orm/blob/master/pyldap_orm/controls.py -# Copyright 2016 Bruno Bonfils -# SPDX-License-Identifier: Apache-2.0 (no NOTICE file) - - -class PasswordModify(ldap.extop.ExtendedRequest): - """ - Implements RFC 3062, LDAP Password Modify Extended Operation - Reference: https://www.ietf.org/rfc/rfc3062.txt - """ - - def __init__(self, identity, new, current=None): - self.requestName = "1.3.6.1.4.1.4203.1.11.1" - self.identity = identity - self.new = new - self.current = current - - def encodedRequestValue(self): - request = self.PasswdModifyRequestValue() - request.setComponentByName("userIdentity", self.identity) - if self.current is not None: - request.setComponentByName("oldPasswd", self.current) - request.setComponentByName("newPasswd", self.new) - return pyasn1.codec.ber.encoder.encode(request) - - class PasswdModifyRequestValue(pyasn1.type.univ.Sequence): - """ - PyASN1 representation of: - PasswdModifyRequestValue ::= SEQUENCE { - userIdentity [0] OCTET STRING OPTIONAL - oldPasswd [1] OCTET STRING OPTIONAL - newPasswd [2] OCTET STRING OPTIONAL } - """ - - componentType = pyasn1.type.namedtype.NamedTypes( - pyasn1.type.namedtype.OptionalNamedType( - "userIdentity", - pyasn1.type.univ.OctetString().subtype( - implicitTag=pyasn1.type.tag.Tag( - pyasn1.type.tag.tagClassContext, - pyasn1.type.tag.tagFormatSimple, - 0, - ) - ), - ), - pyasn1.type.namedtype.OptionalNamedType( - "oldPasswd", - pyasn1.type.univ.OctetString().subtype( - implicitTag=pyasn1.type.tag.Tag( - pyasn1.type.tag.tagClassContext, - pyasn1.type.tag.tagFormatSimple, - 1, - ) - ), - ), - pyasn1.type.namedtype.OptionalNamedType( - "newPasswd", - pyasn1.type.univ.OctetString().subtype( - implicitTag=pyasn1.type.tag.Tag( - pyasn1.type.tag.tagClassContext, - pyasn1.type.tag.tagFormatSimple, - 2, - ) - ), - ), - ) - - -class LdapGroup(ldapdb.models.Model): - """ - Class for representing an LDAP group entry. - """ - - class Meta: - verbose_name = "LDAP group" - verbose_name_plural = "LDAP groups" +def generate_random_secret(): + return generate(size=36) - # LDAP meta-data - base_dn = settings.REG_GROUP_BASE_DN - object_classes = settings.REG_GROUP_OBJECT_CLASSES - # LDAP group attributes - cn = ldapdb_fields.CharField(db_column="cn", max_length=200, primary_key=True) - description = ldapdb_fields.CharField(db_column="description", max_length=200) - members = ldapdb_fields.ListField(db_column="member") +class EmailChangeRequest(TapirModel): + user = models.ForeignKey(TapirUser, on_delete=models.DO_NOTHING, null=False) + new_email = models.CharField(_("New Email"), max_length=150, blank=False) + secret = models.CharField( + _("Secret"), max_length=36, default=partial(generate_random_secret) + ) + created_at = models.DateTimeField(auto_now_add=True, null=False) - def __str__(self): - return self.cn - def __unicode__(self): - return self.cn +class UpdateTapirUserLogEntry(UpdateModelLogEntry): + template_name = "accounts/log/update_tapir_user_log_entry.html" + excluded_fields = ["password"] def language_middleware(get_response): @@ -456,18 +275,3 @@ def middleware(request): return response return middleware - - -class KeycloakUser(models.Model): - user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='keycloak_user') - - sub = models.CharField(max_length=255, unique=True) - realm = models.CharField(max_length=255) - - - class Meta: - constraints = [ - models.UniqueConstraint(fields=['sub', 'realm'], name='unique_sub_realm') - ] - - \ No newline at end of file diff --git a/tapir/accounts/static/accounts/silent-refresh.html b/tapir/accounts/static/accounts/silent-refresh.html new file mode 100644 index 00000000..4656ce95 --- /dev/null +++ b/tapir/accounts/static/accounts/silent-refresh.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/tapir/accounts/templates/accounts/keycloak.html b/tapir/accounts/templates/accounts/keycloak.html new file mode 100644 index 00000000..e05ea9df --- /dev/null +++ b/tapir/accounts/templates/accounts/keycloak.html @@ -0,0 +1,14 @@ +{% load tapir_static %} + + + + {% include 'accounts/keycloak_script.html' %} + + + + +
+ {% include 'wirgarten/generic/loading-spinner.html' %} +
+ + \ No newline at end of file diff --git a/tapir/accounts/templates/accounts/keycloak_script.html b/tapir/accounts/templates/accounts/keycloak_script.html new file mode 100644 index 00000000..3655144a --- /dev/null +++ b/tapir/accounts/templates/accounts/keycloak_script.html @@ -0,0 +1,103 @@ + + \ No newline at end of file diff --git a/tapir/accounts/templates/accounts/link_expired.html b/tapir/accounts/templates/accounts/link_expired.html new file mode 100644 index 00000000..05277820 --- /dev/null +++ b/tapir/accounts/templates/accounts/link_expired.html @@ -0,0 +1,21 @@ +{% load tapir_static %} + + + + + + + + +
+

Ups, dieser Link scheint nicht gültig zu sein.

+
+ {% include 'wirgarten/generic/loading-spinner.html' %} +
+

Du wirst gleich weitergeleitet...

+
+ + \ No newline at end of file diff --git a/tapir/accounts/templates/accounts/password_update.html b/tapir/accounts/templates/accounts/password_update.html new file mode 100644 index 00000000..b2d4d693 --- /dev/null +++ b/tapir/accounts/templates/accounts/password_update.html @@ -0,0 +1,13 @@ + + +{% include 'accounts/keycloak.html' %} + + +redirecting... + + diff --git a/tapir/accounts/templates/registration/password_update.html b/tapir/accounts/templates/registration/password_update.html deleted file mode 100644 index 4b51f77a..00000000 --- a/tapir/accounts/templates/registration/password_update.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "accounts/base.html" %} - -{% load django_bootstrap5 %} -{% load i18n %} - -{% block content %} -
-
-
{% translate "Set a new password" %}
-
-
- {% csrf_token %} - {% bootstrap_form form %} -
- -
-
-
-
-
-{% endblock %} - diff --git a/tapir/accounts/tests/factories/factories.py b/tapir/accounts/tests/factories/factories.py index cfa424fa..d7e78c81 100644 --- a/tapir/accounts/tests/factories/factories.py +++ b/tapir/accounts/tests/factories/factories.py @@ -1,7 +1,6 @@ import factory -from tapir import settings -from tapir.accounts.models import TapirUser, LdapGroup +from tapir.accounts.models import TapirUser from tapir.accounts.tests.factories.user_data_factory import UserDataFactory @@ -10,27 +9,3 @@ class Meta: model = TapirUser username = factory.LazyAttribute(lambda o: f"{o.first_name}.{o.last_name}") - - @factory.post_generation - def password(self, create, password, **kwargs): - if not create: - return - self.set_password(password or self.username) - - @factory.post_generation - def is_in_member_office(self, create, is_in_member_office, **kwargs): - if not create: - return - - group_cn = settings.GROUP_MEMBER_OFFICE - group = LdapGroup.objects.get(cn=group_cn) - user_dn = self.get_ldap().build_dn() - if is_in_member_office: - group.members.append(user_dn) - group.save() - elif user_dn in group.members: - # The current test setup uses the same LDAP server for all the tests, without resetting it in between tests, - # so we have to make sure that this user has not been added to the member office by a previous test - # or a previous run - group.members.remove(user_dn) - group.save() diff --git a/tapir/accounts/tests/test_integration.py b/tapir/accounts/tests/test_integration.py index 470b2d7c..7656db00 100644 --- a/tapir/accounts/tests/test_integration.py +++ b/tapir/accounts/tests/test_integration.py @@ -1,20 +1,22 @@ -from django.test import tag - -from tapir.utils.tests_utils import TapirSeleniumTestBase - - -class AccountsIntegrationTests(TapirSeleniumTestBase): - @tag("selenium") - def test_login_as_admin(self): - self.selenium.get(self.live_server_url) - self.logout_if_necessary() - self.login_as_admin() - self.assertIsNotNone(self.selenium.find_element_by_id("logout")) - - @tag("selenium") - def test_redirect_to_login_page(self): - self.selenium.get(self.live_server_url) - self.logout_if_necessary() - self.selenium.get(f"{self.live_server_url}/config/parameters") - url = str(self.selenium.current_url) - self.assertTrue("/accounts/login/" in url) +# FIXME +# from django.test import tag +# +# from tapir.utils.tests_utils import TapirSeleniumTestBase +# +# +# class AccountsIntegrationTests(TapirSeleniumTestBase): +# @tag("selenium") +# def test_login_as_admin(self): +# self.selenium.get(self.live_server_url) +# self.logout_if_necessary() +# self.login_as_admin() +# self.assertIsNotNone(self.selenium.find_element_by_id("logout")) +# +# +# @tag("selenium") +# def test_redirect_to_login_page(self): +# self.selenium.get(self.live_server_url) +# self.logout_if_necessary() +# self.selenium.get(f"{self.live_server_url}/config/parameters") +# url = str(self.selenium.current_url) +# self.assertTrue("/accounts/login/" in url) diff --git a/tapir/accounts/tests/test_keycloak.py b/tapir/accounts/tests/test_keycloak.py deleted file mode 100644 index 4f288078..00000000 --- a/tapir/accounts/tests/test_keycloak.py +++ /dev/null @@ -1,33 +0,0 @@ -from django.contrib.auth import authenticate, login -from tapir.utils.tests_utils import KeycloakServiceTestCase -from tapir.accounts.tests.factories.factories import TapirUserFactory -from django.test import RequestFactory - - -class KeycloakServerBackendAuthenticationTests(KeycloakServiceTestCase): - """ - This testa are run against the keycloak server within this project - (Docker server keycloak-server) - """ - def test_authenticates_user_against_keycloak(self): - request = RequestFactory().get('/') - auth_user = authenticate(request, username='demo@demo.com', password='demo') - self.assertIsNotNone(auth_user) - - def test_disabled_cannot_login(self): - request = RequestFactory().get('/') - auth_user = authenticate(request, username='inactive@inactive.com', password='inactive') - self.assertIsNone(auth_user) - - def test_differentiates_between_different_realms(self): - self.fail("user from another realm cannot login") - - - -class KeycloakServerSignupTests(KeycloakServiceTestCase): - """ - This testa are run against the keycloak server within this project - (Docker server keycloak-server) - """ - def test_users_can_signup_using_username_and_password(self): - self.fail('caputre signup form nd post it') \ No newline at end of file diff --git a/tapir/accounts/tests/test_models.py b/tapir/accounts/tests/test_models.py deleted file mode 100644 index 7d650337..00000000 --- a/tapir/accounts/tests/test_models.py +++ /dev/null @@ -1,24 +0,0 @@ -from unittest import mock -from django.core.exceptions import ValidationError -from django.contrib.auth import get_user_model -from tapir.utils.tests_utils import LadapCleanupTestMixin, KeycloakTestCase - -User = get_user_model() - -class TapirUserTests(LadapCleanupTestMixin, KeycloakTestCase): - - def test_model_validates_usernames(self): - usernames = [ - "asd$", - ] - for username in usernames: - try: - User.objects.create_user(username=username) - except ValidationError: - continue - self.fail(f"validation error not raised for username: {username}") - - def test_user_can_set_its_password(self): - user = self.create_user() - user.set_password("anypassword") - self.assertTrue(user.check_password("anypassword")) diff --git a/tapir/accounts/tests/test_registration.py b/tapir/accounts/tests/test_registration.py index 8bcff410..690ea653 100644 --- a/tapir/accounts/tests/test_registration.py +++ b/tapir/accounts/tests/test_registration.py @@ -6,60 +6,60 @@ User = get_user_model() -class WizardTests(KeycloakTestCase, WebTest): - def test_simple_signup_flow(self): - self.has_ldap.return_value = False - self.ldap_save.return_value = None - - form = self.app.get(reverse('wirgarten:draftuser_register')).form - form["Harvest Shares-harvest_shares_s"] = 1 - r = form.submit() - - form = r.form - form["Cooperative Shares-statute_consent"] = True - r = form.submit() - - form = r.form - r = form.submit() - - form = r.form - r = form.submit() - - form = r.form - r = form.submit() - - form = r.form - r = form.submit() - - form = r.form - form['Personal Details-first_name'] = 'Firstname' - form['Personal Details-last_name'] = 'Lastname' - form['Personal Details-email'] = 'any@mail.com' - form['Personal Details-phone_number'] = '+12125552368' - form['Personal Details-street'] = 'fake street' - form['Personal Details-street_2'] = '' - form['Personal Details-postcode'] = '12345' - form['Personal Details-city'] = 'Berlin' - form['Personal Details-country'] = 'DE' - form['Personal Details-birthdate'] = '' - r = form.submit() - - form = r.form - form['Payment Details-account_owner'] = 'Firstname Lastname' - form['Payment Details-iban'] = 'DE89370400440532013000' - form['Payment Details-bic'] = 'DEUTDE5M' - r = form.submit() - - form = r.form - form['Payment Details-sepa_consent'] = True - r = form.submit() - - form = r.form - form['Consent-withdrawal_consent'] = True - form['Consent-privacy_consent'] = True - r = form.submit().follow() - - user = User.objects.filter(email='any@mail.com').first() - self.assertIsNotNone(user) - \ No newline at end of file +# FIXME +# class WizardTests(KeycloakTestCase, WebTest): +# def test_simple_signup_flow(self): +# self.has_ldap.return_value = False +# self.ldap_save.return_value = None +# +# form = self.app.get(reverse("wirgarten:draftuser_register")).form +# form["Harvest Shares-harvest_shares_s"] = 1 +# r = form.submit() +# +# form = r.form +# form["Cooperative Shares-statute_consent"] = True +# r = form.submit() +# +# form = r.form +# r = form.submit() +# +# form = r.form +# r = form.submit() +# +# form = r.form +# r = form.submit() +# +# form = r.form +# r = form.submit() +# +# form = r.form +# form["Personal Details-first_name"] = "Firstname" +# form["Personal Details-last_name"] = "Lastname" +# form["Personal Details-email"] = "any@mail.com" +# form["Personal Details-phone_number"] = "+12125552368" +# form["Personal Details-street"] = "fake street" +# form["Personal Details-street_2"] = "" +# form["Personal Details-postcode"] = "12345" +# form["Personal Details-city"] = "Berlin" +# form["Personal Details-country"] = "DE" +# form["Personal Details-birthdate"] = "" +# r = form.submit() +# +# form = r.form +# form["Payment Details-account_owner"] = "Firstname Lastname" +# form["Payment Details-iban"] = "DE89370400440532013000" +# form["Payment Details-bic"] = "DEUTDE5M" +# r = form.submit() +# +# form = r.form +# form["Payment Details-sepa_consent"] = True +# r = form.submit() +# +# form = r.form +# form["Consent-withdrawal_consent"] = True +# form["Consent-privacy_consent"] = True +# r = form.submit().follow() +# +# user = User.objects.filter(email="any@mail.com").first() +# self.assertIsNotNone(user) diff --git a/tapir/accounts/tests/test_standard_user_detail_page.py b/tapir/accounts/tests/test_standard_user_detail_page.py deleted file mode 100644 index 3a557faa..00000000 --- a/tapir/accounts/tests/test_standard_user_detail_page.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.test import Client -from django.urls import reverse - -from tapir.accounts.models import TapirUser -from tapir.accounts.tests.factories.factories import TapirUserFactory -from tapir.utils.tests_utils import TapirFactoryTestBase - - -class AccountsStandardUserDetailPage(TapirFactoryTestBase): - def test_standard_user_detail_page(self): - client = Client() - user: TapirUser = TapirUserFactory.create() - self.assertTrue(client.login(username=user.username, password=user.username)) - response = client.get(reverse("accounts:user_me"), follow=True) - self.assertEqual( - user.id, - response.context["object"].id, - "The logged in user should be the view's context object.", - ) - self.assertInHTML( - f"
{ user.username }
", - response.content.decode(), - ) - - for button in [ - "tapir_user_edit_button", - "share_owner_edit_button", - "add_note_button", - ]: - self.assertNotContains( - response, - button, - 200, - "The user is not in the member office, they should not see the edit buttons", - ) diff --git a/tapir/accounts/tests/test_views.py b/tapir/accounts/tests/test_views.py index 5f7ae8ba..9e0673ce 100644 --- a/tapir/accounts/tests/test_views.py +++ b/tapir/accounts/tests/test_views.py @@ -1,116 +1,108 @@ -from io import StringIO -from unittest import mock -from django_webtest import WebTest -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Permission as PermissionModel -from tapir.accounts.tests.factories.factories import TapirUserFactory -from tapir.wirgarten.factories import MemberFactory, ShareOwnershipFactory, SubscriptionFactory, PaymentFactory -from tapir.utils.tests_utils import LadapCleanupTestMixin, KeycloakTestCase -from tapir.wirgarten.constants import Permission -from django.urls import reverse - -from tapir.configuration.models import TapirParameter - -User = get_user_model() - -class PermissionsTests(LadapCleanupTestMixin, KeycloakTestCase, WebTest): - - def test_send_welcome_mail_form_only_available_with_proper_perm(self): - user = self.create_user() - self.assertFalse(user.has_perm('accounts.manage')) - url = reverse("accounts:user_detail", kwargs={"pk": user.pk}) - r = self.app.get(url, user=user) - selector = '#send-user-welcome-mail-form' - self.assertIsNone(r.html.select_one(selector)) - - user.add_perm('accounts.manage') - self.assertTrue(user.has_perm('accounts.manage')) - r = self.app.get(url, user=user) - self.assertIsNotNone(r.html.select_one(selector)) - - def test_user_edit_only_available_with_proper_perm(self): - member = ShareOwnershipFactory().member - user = self.create_user() - self.assertFalse(user.has_perm('accounts.manage')) - url = reverse("wirgarten:member_detail", kwargs={"pk": member.pk}) - r = self.app.get(url, user=user, status=403) - - selector = '#tapir_user_edit_button' - user.add_perm('accounts.manage') - self.assertTrue(user.has_perm('accounts.manage')) - r = self.app.get(url, user=user) - self.assertIsNotNone(r.html.select_one(selector)) - - def test_edit_payment_form_only_available_with_proper_perm(self): - member = SubscriptionFactory().member - p = PaymentFactory(mandate_ref__member=member) - user = self.create_user() - self.assertFalse(user.has_perm('coop.manage')) - url = reverse("wirgarten:member_payments", kwargs={"pk": member.pk}) - r = self.app.get(url, user=user, status=403) - - selector = '#edit-payment-form' - user.add_perm('coop.manage') - self.assertTrue(user.has_perm('coop.manage')) - r = self.app.get(url, user=user) - self.assertIsNotNone(r.html.select_one(selector)) - - def test_member_edit_button_only_available_with_proper_perm(self): - member = MemberFactory() - user = self.create_user() - self.assertFalse(user.has_perm('accounts.manage')) - url = reverse("wirgarten:member_list") - r = self.app.get(url, user=user, status=403) - - selector = '#edit-member-details-btn' - user.add_perm('accounts.manage') - self.assertTrue(user.has_perm('accounts.manage')) - r = self.app.get(url, user=user) - self.assertIsNotNone(r.html.select_one(selector)) - - def test_trans_coop_shares_button_only_available_with_proper_perm(self): - member = MemberFactory() - user = self.create_user() - self.assertFalse(user.has_perm('coop.manage')) - url = reverse("wirgarten:member_list") - r = self.app.get(url, user=user, status=403) - - selector = '#trans-coop-shares-btn' - user.add_perm('accounts.manage') - self.assertTrue(user.has_perm('coop.manage')) - r = self.app.get(url, user=user) - self.assertIsNotNone(r.html.select_one(selector)) - - def test_product_view_buttons_only_available_with_proper_perm(self): - user = self.create_user() - url = reverse("wirgarten:product") - r = self.app.get(url, user=user, expect_errors=True) - self.assertEqual(r.status_code, 403) - user.add_perm(Permission.Products.VIEW) - self.assertTrue(user.has_perm(Permission.Products.VIEW)) - r = self.app.get(url, user=user) - - checks = [ - ('coop.manage', [ - '#growing-period-add-btn', - '#growing-period-copy-form-btn', - '#del-growing-period-btn', - ]), - (Permission.Products.MANAGE, [ - '#add-capacity-btn', - '#edit-capacity-btn', - '#del-capacity-btn', - '#add-product-btn', - '#edit-product-btn', - '#del-product-btn', - ]) - ] - - for perm, selectors in checks: - user.add_perm(perm) - self.assertTrue(user.has_perm(perm)) - r = self.app.get(url, user=user) - for selector in selectors: - self.assertIsNotNone(r.html.select_one(selector), selectors) - - \ No newline at end of file +# FIXME +# from django_webtest import WebTest +# from django.contrib.auth import get_user_model +# from tapir.wirgarten.factories import ( +# MemberFactory, +# ShareOwnershipFactory, +# SubscriptionFactory, +# PaymentFactory, +# ) +# from tapir.utils.tests_utils import KeycloakTestCase +# from tapir.wirgarten.constants import Permission +# from django.urls import reverse +# +# User = get_user_model() +# +# +# class PermissionsTests( KeycloakTestCase, WebTest): +# +# def test_user_edit_only_available_with_proper_perm(self): +# member = ShareOwnershipFactory().member +# user = self.create_user() +# self.assertFalse(user.has_perm("accounts.manage")) +# url = reverse("wirgarten:member_detail", kwargs={"pk": member.pk}) +# r = self.app.get(url, user=user, status=403) +# +# selector = "#tapir_user_edit_button" +# user.add_perm("accounts.manage") +# self.assertTrue(user.has_perm("accounts.manage")) +# r = self.app.get(url, user=user) +# self.assertIsNotNone(r.html.select_one(selector)) +# +# def test_edit_payment_form_only_available_with_proper_perm(self): +# member = SubscriptionFactory().member +# p = PaymentFactory(mandate_ref__member=member) +# user = self.create_user() +# self.assertFalse(user.has_perm("coop.manage")) +# url = reverse("wirgarten:member_payments", kwargs={"pk": member.pk}) +# r = self.app.get(url, user=user, status=403) +# +# selector = "#edit-payment-form" +# user.add_perm("coop.manage") +# self.assertTrue(user.has_perm("coop.manage")) +# r = self.app.get(url, user=user) +# self.assertIsNotNone(r.html.select_one(selector)) +# +# def test_member_edit_button_only_available_with_proper_perm(self): +# member = MemberFactory() +# user = self.create_user() +# self.assertFalse(user.has_perm("accounts.manage")) +# url = reverse("wirgarten:member_list") +# r = self.app.get(url, user=user, status=403) +# +# selector = "#edit-member-details-btn" +# user.add_perm("accounts.manage") +# self.assertTrue(user.has_perm("accounts.manage")) +# r = self.app.get(url, user=user) +# self.assertIsNotNone(r.html.select_one(selector)) +# +# def test_trans_coop_shares_button_only_available_with_proper_perm(self): +# member = MemberFactory() +# user = self.create_user() +# self.assertFalse(user.has_perm("coop.manage")) +# url = reverse("wirgarten:member_list") +# r = self.app.get(url, user=user, status=403) +# +# selector = "#trans-coop-shares-btn" +# user.add_perm("accounts.manage") +# self.assertTrue(user.has_perm("coop.manage")) +# r = self.app.get(url, user=user) +# self.assertIsNotNone(r.html.select_one(selector)) +# +# def test_product_view_buttons_only_available_with_proper_perm(self): +# user = self.create_user() +# url = reverse("wirgarten:product") +# r = self.app.get(url, user=user, expect_errors=True) +# self.assertEqual(r.status_code, 403) +# user.add_perm(Permission.Products.VIEW) +# self.assertTrue(user.has_perm(Permission.Products.VIEW)) +# r = self.app.get(url, user=user) +# +# checks = [ +# ( +# "coop.manage", +# [ +# "#growing-period-add-btn", +# "#growing-period-copy-form-btn", +# "#del-growing-period-btn", +# ], +# ), +# ( +# Permission.Products.MANAGE, +# [ +# "#add-capacity-btn", +# "#edit-capacity-btn", +# "#del-capacity-btn", +# "#add-product-btn", +# "#edit-product-btn", +# "#del-product-btn", +# ], +# ), +# ] +# +# for perm, selectors in checks: +# user.add_perm(perm) +# self.assertTrue(user.has_perm(perm)) +# r = self.app.get(url, user=user) +# for selector in selectors: +# self.assertIsNotNone(r.html.select_one(selector), selectors) diff --git a/tapir/accounts/urls.py b/tapir/accounts/urls.py index a413f532..02c22860 100644 --- a/tapir/accounts/urls.py +++ b/tapir/accounts/urls.py @@ -1,68 +1,20 @@ -import django.contrib.auth.views as auth_views from django.urls import path, include, reverse_lazy from django.views import generic -from tapir.accounts import views - - -accounts_urlpatterns = [ - path( - "", generic.RedirectView.as_view(pattern_name="accounts:user_me"), name="index" - ), - path("user/me/", views.UserMeView.as_view(), name="user_me"), - path("user//", views.UserDetailView.as_view(), name="user_detail"), - path("user//edit", views.UserUpdateView.as_view(), name="user_update"), - path( - "user//send_welcome_email", - views.send_user_welcome_email, - name="send_user_welcome_email", - ), -] +from tapir.wirgarten.views.member import change_email urlpatterns = [ # Standard login/logout/password views should be un-namespaced because Django refers to them in a few places and # it's easier to do it like this than hunt down all the places and fix the references - path("", include((accounts_urlpatterns, "accounts"))), - path( - "login/", - auth_views.LoginView.as_view(), - name="login", - ), path( - "logout/", - auth_views.logout_then_login, - name="logout", - ), - path( - "password_change/", - auth_views.PasswordChangeView.as_view( - success_url=reverse_lazy("accounts:user_me"), - template_name="registration/password_update.html", - ), + "password_change", + generic.TemplateView.as_view(template_name="accounts/password_update.html"), name="password_change", ), + path("email_change/", change_email, name="change_email_confirm"), path( - "password_reset/", - views.PasswordResetView.as_view( - html_email_template_name="registration/email/password_reset_email.html", - subject_template_name="registration/email/password_reset_subject.html", - ), - name="password_reset", - ), - path( - "password_reset/done/", - auth_views.PasswordResetDoneView.as_view(), - name="password_reset_done", - ), - path( - "reset///", - auth_views.PasswordResetConfirmView.as_view(), - name="password_reset_confirm", - ), - path( - "reset/done/", - auth_views.PasswordResetCompleteView.as_view(), - name="password_reset_complete", + "link_expired", + generic.TemplateView.as_view(template_name="accounts/link_expired.html"), + name="link_expired", ), ] - diff --git a/tapir/configuration/forms.py b/tapir/configuration/forms.py index b4ac6100..51e5fc3a 100644 --- a/tapir/configuration/forms.py +++ b/tapir/configuration/forms.py @@ -1,4 +1,4 @@ -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms from django.forms import Textarea diff --git a/tapir/configuration/views.py b/tapir/configuration/views.py index c8308453..cf3bc145 100644 --- a/tapir/configuration/views.py +++ b/tapir/configuration/views.py @@ -9,7 +9,7 @@ class ParameterView(PermissionRequiredMixin, generic.FormView): template_name = "configuration/parameter_view.html" - permission_required = "coop.admin" + permission_required = "coop.manage" form_class = ParameterForm def get_success_url(self, **kwargs): diff --git a/tapir/core/static/core/css/custom.css b/tapir/core/static/core/css/custom.css index f7d2df20..832c2a6f 100644 --- a/tapir/core/static/core/css/custom.css +++ b/tapir/core/static/core/css/custom.css @@ -230,4 +230,4 @@ tr.tr-href:hover:not(.active), tr.tr-clickable:hover:not(.active){ div.option.selected { background-color: var(--secondary); -} \ No newline at end of file +} diff --git a/tapir/core/templates/core/base.html b/tapir/core/templates/core/base.html index 2633b094..140d3e1c 100644 --- a/tapir/core/templates/core/base.html +++ b/tapir/core/templates/core/base.html @@ -17,7 +17,10 @@ - + + + {% include 'accounts/keycloak_script.html' %} + @@ -29,42 +32,48 @@ - - -{% include 'wirgarten/generic/modal/form-modal.html' %} -{% include 'wirgarten/generic/modal/confirmation-modal.html' %} + {% include 'wirgarten/generic/modal/form-modal.html' %} + {% include 'wirgarten/generic/modal/confirmation-modal.html' %} -
-
- +
+
+ {% if request.user and "coop.view" in request.user.roles %} + + {% endif %} -
- - {% bootstrap_messages %} - {% block content %}{% endblock %} -
+
+ + {% bootstrap_messages %} + {% block content %}{% endblock %} +
+ +
- @@ -79,6 +88,5 @@ elem.classList.add('form-select'); elem.classList.add('is-valid'); } - diff --git a/tapir/core/templatetags/core.py b/tapir/core/templatetags/core.py index 33131375..805d61e3 100644 --- a/tapir/core/templatetags/core.py +++ b/tapir/core/templatetags/core.py @@ -25,7 +25,7 @@ def get_sidebar_link_groups(request): groups = [] if request.user.has_perm(Permission.Coop.VIEW): - add_admin_links(groups) + add_admin_links(groups, request) # misc_group = SidebarLinkGroup(name=_("Miscellaneous")) # groups.append(misc_group) @@ -58,7 +58,7 @@ def get_sidebar_link_groups(request): return groups -def add_admin_links(groups): +def add_admin_links(groups, request): debug_group = SidebarLinkGroup(name=_("Debug")) debug_group.add_link( display_name=_("Exportierte Dateien"), @@ -72,44 +72,50 @@ def add_admin_links(groups): material_icon="dashboard", url=reverse_lazy("wirgarten:admin_dashboard"), ) - admin_group.add_link( - display_name=_("Konfiguration"), - material_icon="settings", - url=reverse_lazy("configuration:parameters"), - ) - admin_group.add_link( - display_name=_("Anbauperiode & Produkte"), - material_icon="agriculture", - url=reverse_lazy("wirgarten:product"), - ) - admin_group.add_link( - display_name=_("Abholorte"), - material_icon="add_location_alt", - url=reverse_lazy("wirgarten:pickup_locations"), - ) - admin_group.add_link( - display_name=_("Lastschrift"), - material_icon="account_balance", - url=reverse_lazy("wirgarten:payment_transactions"), - ) - - members_group = SidebarLinkGroup(name=_("Mitglieder")) - members_group.add_link( - display_name=(_("Mitglieder")), - material_icon="groups", - url=reverse_lazy("wirgarten:member_list"), - ) - members_group.add_link( - display_name=_("Verträge"), - material_icon="history_edu", - url=reverse_lazy("wirgarten:subscription_list"), - ) - members_group.add_link( - display_name=_("Warteliste"), - material_icon="schedule", - url=reverse_lazy("wirgarten:waitinglist"), - ) - - groups.append(members_group) + if request.user.has_perm(Permission.Coop.MANAGE): + admin_group.add_link( + display_name=_("Konfiguration"), + material_icon="settings", + url=reverse_lazy("configuration:parameters"), + ) + if request.user.has_perm(Permission.Products.VIEW): + admin_group.add_link( + display_name=_("Anbauperiode & Produkte"), + material_icon="agriculture", + url=reverse_lazy("wirgarten:product"), + ) + if request.user.has_perm(Permission.Coop.VIEW): + admin_group.add_link( + display_name=_("Abholorte"), + material_icon="add_location_alt", + url=reverse_lazy("wirgarten:pickup_locations"), + ) + if request.user.has_perm(Permission.Payments.VIEW): + admin_group.add_link( + display_name=_("Lastschrift"), + material_icon="account_balance", + url=reverse_lazy("wirgarten:payment_transactions"), + ) + + if request.user.has_perm(Permission.Accounts.VIEW): + members_group = SidebarLinkGroup(name=_("Mitglieder")) + members_group.add_link( + display_name=(_("Mitglieder")), + material_icon="groups", + url=reverse_lazy("wirgarten:member_list"), + ) + members_group.add_link( + display_name=_("Verträge"), + material_icon="history_edu", + url=reverse_lazy("wirgarten:subscription_list"), + ) + members_group.add_link( + display_name=_("Warteliste"), + material_icon="schedule", + url=reverse_lazy("wirgarten:waitinglist"), + ) + + groups.append(members_group) groups.append(admin_group) + groups.append(debug_group) diff --git a/tapir/core/tests/test_project.py b/tapir/core/tests/test_project.py deleted file mode 100644 index 2dc79ccf..00000000 --- a/tapir/core/tests/test_project.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.contrib import admin -from django.contrib.auth import get_user_model -from django.test import Client - -from tapir.utils.tests_utils import LdapEnabledTestCase - - -class TestProject(LdapEnabledTestCase): - def setUp(self): - self.user = get_user_model().objects.create_superuser( - username="admin", email="admin@admin.de", password="admin" - ) - self.client = Client() - self.client.force_login(self.user) - - def test_model_admins(self): - model_admin_urls = [ - f"/admin/{model._meta.app_label}/{model._meta.model_name}/" - for model, model_admin in admin.site._registry.items() - ] - for url in model_admin_urls: - response = self.client.get(url, follow=True) - self.assertEqual(response.status_code, 200) diff --git a/tapir/finance/__init__.py b/tapir/finance/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tapir/finance/migrations/0001_initial.py b/tapir/finance/migrations/0001_initial.py deleted file mode 100644 index 2bc64bb4..00000000 --- a/tapir/finance/migrations/0001_initial.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.1.7 on 2021-04-10 14:51 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Invoice", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("odoo_id", models.IntegerField()), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="invoices", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/tapir/finance/migrations/0002_auto_20210411_0559.py b/tapir/finance/migrations/0002_auto_20210411_0559.py deleted file mode 100644 index c0285af5..00000000 --- a/tapir/finance/migrations/0002_auto_20210411_0559.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.7 on 2021-04-11 05:59 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("finance", "0001_initial"), - ] - - operations = [ - migrations.AlterField( - model_name="invoice", - name="odoo_id", - field=models.IntegerField(unique=True), - ), - ] diff --git a/tapir/finance/migrations/__init__.py b/tapir/finance/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tapir/finance/models.py b/tapir/finance/models.py deleted file mode 100644 index 034860ba..00000000 --- a/tapir/finance/models.py +++ /dev/null @@ -1,90 +0,0 @@ -from decimal import Decimal - -from django.db import models - -from tapir.accounts.models import TapirUser -from tapir.odoo.models import OdooAPI, OdooModel, OdooPartner - -_ODOO_INVOICE_MODEL_NAME = "account.invoice" - - -class InvoiceManager(models.Manager): - def create_with_user(self, user: TapirUser): - odoo_id = self.model._odoo_create({"partner_id": user.odoo_partner.odoo_id}) - return self.create(odoo_id=odoo_id, user=user) - - def create_with_odoo_partner(self, odoo_partner: OdooPartner): - odoo_id = self.model._odoo_create({"partner_id": odoo_partner.odoo_id}) - return self.create(odoo_id=odoo_id) - - -class Invoice(OdooModel): - objects = InvoiceManager() - odoo_model_name = "account.invoice" - - odoo_id = models.IntegerField(blank=False, null=False, unique=True) - - # May be NULL if this Invoice is attached to a DraftUser for the moment - user = models.ForeignKey( - TapirUser, - related_name="invoices", - blank=True, - null=True, - on_delete=models.PROTECT, - ) - - # No enum here as Django template language can't access them - STATE_DRAFT = "draft" - STATE_OPEN = "open" - STATE_PAID = "paid" - - def get_state(self): - return self._odoo_get_field("state") - - def get_total_amount(self) -> Decimal: - return Decimal(self._odoo_get_field("amount_total_signed")) - - def add_invoice_line(self, name, amount: Decimal, account_id, tax_id): - OdooAPI.get_connection().create( - "account.invoice.line", - { - "invoice_id": self.odoo_id, - "name": name, - "price_unit": str(amount), - "quantity": 1, - "account_id": account_id, - # Uses the Many2Many command format documented in the odoo API docs. - # The command (6, 0, x) replaces all existing entries - "invoice_line_tax_ids": [(6, 0, [tax_id])], - }, - ) - - def mark_open(self): - """Transition from draft to open state.""" - return self._odoo_execute("action_invoice_open", {}) - - def register_payment(self, amount: Decimal, journal_id): - assert amount > 0 - - self_fields = self._odoo_get(["partner_id", "reference"]) - partner_id = self_fields["partner_id"][0] - reference = self_fields["reference"] - payment_id = OdooAPI.get_connection().create( - "account.payment", - { - "journal_id": journal_id, - "invoice_ids": [(6, 0, [self.odoo_id])], - "amount": str(amount), - "payment_type": "inbound", - # NOTE(Leon Handreke): I have no idea what this does, all payments seem to have the same if - # registered manually - "payment_method_id": 1, # Manual (inbound) - "partner_id": partner_id, - "communication": "{} via Tapir".format(reference), - "partner_type": "customer", - }, - ) - - OdooAPI.get_connection().execute( - "account.payment", "action_validate_invoice_payment", [payment_id], {} - ) diff --git a/tapir/log/migrations/0001_initial.py b/tapir/log/migrations/0001_initial.py index 2dcc26f8..a55d097c 100644 --- a/tapir/log/migrations/0001_initial.py +++ b/tapir/log/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2022-09-21 11:04 +# Generated by Django 3.2.16 on 2023-01-30 18:56 from django.conf import settings from django.db import migrations, models diff --git a/tapir/log/templatetags/log.py b/tapir/log/templatetags/log.py index 87c43990..ef3a67a1 100644 --- a/tapir/log/templatetags/log.py +++ b/tapir/log/templatetags/log.py @@ -13,24 +13,8 @@ def user_log_entry_list(context, selected_user): context["log_entries"] = log_entries context["create_text_log_entry_action_url"] = "%s?next=%s" % ( - reverse("log:create_user_text_log_entry", args=[selected_user.pk]), + reverse("wirgarten:member_detail", args=[selected_user.pk]), selected_user.get_absolute_url(), ) return context - - -@register.inclusion_tag("log/log_entry_list_tag.html", takes_context=True) -def share_owner_log_entry_list(context, share_owner): - raw_entries = LogEntry.objects.filter(share_owner=share_owner).order_by( - "-created_date" - ) - log_entries = [entry.as_leaf_class() for entry in raw_entries] - context["log_entries"] = log_entries - - context["create_text_log_entry_action_url"] = "%s?next=%s" % ( - reverse("log:create_share_owner_text_log_entry", args=[share_owner.pk]), - share_owner.get_absolute_url(), - ) - - return context diff --git a/tapir/log/urls.py b/tapir/log/urls.py index e5539f56..0c9627bb 100644 --- a/tapir/log/urls.py +++ b/tapir/log/urls.py @@ -5,17 +5,17 @@ app_name = "log" urlpatterns = [ path( - "/email_content", + "/email_content", views.email_log_entry_content, name="email_log_entry_content", ), path( - "text/create/user/", + "text/create/user/", views.create_text_log_entry, name="create_user_text_log_entry", ), path( - "text/create/shareowner/", + "text/create/shareowner/", views.create_text_log_entry, name="create_share_owner_text_log_entry", ), diff --git a/tapir/settings.py b/tapir/settings.py index cf9a7554..49ec212b 100644 --- a/tapir/settings.py +++ b/tapir/settings.py @@ -37,11 +37,12 @@ TAPIR_VERSION = env( "TAPIR_VERSION", cast=str, default=os.environ.get("TAPIR_VERSION", "") ) -print( - f"Tapir Version: {TAPIR_VERSION}" - if TAPIR_VERSION - else "\033[93m>>> WARNING: TAPIR_VERSION is not set, cache busting will not work!\033[0m" -) +if not DEBUG: + print( + f"Tapir Version: {TAPIR_VERSION}" + if TAPIR_VERSION + else "\033[93m>>> WARNING: TAPIR_VERSION is not set, cache busting will not work!\033[0m" + ) ### WIRGARTEN CONFIG ### @@ -87,10 +88,11 @@ "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", - "tapir.accounts.middleware.ClientPermsMiddleware", + # "tapir.accounts.middleware.ClientPermsMiddleware", "tapir.accounts.models.language_middleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "tapir.accounts.middleware.KeycloakMiddleware", ] X_FRAME_OPTIONS = "ALLOWALL" @@ -125,14 +127,8 @@ # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { "default": env.db(default="postgresql://tapir:tapir@db:5432/tapir"), - "ldap": env.db_url( - "LDAP_URL", - default="ldap://cn=admin,dc=lueneburg,dc=wirgarten,dc=com:admin@openldap", - ), } -DATABASE_ROUTERS = ["ldapdb.router.Router"] - CELERY_BROKER_URL = "redis://redis:6379" CELERY_RESULT_BACKEND = "redis://redis:6379" CELERY_BEAT_SCHEDULE = { @@ -237,40 +233,9 @@ WEASYPRINT_BASEURL = "/" -LDAP_BASE_DN = "dc=lueneburg,dc=wirgarten,dc=com" -REG_PERSON_BASE_DN = "ou=people," + LDAP_BASE_DN -REG_PERSON_OBJECT_CLASSES = ["inetOrgPerson", "organizationalPerson", "person"] -REG_GROUP_BASE_DN = "ou=groups," + LDAP_BASE_DN -REG_GROUP_OBJECT_CLASSES = ["groupOfNames"] - -# Groups are stored in the LDAP tree -GROUP_ADMIN = "admin" -GROUP_VORSTAND = "vorstand" -GROUP_MEMBER_OFFICE = "member-office" -# This is our own little stupid permission system. See explanation in accounts/models.py. -PERMISSIONS = { - "coop.view": [GROUP_VORSTAND, GROUP_ADMIN, GROUP_MEMBER_OFFICE], - "coop.manage": [GROUP_VORSTAND, GROUP_ADMIN], - # TODO(Leon Handreke): Reserve this to a list of knowledgeable superusers - "coop.admin": [GROUP_VORSTAND, GROUP_ADMIN], - "accounts.view": [GROUP_VORSTAND, GROUP_ADMIN, GROUP_MEMBER_OFFICE], - "accounts.manage": [GROUP_VORSTAND, GROUP_ADMIN, GROUP_MEMBER_OFFICE], - "payments.view": [GROUP_VORSTAND, GROUP_ADMIN, GROUP_MEMBER_OFFICE], - "payments.manage": [GROUP_VORSTAND, GROUP_ADMIN], - "products.view": [GROUP_VORSTAND, GROUP_ADMIN], - "products.manage": [GROUP_VORSTAND, GROUP_ADMIN], -} - -# Permissions granted to client presenting a given SSL client cert. Currently used for the welcome desk machines. -LDAP_WELCOME_DESK_ID = "CN=welcome-desk.members.supercoop.de,O=SuperCoop Berlin eG,C=DE" -CLIENT_PERMISSIONS = { - LDAP_WELCOME_DESK_ID: [ - "welcomedesk.view", - ] -} - AUTH_USER_MODEL = "accounts.TapirUser" -LOGIN_REDIRECT_URL = "accounts:user_me" +# LOGIN_REDIRECT_URL = "index" +LOGIN_URL = "login" SITE_URL = env("SITE_URL", default="http://127.0.0.1:8000") @@ -283,20 +248,21 @@ SILKY_PYTHON_PROFILER_BINARY = True SILKY_META = True -KEYCLOAK_CONFIG = dict( - SERVER_URL=env("KEYCLOCK_SERVER_URL", default="http://keycloak-server:8080"), - CLIENT_ID=env("KEYCLOCK_CLIENT_ID", default="tapir-client"), - REALM_NAME=env("KEYCLOCK_REALM_NAME", default="master"), - CLIENT_SECRET_KEY=env("KEYCLOCK_CLIENT_SECRET_KEY", default="CPE1r5Uf37ICNU9Wce7w9vDEeZ6cbNSF"), -) KEYCLOAK_ADMIN_CONFIG = dict( - SERVER_URL=env("KEYCLOCK_ADMIN_SERVER_URL", default="http://keycloak-server:8080"), - USERNAME=env("KEYCLOCK_ADMIN_USERNAME", default="admin"), - PASSWORD=env("KEYCLOCK_ADMIN_PASSWORD", default="admin"), + SERVER_URL=env("KEYCLOCK_ADMIN_SERVER_URL", default="http://keycloak:8080"), + PUBLIC_URL=env("KEYCLOCK_PUBLIC_URL", default="http://localhost:8080"), + CLIENT_ID=env("KEYCLOCK_CLIENT_ID", default="tapir-backend"), + FRONTEND_CLIENT_ID=env("KEYCLOCK_FRONTEND_CLIENT_ID", default="tapir-frontend"), REALM_NAME=env("KEYCLOCK_ADMIN_REALM_NAME", default="master"), - USER_REALM_NAME=env("KEYCLOCK_ADMIN_USER_REALM_NAME", default="master"), - CLIENT_SECRET_KEY=env("KEYCLOCK_ADMIN_CLIENT_SECRET_KEY", default="CPE1r5Uf37ICNU9Wce7w9vDEeZ6cbNSF"), + USER_REALM_NAME=env("KEYCLOCK_ADMIN_USER_REALM_NAME", default="tapir"), + CLIENT_SECRET_KEY=env( + "KEYCLOCK_ADMIN_CLIENT_SECRET_KEY", default="MM1XgzThEKoy3RmbQQ0kWqY6wtdRjfjk" + ), ) -AUTHENTICATION_BACKENDS = [ - 'tapir.accounts.backends.KeycloakAuthorizationCredentialsBackend', -] \ No newline at end of file + +# these are keycloak internal roles and will be filtered out automatically when fetching roles +KEYCLOAK_NON_TAPIR_ROLES = [ + "offline_access", + "uma_authorization", + "default-roles-tapir", +] diff --git a/tapir/urls.py b/tapir/urls.py index d0cb41e1..09f8edff 100644 --- a/tapir/urls.py +++ b/tapir/urls.py @@ -18,12 +18,20 @@ from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include +from django.views import generic from tapir.settings import ENABLE_SILK_PROFILING from tapir.wirgarten.views.default_redirect import wirgarten_redirect_view +handler403 = "tapir.wirgarten.views.default_redirect.handle_403" + urlpatterns = [ - path("", wirgarten_redirect_view), + path("", wirgarten_redirect_view, name="index"), + path( + "login", + generic.TemplateView.as_view(template_name="accounts/keycloak.html"), + name="login", + ), path("admin/", admin.site.urls), path("accounts/", include("tapir.accounts.urls")), path("log/", include("tapir.log.urls")), diff --git a/tapir/utils/management/commands/populate_functions.py b/tapir/utils/management/commands/populate_functions.py index c42263c3..6623509d 100644 --- a/tapir/utils/management/commands/populate_functions.py +++ b/tapir/utils/management/commands/populate_functions.py @@ -2,22 +2,23 @@ import os import pathlib import random + +from django.db import transaction + from datetime import date from dateutil.relativedelta import relativedelta -from django.db import transaction -from tapir.accounts.models import TapirUser + +from tapir.accounts.models import TapirUser, EmailChangeRequest from tapir.wirgarten.constants import ProductTypes from tapir.wirgarten.models import ( Member, ShareOwnership, - Subscription, Product, ProductType, GrowingPeriod, MandateReference, Payment, - PickupLocation, ) from tapir.log.models import LogEntry from tapir.utils.json_user import JsonUser @@ -25,7 +26,6 @@ from tapir.wirgarten.models import Subscription from tapir.wirgarten.service.delivery import get_active_pickup_locations from tapir.wirgarten.service.member import ( - create_member, buy_cooperative_shares, get_or_create_mandate_ref, get_next_contract_start_date, @@ -43,10 +43,10 @@ def get_test_users(): return json.loads(json_string)["results"] -USER_COUNT = 200 +USER_COUNT = 5 -@transaction.atomic +# @transaction.atomic def populate_users(): # Users generated with https://randomuser.me print(f"Creating {USER_COUNT} users, this may take a while") @@ -58,32 +58,35 @@ def populate_users(): print(str(index + 1) + f"/{USER_COUNT}") json_user = JsonUser(parsed_user) - is_superuser = False if json_user.get_username() == "roberto.cortes": - is_superuser = True - - pickup_locations = get_active_pickup_locations() - wirgarten_user = Member( - username=json_user.get_username(), - is_staff=False, - is_active=True, - date_joined=json_user.date_joined, - password=json_user.get_username(), - is_superuser=is_superuser, - iban="DE02100500000054540402", - bic="BELADEBE", - account_owner=json_user.get_full_name(), - sepa_consent=json_user.date_joined, - privacy_consent=json_user.date_joined, - withdrawal_consent=json_user.date_joined, - pickup_location=pickup_locations[ - random.randint(0, len(pickup_locations) - 1) - ], - ) - copy_user_info(json_user, wirgarten_user) - wirgarten_user = create_member(wirgarten_user) - min_shares = create_subscriptions(wirgarten_user) - create_shareownership(wirgarten_user, min_shares) + wirgarten_user = TapirUser(is_superuser=True, is_staff=True) + copy_user_info(json_user, wirgarten_user) + wirgarten_user.save() + else: + pickup_locations = get_active_pickup_locations() + wirgarten_user = Member( + # username=json_user.get_username(), + is_staff=False, + is_active=True, + date_joined=json_user.date_joined, + password=json_user.get_username(), + is_superuser=False, + iban="DE02100500000054540402", + bic="BELADEBE", + account_owner=json_user.get_full_name(), + sepa_consent=json_user.date_joined, + privacy_consent=json_user.date_joined, + withdrawal_consent=json_user.date_joined, + pickup_location=pickup_locations[ + random.randint(0, len(pickup_locations) - 1) + ], + ) + copy_user_info(json_user, wirgarten_user) + wirgarten_user.save() + + min_shares = create_subscriptions(wirgarten_user) + create_shareownership(wirgarten_user, min_shares) + print("Created Wirgarten test users") @@ -103,7 +106,7 @@ def create_subscriptions(wirgarten_user): growing_period = GrowingPeriod.objects.get( start_date__lte=today, end_date__gte=today ) - mandate_ref = get_or_create_mandate_ref(wirgarten_user.tapiruser_ptr_id, False) + mandate_ref = get_or_create_mandate_ref(wirgarten_user, False) start_date = get_next_contract_start_date(today) end_date = growing_period.end_date @@ -152,7 +155,7 @@ def create_subscriptions(wirgarten_user): product = Product.objects.get(type=product_type, name=product_name) Subscription.objects.create( - member_id=wirgarten_user.tapiruser_ptr_id, + member=wirgarten_user, product=product, period=growing_period, quantity=quantity_int, @@ -171,7 +174,9 @@ def clear_data(): ShareOwnership.objects.all().delete() Payment.objects.all().delete() MandateReference.objects.all().delete() + EmailChangeRequest.objects.all().delete() Member.objects.all().delete() + TapirUser.objects.all().delete() print("Done") diff --git a/tapir/utils/tests_utils.py b/tapir/utils/tests_utils.py index b15a8efd..ee8144ad 100644 --- a/tapir/utils/tests_utils.py +++ b/tapir/utils/tests_utils.py @@ -5,8 +5,6 @@ import socket from io import StringIO -import factory.random -from unittest import mock from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.db import DEFAULT_DB_ALIAS from django.test import TestCase, override_settings, Client @@ -23,9 +21,7 @@ from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.ui import WebDriverWait -from tapir.accounts.models import TapirUser, LdapGroup from tapir.accounts.templatetags.accounts import format_phone_number -from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.utils.json_user import JsonUser from tapir.wirgarten.constants import Permission from tapir.wirgarten.models import Product @@ -199,42 +195,12 @@ def check_tapir_user_details(self, user: JsonUser): ) -class LdapEnabledTestCase(TestCase): - databases = {"ldap", DEFAULT_DB_ALIAS} - - -class TapirFactoryTestBase(LdapEnabledTestCase): - client: Client - - def setUp(self) -> None: - factory.random.reseed_random(self.__class__.__name__) - self.client = Client() - - def login_as_user(self, user: TapirUser): - success = self.client.login(username=user.username, password=user.username) - self.assertTrue(success, f"User {user.username} should be able to log in.") - - def login_as_member_office_user(self) -> TapirUser: - user = TapirUserFactory.create(is_in_member_office=True) - self.login_as_user(user) - return user - - def login_as_normal_user(self) -> TapirUser: - user = TapirUserFactory.create(is_in_member_office=False) - self.login_as_user(user) - return user - - class KeycloakTestCase(TestCase): - '''once we have enough tests, just remove ldap database''' - databases = {"ldap", DEFAULT_DB_ALIAS} - @classmethod def setUpTestData(cls): super().setUpTestData() cls.now = now = timezone.now() - - call_command("parameter_definitions", stdout=StringIO()) + fixtures = [ "0010_pickup_locations", "0020_product_types", @@ -246,8 +212,14 @@ def setUpTestData(cls): "0060_pickup_location_capabilities", ] for fix in fixtures: - call_command("loaddata", f"tapir/wirgarten/fixtures/{fix}", app="wirgarten", stdout=StringIO()) - + call_command( + "loaddata", + f"tapir/wirgarten/fixtures/{fix}", + app="wirgarten", + stdout=StringIO(), + ) + + call_command("parameter_definitions", stdout=StringIO()) permissions = [ (Permission.Products.VIEW, ContentType.objects.get_for_model(Product)), @@ -257,55 +229,14 @@ def setUpTestData(cls): content_type=ct, codename=codename, ) - GrowingPeriod.objects.create( start_date=now.replace(year=now.year - 1, month=3, day=1), - end_date=now.replace(year=now.year + 1, month=2, day=28) + end_date=now.replace(year=now.year + 1, month=2, day=28), ) - - -class KeycloakServiceTestCase(KeycloakTestCase): - ''' base class to interact with a running Keycloak service''' - pass +class KeycloakServiceTestCase(KeycloakTestCase): + """base class to interact with a running Keycloak service""" -class LadapCleanupTestMixin: - - def setUp(self): - super().setUp() - self.addCleanup(self.cleanup_users) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._users_to_clean = [] - - def for_user(self, user): - user_dn = user.get_ldap().build_dn() - for group in LdapGroup.objects.filter(members=user_dn): - group.members.remove(user_dn) - group.save() - - def cleanup_users(self): - for user in self._users_to_clean: - self.for_user(user) - - def create_user(self, **kwargs): - user = TapirUserFactory(**kwargs) - self._users_to_clean.append(user) - return user - - -class MuteLadapTestCase(TestCase): - - def setUp(self): - super().setUp() - has_ldap_patch = mock.patch("tapir.accounts.models.LdapUser.has_ldap") - self.has_ldap = has_ldap_patch.start() - - ldap_save_patch = mock.patch("tapir.accounts.models.LdapPerson.save") - self.ldap_save = ldap_save_patch.start() - - self.addCleanup(has_ldap_patch.stop) - self.addCleanup(ldap_save_patch.stop) \ No newline at end of file + pass diff --git a/tapir/wirgarten/constants.py b/tapir/wirgarten/constants.py index 9a243952..1aabc9a3 100644 --- a/tapir/wirgarten/constants.py +++ b/tapir/wirgarten/constants.py @@ -1,3 +1,5 @@ +import inspect + from django.utils.translation import gettext_lazy as _ DeliveryCycle = [ @@ -16,6 +18,26 @@ class ProductTypes: class Permission: + permission_strings = False + + @staticmethod + def all() -> [str]: + if not Permission.permission_strings: + perms = [] + internal_classes = [ + member[1] + for member in inspect.getmembers(Permission) + if inspect.isclass(member[1]) + ] + for clazz in internal_classes: + for field in filter(lambda x: not x.startswith("_"), dir(clazz)): + perm = getattr(clazz, field) + if type(perm) is str: + perms.append(perm) + + Permission.permission_strings = perms + return Permission.permission_strings + class Coop: VIEW = "coop.view" MANAGE = "coop.manage" diff --git a/tapir/wirgarten/factories.py b/tapir/wirgarten/factories.py index 076ebd95..e6db5f9a 100644 --- a/tapir/wirgarten/factories.py +++ b/tapir/wirgarten/factories.py @@ -3,24 +3,36 @@ from datetime import timedelta from django.utils import timezone -from tapir.wirgarten.models import (PickupLocation, Member, ShareOwnership, MandateReference, Subscription, Product, \ - ProductType, GrowingPeriod, PaymentTransaction, Payment, ExportedFile) +from tapir.wirgarten.models import ( + PickupLocation, + Member, + ShareOwnership, + MandateReference, + Subscription, + Product, + ProductType, + GrowingPeriod, + PaymentTransaction, + Payment, + ExportedFile, +) from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.wirgarten.service.payment import generate_mandate_ref from tapir.wirgarten.constants import NO_DELIVERY + class PickupLocationFactory(factory.django.DjangoModelFactory): class Meta: model = PickupLocation - + name = factory.fuzzy.FuzzyText() coords_lon = 1 coords_lat = 2 street = factory.fuzzy.FuzzyText() street_2 = factory.fuzzy.FuzzyText() - postcode = factory.Faker('postcode') - city = factory.Faker('city') - info = factory.Faker('sentence') + postcode = factory.Faker("postcode") + city = factory.Faker("city") + info = factory.Faker("sentence") class MemberFactory(TapirUserFactory): @@ -37,7 +49,6 @@ class Meta: class MandateReferenceFactory(factory.django.DjangoModelFactory): - class Meta: model = MandateReference @@ -45,7 +56,7 @@ class Params: for_shares = factory.Trait( ref=factory.LazyAttribute(lambda o: generate_mandate_ref(o.member.id, True)) ) - + ref = factory.LazyAttribute(lambda o: generate_mandate_ref(o.member.id, False)) member = factory.SubFactory(MemberFactory) start_ts = factory.LazyAttribute(lambda _: timezone.now()) @@ -65,15 +76,16 @@ class Meta: type = factory.SubFactory(ProductTypeFactory) name = factory.fuzzy.FuzzyText() - + class GrowingPeriodFactory(factory.django.DjangoModelFactory): class Meta: model = GrowingPeriod start_date = timezone.now().replace(month=3) - end_date = factory.LazyAttribute(lambda o: o.start_date.replace(month=2, year=o.start_date.year+1)) - + end_date = factory.LazyAttribute( + lambda o: o.start_date.replace(month=2, year=o.start_date.year + 1) + ) class SubscriptionFactory(factory.django.DjangoModelFactory): @@ -88,7 +100,9 @@ class Meta: end_date = factory.LazyAttribute(lambda _: timezone.now()) cancellation_ts = factory.LazyAttribute(lambda _: timezone.now()) solidarity_price = 0.3 - mandate_ref = factory.SubFactory(MandateReferenceFactory, member=factory.SelfAttribute('..member')) + mandate_ref = factory.SubFactory( + MandateReferenceFactory, member=factory.SelfAttribute("..member") + ) class ShareOwnershipFactory(factory.django.DjangoModelFactory): @@ -99,8 +113,11 @@ class Meta: quantity = 1 share_price = 10 entry_date = factory.LazyAttribute(lambda _: timezone.now()) - mandate_ref = factory.SubFactory(MandateReferenceFactory, member=factory.SelfAttribute('..member'), for_shares=True) - + mandate_ref = factory.SubFactory( + MandateReferenceFactory, + member=factory.SelfAttribute("..member"), + for_shares=True, + ) class ExportedFileFactory(factory.django.DjangoModelFactory): @@ -109,7 +126,7 @@ class Meta: name = factory.fuzzy.FuzzyText() type = "pdf" - #file = models.BinaryField(null=False) + # file = models.BinaryField(null=False) created_at = factory.LazyAttribute(lambda _: timezone.now()) @@ -124,9 +141,9 @@ class Meta: class PaymentFactory(factory.django.DjangoModelFactory): class Meta: model = Payment - + due_date = factory.LazyAttribute(lambda _: timezone.now() + timedelta(weeks=1)) mandate_ref = factory.SubFactory(MandateReferenceFactory) amount = 100 status = "PAID" - transaction = factory.SubFactory(PaymentTransactionFactory) \ No newline at end of file + transaction = factory.SubFactory(PaymentTransactionFactory) diff --git a/tapir/wirgarten/forms/member/forms.py b/tapir/wirgarten/forms/member/forms.py index 11e3e089..faacb900 100644 --- a/tapir/wirgarten/forms/member/forms.py +++ b/tapir/wirgarten/forms/member/forms.py @@ -1,5 +1,5 @@ -from datetime import datetime -from importlib.resources import _ +from datetime import datetime, timezone +from django.utils.translation import gettext_lazy as _ from django.core.validators import ( EmailValidator, @@ -17,6 +17,7 @@ IntegerField, ) +from tapir import settings from tapir.configuration.parameter import get_parameter_value from tapir.utils.forms import TapirPhoneNumberField, DateInput from tapir.wirgarten.models import Payment, Member, ShareOwnership @@ -55,6 +56,27 @@ class Meta: phone_number = TapirPhoneNumberField(label=_("Telefon-Nr")) + def is_valid(self): + # this is kind of problematic: in the registration wizard the form field key contains the step name + # FIXME: cleanup and constans for keys!! + email = ( + self.data["Personal Details-email"] + if "Personal Details-email" in self.data + else self.data["email"] + ) + + duplicate_email_query = Member.objects.filter(email=email) + if self.instance and self.instance.id: + duplicate_email_query = duplicate_email_query.exclude(id=self.instance.id) + + if duplicate_email_query.exists(): + self.add_error( + "email", _("Ein Nutzer mit dieser Email Adresse existiert bereits.") + ) + return False + + return super(PersonalDataForm, self).is_valid() + class PaymentAmountEditForm(Form): def __init__(self, *args, **kwargs): @@ -247,7 +269,7 @@ def save(self): if value ] ) - now = datetime.now() + now = datetime.now(tz=timezone.utc) for sub in subs_to_cancel: sub.cancellation_ts = now sub.end_date = self.next_trial_end_date diff --git a/tapir/wirgarten/forms/pickup_location.py b/tapir/wirgarten/forms/pickup_location.py index 98fc02af..7c482213 100644 --- a/tapir/wirgarten/forms/pickup_location.py +++ b/tapir/wirgarten/forms/pickup_location.py @@ -1,5 +1,5 @@ import json -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms diff --git a/tapir/wirgarten/forms/product_cfg/period_product_cfg_forms.py b/tapir/wirgarten/forms/product_cfg/period_product_cfg_forms.py index f8988442..b22c34e8 100644 --- a/tapir/wirgarten/forms/product_cfg/period_product_cfg_forms.py +++ b/tapir/wirgarten/forms/product_cfg/period_product_cfg_forms.py @@ -1,5 +1,5 @@ import datetime -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from dateutil.relativedelta import relativedelta from django import forms diff --git a/tapir/wirgarten/forms/registration/bestellcoop.py b/tapir/wirgarten/forms/registration/bestellcoop.py index 5542bd27..20b27964 100644 --- a/tapir/wirgarten/forms/registration/bestellcoop.py +++ b/tapir/wirgarten/forms/registration/bestellcoop.py @@ -1,5 +1,5 @@ from datetime import date -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms from django.db import transaction diff --git a/tapir/wirgarten/forms/registration/chicken_shares.py b/tapir/wirgarten/forms/registration/chicken_shares.py index 41c6d3d4..8bdaa0a9 100644 --- a/tapir/wirgarten/forms/registration/chicken_shares.py +++ b/tapir/wirgarten/forms/registration/chicken_shares.py @@ -1,5 +1,5 @@ from datetime import date -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms from django.db import transaction diff --git a/tapir/wirgarten/forms/registration/consents.py b/tapir/wirgarten/forms/registration/consents.py index 49f6c168..b477328b 100644 --- a/tapir/wirgarten/forms/registration/consents.py +++ b/tapir/wirgarten/forms/registration/consents.py @@ -1,4 +1,4 @@ -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms diff --git a/tapir/wirgarten/forms/registration/coop_shares.py b/tapir/wirgarten/forms/registration/coop_shares.py index 0a25d36b..624a19b7 100644 --- a/tapir/wirgarten/forms/registration/coop_shares.py +++ b/tapir/wirgarten/forms/registration/coop_shares.py @@ -1,4 +1,4 @@ -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms from django.core.validators import MinValueValidator diff --git a/tapir/wirgarten/forms/registration/harvest_shares.py b/tapir/wirgarten/forms/registration/harvest_shares.py index 880023fe..b5db65d5 100644 --- a/tapir/wirgarten/forms/registration/harvest_shares.py +++ b/tapir/wirgarten/forms/registration/harvest_shares.py @@ -1,5 +1,5 @@ from datetime import date -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms from django.db import transaction diff --git a/tapir/wirgarten/forms/registration/payment_data.py b/tapir/wirgarten/forms/registration/payment_data.py index edd57d98..24f5ccd5 100644 --- a/tapir/wirgarten/forms/registration/payment_data.py +++ b/tapir/wirgarten/forms/registration/payment_data.py @@ -1,4 +1,4 @@ -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms from localflavor.exceptions import ValidationError @@ -35,7 +35,6 @@ def get_initial_for_field(self, field, field_name): return if field_name in ["account_owner", "iban", "bic"]: - print(self.instance) return getattr(self.instance, field_name) else: return super().get_initial_for_field(field, field_name) diff --git a/tapir/wirgarten/forms/registration/summary.py b/tapir/wirgarten/forms/registration/summary.py index 72508a70..f7002afa 100644 --- a/tapir/wirgarten/forms/registration/summary.py +++ b/tapir/wirgarten/forms/registration/summary.py @@ -1,4 +1,4 @@ -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django import forms diff --git a/tapir/wirgarten/migrations/0001_initial.py b/tapir/wirgarten/migrations/0001_initial.py index acfcb4f7..8bdff4d8 100644 --- a/tapir/wirgarten/migrations/0001_initial.py +++ b/tapir/wirgarten/migrations/0001_initial.py @@ -1,33 +1,158 @@ -# Generated by Django 3.2.15 on 2022-09-23 10:37 +# Generated by Django 3.2.16 on 2023-01-30 18:56 +import datetime +import django.contrib.postgres.fields.hstore +from django.db import migrations, models import django.db.models.deletion +import functools import localflavor.generic.models -from django.db import migrations, models - import tapir.accounts.models +import tapir.core.models +import tapir.wirgarten.models class Migration(migrations.Migration): + initial = True - dependencies = [] + dependencies = [ + ("log", "0001_initial"), + ("accounts", "0001_initial"), + ] operations = [ migrations.CreateModel( - name="GrowingPeriod", + name="Deliveries", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ("delivery_date", models.DateField()), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="DeliveryExceptionPeriod", fields=[ ( "id", - models.BigAutoField( + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ("start_date", models.DateField()), + ("end_date", models.DateField()), + ("comment", models.CharField(default="", max_length=128)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="EditFuturePaymentLogEntry", + fields=[ + ( + "logentry_ptr", + models.OneToOneField( auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="log.logentry", + ), + ), + ("old_values", django.contrib.postgres.fields.hstore.HStoreField()), + ("new_values", django.contrib.postgres.fields.hstore.HStoreField()), + ("comment", models.CharField(max_length=256)), + ], + options={ + "abstract": False, + }, + bases=("log.logentry",), + ), + migrations.CreateModel( + name="ExportedFile", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=256)), + ( + "type", + models.CharField( + choices=[("csv", "CSV"), ("pdf", "PDF")], max_length=8 + ), + ), + ("file", models.BinaryField()), + ("created_at", models.DateTimeField(auto_now_add=True)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="GrowingPeriod", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), ("start_date", models.DateField()), ("end_date", models.DateField()), ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="MandateReference", + fields=[ + ( + "ref", + models.CharField(max_length=35, primary_key=True, serialize=False), + ), + ("start_ts", models.DateTimeField()), + ("end_ts", models.DateTimeField(null=True)), + ], ), migrations.CreateModel( name="Member", @@ -45,13 +170,16 @@ class Migration(migrations.Migration): ), ( "account_owner", - models.CharField(max_length=150, verbose_name="Account owner"), + models.CharField( + max_length=150, null=True, verbose_name="Account owner" + ), ), ( "iban", localflavor.generic.models.IBANField( include_countries=None, max_length=34, + null=True, use_nordea_extensions=False, verbose_name="IBAN", ), @@ -59,36 +187,99 @@ class Migration(migrations.Migration): ( "bic", localflavor.generic.models.BICField( - max_length=11, verbose_name="BIC" + max_length=11, null=True, verbose_name="BIC" ), ), - ("sepa_consent", models.DateTimeField(verbose_name="SEPA Consent")), + ( + "sepa_consent", + models.DateTimeField(null=True, verbose_name="SEPA Consent"), + ), ( "withdrawal_consent", - models.DateTimeField(verbose_name="Right of withdrawal consent"), + models.DateTimeField( + null=True, verbose_name="Right of withdrawal consent" + ), ), ( "privacy_consent", - models.DateTimeField(verbose_name="Privacy consent"), + models.DateTimeField(null=True, verbose_name="Privacy consent"), ), + ("created_at", models.DateTimeField(auto_now_add=True)), ], options={ "abstract": False, }, - bases=("accounts.tapiruser", models.Model), - managers=[ - ("objects", tapir.accounts.models.TapirUserManager()), + bases=("accounts.tapiruser",), + ), + migrations.CreateModel( + name="Payment", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ("due_date", models.DateField()), + ("amount", models.DecimalField(decimal_places=2, max_digits=8)), + ( + "status", + models.CharField( + choices=[("PAID", "Bezahlt"), ("DUE", "Offen")], + default="DUE", + max_length=8, + ), + ), + ("edited", models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name="PaymentTransaction", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ( + "created_at", + models.DateTimeField( + default=functools.partial(datetime.datetime.now, *(), **{}) + ), + ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( name="PickupLocation", fields=[ ( "id", - models.BigAutoField( - auto_created=True, + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), @@ -132,24 +323,93 @@ class Migration(migrations.Migration): ), ], ), + migrations.CreateModel( + name="PickupLocationCapability", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ], + options={ + "abstract": False, + }, + ), migrations.CreateModel( name="Product", fields=[ ( "id", - models.BigAutoField( - auto_created=True, + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), ("name", models.CharField(max_length=128)), + ("deleted", models.BooleanField(default=False)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="ProductCapacity", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), + ("capacity", models.DecimalField(decimal_places=2, max_digits=20)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="ProductPrice", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", + ), + ), ( "price", - models.DecimalField(decimal_places=2, editable=False, max_digits=6), + models.DecimalField(decimal_places=2, editable=False, max_digits=8), ), - ("deleted", models.IntegerField(default=0)), + ("valid_from", models.DateField(editable=False)), ], ), migrations.CreateModel( @@ -157,15 +417,59 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.BigAutoField( - auto_created=True, + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), - ("name", models.CharField(max_length=128)), + ("name", models.CharField(max_length=128, verbose_name="Produkt Name")), + ( + "delivery_cycle", + models.CharField( + choices=[ + ("no_delivery", "Keine Lieferung/Abholung"), + ("weekly", "1x pro Woche"), + ("odd_weeks", "2x pro Monat (ungerade KW)"), + ("even_weeks", "2x pro Monat (gerade KW)"), + ("monthly", "1x pro Monat"), + ], + default=("no_delivery", "Keine Lieferung/Abholung"), + max_length=16, + verbose_name="Lieferzyklus", + ), + ), + ], + ), + migrations.CreateModel( + name="TransferCoopSharesLogEntry", + fields=[ + ( + "logentry_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="log.logentry", + ), + ), + ("quantity", models.PositiveSmallIntegerField()), + ( + "target_member", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.member", + ), + ), ], + bases=("log.logentry",), ), migrations.CreateModel( name="HarvestShareProduct", @@ -183,63 +487,134 @@ class Migration(migrations.Migration): ), ("min_coop_shares", models.IntegerField()), ], + options={ + "abstract": False, + }, bases=("wirgarten.product",), ), migrations.CreateModel( - name="Subscription", + name="ReceivedCoopSharesLogEntry", fields=[ ( - "id", - models.BigAutoField( + "transfercoopshareslogentry_ptr", + models.OneToOneField( auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wirgarten.transfercoopshareslogentry", + ), + ), + ], + bases=("wirgarten.transfercoopshareslogentry",), + ), + migrations.CreateModel( + name="WaitingListEntry", + fields=[ + ( + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), - ("quantity", models.PositiveSmallIntegerField()), - ("start_date", models.DateField()), - ("end_date", models.DateField()), - ("cancellation_ts", models.DateTimeField(null=True)), - ("solidarity_price", models.FloatField(default=0.0)), + ("first_name", models.CharField(max_length=256)), + ("last_name", models.CharField(max_length=256)), + ("email", models.CharField(max_length=256)), + ( + "type", + models.CharField( + choices=[ + ("HARVEST_SHARES", "Ernteanteile"), + ("COOP_SHARES", "Genossenschaftsanteile"), + ], + max_length=32, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("privacy_consent", models.DateTimeField()), ( "member", models.ForeignKey( + null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="wirgarten.member", ), ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="TaxRate", + fields=[ ( - "period", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.growingperiod", + "id", + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, + primary_key=True, + serialize=False, + unique=True, + verbose_name="ID", ), ), + ("tax_rate", models.FloatField()), ( - "product", + "valid_from", + models.DateField( + default=functools.partial(datetime.date.today, *(), **{}) + ), + ), + ("valid_to", models.DateField(null=True)), + ( + "product_type", models.ForeignKey( on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.product", + to="wirgarten.producttype", ), ), ], ), migrations.CreateModel( - name="ShareOwnership", + name="Subscription", fields=[ ( "id", - models.BigAutoField( - auto_created=True, + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), - ("quantity", models.PositiveSmallIntegerField(null=False)), - ("share_price", models.DecimalField(decimal_places=2, max_digits=5)), - ("entry_date", models.DateField()), + ("quantity", models.PositiveSmallIntegerField()), + ("start_date", models.DateField()), + ("end_date", models.DateField()), + ("cancellation_ts", models.DateTimeField(null=True)), + ("solidarity_price", models.FloatField(default=0.0)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "mandate_ref", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.mandatereference", + ), + ), ( "member", models.ForeignKey( @@ -247,36 +622,90 @@ class Migration(migrations.Migration): to="wirgarten.member", ), ), + ( + "period", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.growingperiod", + ), + ), + ( + "product", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.product", + ), + ), ], + bases=(models.Model, tapir.wirgarten.models.Payable), ), migrations.CreateModel( - name="ProductCapacity", + name="ShareOwnership", fields=[ ( "id", - models.BigAutoField( - auto_created=True, + models.CharField( + default=functools.partial( + tapir.core.models.generate_id, *(), **{} + ), + max_length=10, primary_key=True, serialize=False, + unique=True, verbose_name="ID", ), ), - ("capacity", models.DecimalField(decimal_places=2, max_digits=20)), + ("quantity", models.PositiveSmallIntegerField()), + ("share_price", models.DecimalField(decimal_places=2, max_digits=5)), + ("entry_date", models.DateField()), + ("created_at", models.DateTimeField(auto_now_add=True)), ( - "period", + "mandate_ref", models.ForeignKey( on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.growingperiod", + to="wirgarten.mandatereference", ), ), ( - "product_type", + "member", models.ForeignKey( on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.producttype", + to="wirgarten.member", ), ), ], + bases=(models.Model, tapir.wirgarten.models.Payable), + ), + migrations.AddConstraint( + model_name="producttype", + constraint=models.UniqueConstraint( + fields=("name",), name="unique_product_Type" + ), + ), + migrations.AddField( + model_name="productprice", + name="product", + field=models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.product", + ), + ), + migrations.AddField( + model_name="productcapacity", + name="period", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.growingperiod", + ), + ), + migrations.AddField( + model_name="productcapacity", + name="product_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.producttype", + ), ), migrations.AddField( model_name="product", @@ -287,18 +716,139 @@ class Migration(migrations.Migration): to="wirgarten.producttype", ), ), + migrations.AddField( + model_name="pickuplocationcapability", + name="pickup_location", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.pickuplocation", + ), + ), + migrations.AddField( + model_name="pickuplocationcapability", + name="product_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.producttype", + ), + ), migrations.AddConstraint( model_name="pickuplocation", constraint=models.UniqueConstraint( fields=("name", "coords_lat", "coords_lon"), name="unique_location" ), ), + migrations.AddField( + model_name="paymenttransaction", + name="file", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.exportedfile", + ), + ), + migrations.AddField( + model_name="payment", + name="mandate_ref", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.mandatereference", + ), + ), + migrations.AddField( + model_name="payment", + name="transaction", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.paymenttransaction", + ), + ), migrations.AddField( model_name="member", name="pickup_location", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.pickuplocation", + ), + ), + migrations.AddField( + model_name="mandatereference", + name="member", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, to="wirgarten.member" + ), + ), + migrations.AddField( + model_name="deliveryexceptionperiod", + name="product_type", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="wirgarten.producttype", + ), + ), + migrations.AddField( + model_name="deliveries", + name="member", + field=models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, to="wirgarten.member" + ), + ), + migrations.AddField( + model_name="deliveries", + name="pickup_location", field=models.ForeignKey( on_delete=django.db.models.deletion.DO_NOTHING, to="wirgarten.pickuplocation", ), ), + migrations.AddIndex( + model_name="taxrate", + index=models.Index( + fields=["product_type"], name="idx_tax_rate_product_type" + ), + ), + migrations.AddConstraint( + model_name="taxrate", + constraint=models.UniqueConstraint( + fields=("product_type", "valid_from"), + name="unique_tax_rate_product_type_valid_from", + ), + ), + migrations.AddIndex( + model_name="subscription", + index=models.Index( + fields=["period_id", "created_at"], + name="wirgarten_s_period__f679c5_idx", + ), + ), + migrations.AddIndex( + model_name="shareownership", + index=models.Index(fields=["member"], name="idx_shareownership_member"), + ), + migrations.AddIndex( + model_name="productprice", + index=models.Index(fields=["product"], name="idx_productprice_product"), + ), + migrations.AddConstraint( + model_name="productprice", + constraint=models.UniqueConstraint( + fields=("product", "valid_from"), name="unique_product_price_date" + ), + ), + migrations.AddIndex( + model_name="payment", + index=models.Index(fields=["mandate_ref"], name="idx_payment_mandate_ref"), + ), + migrations.AddConstraint( + model_name="payment", + constraint=models.UniqueConstraint( + fields=("mandate_ref", "due_date"), name="unique_mandate_ref_date" + ), + ), + migrations.AddIndex( + model_name="mandatereference", + index=models.Index(fields=["member"], name="idx_mandatereference_mamber"), + ), ] diff --git a/tapir/wirgarten/migrations/0002_exportedfile.py b/tapir/wirgarten/migrations/0002_exportedfile.py deleted file mode 100644 index b773f1d1..00000000 --- a/tapir/wirgarten/migrations/0002_exportedfile.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.2.15 on 2022-09-26 09:55 - -import datetime -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0001_initial"), - ] - - operations = [ - migrations.CreateModel( - name="ExportedFile", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=256)), - ( - "type", - models.CharField( - choices=[("csv", "CSV"), ("pdf", "PDF")], max_length=8 - ), - ), - ("file", models.BinaryField()), - ( - "created_at", - models.DateTimeField( - default=datetime.datetime(2022, 9, 26, 11, 55, 26, 871123) - ), - ), - ], - ), - ] diff --git a/tapir/wirgarten/migrations/0003_auto_20221007_1342.py b/tapir/wirgarten/migrations/0003_auto_20221007_1342.py deleted file mode 100644 index 08caf629..00000000 --- a/tapir/wirgarten/migrations/0003_auto_20221007_1342.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.15 on 2022-10-07 11:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0002_exportedfile"), - ] - - operations = [ - migrations.AddField( - model_name="producttype", - name="pickup_enabled", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="exportedfile", - name="created_at", - field=models.DateTimeField(auto_now_add=True), - ), - ] diff --git a/tapir/wirgarten/migrations/0004_auto_20221007_1610.py b/tapir/wirgarten/migrations/0004_auto_20221007_1610.py deleted file mode 100644 index 891338ab..00000000 --- a/tapir/wirgarten/migrations/0004_auto_20221007_1610.py +++ /dev/null @@ -1,110 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-07 14:10 - -from django.db import migrations, models -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - dependencies = [ - ("wirgarten", "0003_auto_20221007_1342"), - ] - - operations = [ - migrations.AlterField( - model_name="exportedfile", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="growingperiod", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="pickuplocation", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="product", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="productcapacity", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="producttype", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="shareownership", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="subscription", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=16, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0004_auto_20221019_1000.py b/tapir/wirgarten/migrations/0004_auto_20221019_1000.py deleted file mode 100644 index 9a4a78da..00000000 --- a/tapir/wirgarten/migrations/0004_auto_20221019_1000.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-19 08:00 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0003_auto_20221007_1342"), - ] - - operations = [ - migrations.CreateModel( - name="MandateReference", - fields=[ - ( - "ref", - models.CharField(max_length=35, primary_key=True, serialize=False), - ), - ("start_ts", models.DateTimeField()), - ("end_ts", models.DateTimeField(null=True)), - ( - "member", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.member", - ), - ), - ], - ), - migrations.AddField( - model_name="subscription", - name="mandate_ref", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.mandatereference", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0005_alter_subscription_mandate_ref.py b/tapir/wirgarten/migrations/0005_alter_subscription_mandate_ref.py deleted file mode 100644 index 206a4f5a..00000000 --- a/tapir/wirgarten/migrations/0005_alter_subscription_mandate_ref.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-19 08:01 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0004_auto_20221019_1000"), - ] - - operations = [ - migrations.AlterField( - model_name="subscription", - name="mandate_ref", - field=models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.mandatereference", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0006_shareownership_mandate_ref.py b/tapir/wirgarten/migrations/0006_shareownership_mandate_ref.py deleted file mode 100644 index ba86ed0c..00000000 --- a/tapir/wirgarten/migrations/0006_shareownership_mandate_ref.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-21 09:43 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0005_alter_subscription_mandate_ref"), - ] - - operations = [ - migrations.AddField( - model_name="shareownership", - name="mandate_ref", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.mandatereference", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0007_alter_shareownership_mandate_ref.py b/tapir/wirgarten/migrations/0007_alter_shareownership_mandate_ref.py deleted file mode 100644 index 58e8353c..00000000 --- a/tapir/wirgarten/migrations/0007_alter_shareownership_mandate_ref.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-21 09:46 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0006_shareownership_mandate_ref"), - ] - - operations = [ - migrations.AlterField( - model_name="shareownership", - name="mandate_ref", - field=models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.mandatereference", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0008_payment.py b/tapir/wirgarten/migrations/0008_payment.py deleted file mode 100644 index 96010f36..00000000 --- a/tapir/wirgarten/migrations/0008_payment.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-21 15:45 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0007_alter_shareownership_mandate_ref"), - ] - - operations = [ - migrations.CreateModel( - name="Payment", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("due_date", models.DateField()), - ("amount", models.DecimalField(decimal_places=2, max_digits=8)), - ( - "mandate_ref", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.mandatereference", - ), - ), - ], - ), - ] diff --git a/tapir/wirgarten/migrations/0009_payment_status.py b/tapir/wirgarten/migrations/0009_payment_status.py deleted file mode 100644 index 8beec72d..00000000 --- a/tapir/wirgarten/migrations/0009_payment_status.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-21 16:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0008_payment"), - ] - - operations = [ - migrations.AddField( - model_name="payment", - name="status", - field=models.CharField( - choices=[ - ("UPCOMING", "Bevorstehend"), - ("PAID", "Bezahlt"), - ("DUE", "Offen"), - ], - default="DUE", - max_length=8, - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0010_deliveries.py b/tapir/wirgarten/migrations/0010_deliveries.py deleted file mode 100644 index 6461eb66..00000000 --- a/tapir/wirgarten/migrations/0010_deliveries.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-21 17:38 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0009_payment_status"), - ] - - operations = [ - migrations.CreateModel( - name="Deliveries", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("delivery_date", models.DateField()), - ( - "member", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.member", - ), - ), - ], - ), - ] diff --git a/tapir/wirgarten/migrations/0011_deliveries_pickup_location.py b/tapir/wirgarten/migrations/0011_deliveries_pickup_location.py deleted file mode 100644 index c0cbdf17..00000000 --- a/tapir/wirgarten/migrations/0011_deliveries_pickup_location.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-22 08:10 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0010_deliveries"), - ] - - operations = [ - migrations.AddField( - model_name="deliveries", - name="pickup_location", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.pickuplocation", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0012_alter_deliveries_pickup_location.py b/tapir/wirgarten/migrations/0012_alter_deliveries_pickup_location.py deleted file mode 100644 index 57fa7ae8..00000000 --- a/tapir/wirgarten/migrations/0012_alter_deliveries_pickup_location.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-22 08:11 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0011_deliveries_pickup_location"), - ] - - operations = [ - migrations.AlterField( - model_name="deliveries", - name="pickup_location", - field=models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.pickuplocation", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0013_merge_20221022_1732.py b/tapir/wirgarten/migrations/0013_merge_20221022_1732.py deleted file mode 100644 index 726cdbf3..00000000 --- a/tapir/wirgarten/migrations/0013_merge_20221022_1732.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-22 15:32 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0004_auto_20221007_1610"), - ("wirgarten", "0012_alter_deliveries_pickup_location"), - ] - - operations = [] diff --git a/tapir/wirgarten/migrations/0014_auto_20221023_1730.py b/tapir/wirgarten/migrations/0014_auto_20221023_1730.py deleted file mode 100644 index 199fe6c7..00000000 --- a/tapir/wirgarten/migrations/0014_auto_20221023_1730.py +++ /dev/null @@ -1,145 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-23 15:30 - -from django.db import migrations, models -import django.db.models.deletion -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0013_merge_20221022_1732"), - ] - - operations = [ - migrations.AlterField( - model_name="deliveries", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="exportedfile", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="growingperiod", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="member", - name="pickup_location", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.pickuplocation", - ), - ), - migrations.AlterField( - model_name="payment", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="pickuplocation", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="product", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="productcapacity", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="producttype", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="shareownership", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - migrations.AlterField( - model_name="subscription", - name="id", - field=models.CharField( - default=functools.partial(tapir.core.models.generate_id, *(), **{}), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0015_payment_unique_mandate_ref_date.py b/tapir/wirgarten/migrations/0015_payment_unique_mandate_ref_date.py deleted file mode 100644 index 6e37e1fc..00000000 --- a/tapir/wirgarten/migrations/0015_payment_unique_mandate_ref_date.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-25 07:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0014_auto_20221023_1730"), - ] - - operations = [ - migrations.AddConstraint( - model_name="payment", - constraint=models.UniqueConstraint( - fields=("mandate_ref", "due_date"), name="unique_mandate_ref_date" - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0016_taxrate.py b/tapir/wirgarten/migrations/0016_taxrate.py deleted file mode 100644 index d86f3a8f..00000000 --- a/tapir/wirgarten/migrations/0016_taxrate.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-25 12:29 - -import datetime -from django.db import migrations, models -import django.db.models.deletion -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0015_payment_unique_mandate_ref_date"), - ] - - operations = [ - migrations.CreateModel( - name="TaxRate", - fields=[ - ( - "id", - models.CharField( - default=functools.partial( - tapir.core.models.generate_id, *(), **{} - ), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ("tax_rate", models.FloatField()), - ( - "valid_from", - models.DateField( - default=functools.partial(datetime.date.today, *(), **{}) - ), - ), - ("valid_to", models.DateField(null=True)), - ( - "product_type", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.producttype", - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/tapir/wirgarten/migrations/0017_auto_20221031_1541.py b/tapir/wirgarten/migrations/0017_auto_20221031_1541.py deleted file mode 100644 index 876e651b..00000000 --- a/tapir/wirgarten/migrations/0017_auto_20221031_1541.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-31 14:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0016_taxrate"), - ] - - operations = [ - migrations.AddField( - model_name="payment", - name="edited", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="payment", - name="status", - field=models.CharField( - choices=[("PAID", "Bezahlt"), ("DUE", "Offen")], - default="DUE", - max_length=8, - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0017_deliveryexceptionperiod.py b/tapir/wirgarten/migrations/0017_deliveryexceptionperiod.py deleted file mode 100644 index 72faebd3..00000000 --- a/tapir/wirgarten/migrations/0017_deliveryexceptionperiod.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-27 14:50 - -from django.db import migrations, models -import django.db.models.deletion -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0016_taxrate"), - ] - - operations = [ - migrations.CreateModel( - name="DeliveryExceptionPeriod", - fields=[ - ( - "id", - models.CharField( - default=functools.partial( - tapir.core.models.generate_id, *(), **{} - ), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ("start_date", models.DateField()), - ("end_date", models.DateField()), - ("comment", models.CharField(default="", max_length=128)), - ( - "product_type", - models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.producttype", - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/tapir/wirgarten/migrations/0018_merge_20221031_1741.py b/tapir/wirgarten/migrations/0018_merge_20221031_1741.py deleted file mode 100644 index bcd9ae6b..00000000 --- a/tapir/wirgarten/migrations/0018_merge_20221031_1741.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 3.2.16 on 2022-10-31 16:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0017_auto_20221031_1541"), - ("wirgarten", "0017_deliveryexceptionperiod"), - ] - - operations = [] diff --git a/tapir/wirgarten/migrations/0019_editfuturepaymentlogentry.py b/tapir/wirgarten/migrations/0019_editfuturepaymentlogentry.py deleted file mode 100644 index d1a5a54f..00000000 --- a/tapir/wirgarten/migrations/0019_editfuturepaymentlogentry.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-04 09:19 - -import django.contrib.postgres.fields.hstore -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("log", "0001_initial"), - ("wirgarten", "0018_merge_20221031_1741"), - ] - - operations = [ - migrations.CreateModel( - name="EditFuturePaymentLogEntry", - fields=[ - ( - "logentry_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="log.logentry", - ), - ), - ("old_values", django.contrib.postgres.fields.hstore.HStoreField()), - ("new_values", django.contrib.postgres.fields.hstore.HStoreField()), - ], - options={ - "abstract": False, - }, - bases=("log.logentry",), - ), - ] diff --git a/tapir/wirgarten/migrations/0020_editfuturepaymentlogentry_comment.py b/tapir/wirgarten/migrations/0020_editfuturepaymentlogentry_comment.py deleted file mode 100644 index ddccdead..00000000 --- a/tapir/wirgarten/migrations/0020_editfuturepaymentlogentry_comment.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-04 15:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0019_editfuturepaymentlogentry"), - ] - - operations = [ - migrations.AddField( - model_name="editfuturepaymentlogentry", - name="comment", - field=models.CharField(max_length=256, null=True), - ), - ] diff --git a/tapir/wirgarten/migrations/0021_alter_editfuturepaymentlogentry_comment.py b/tapir/wirgarten/migrations/0021_alter_editfuturepaymentlogentry_comment.py deleted file mode 100644 index 4c34c889..00000000 --- a/tapir/wirgarten/migrations/0021_alter_editfuturepaymentlogentry_comment.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-04 15:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0020_editfuturepaymentlogentry_comment"), - ] - - operations = [ - migrations.AlterField( - model_name="editfuturepaymentlogentry", - name="comment", - field=models.CharField(max_length=256), - ), - ] diff --git a/tapir/wirgarten/migrations/0022_auto_20221108_1254.py b/tapir/wirgarten/migrations/0022_auto_20221108_1254.py deleted file mode 100644 index b80cad9a..00000000 --- a/tapir/wirgarten/migrations/0022_auto_20221108_1254.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-08 11:54 - -import datetime -from django.db import migrations, models -import django.db.models.deletion -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0021_alter_editfuturepaymentlogentry_comment"), - ] - - operations = [ - migrations.CreateModel( - name="PaymentTransaction", - fields=[ - ( - "id", - models.CharField( - default=functools.partial( - tapir.core.models.generate_id, *(), **{} - ), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ( - "created_at", - models.DateField( - default=functools.partial(datetime.date.today, *(), **{}) - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.AddField( - model_name="payment", - name="transaction", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.paymenttransaction", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0022_auto_20221108_1312.py b/tapir/wirgarten/migrations/0022_auto_20221108_1312.py deleted file mode 100644 index d9ffd80d..00000000 --- a/tapir/wirgarten/migrations/0022_auto_20221108_1312.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-08 12:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0021_alter_editfuturepaymentlogentry_comment"), - ] - - operations = [ - migrations.RemoveField( - model_name="producttype", - name="pickup_enabled", - ), - migrations.AddField( - model_name="producttype", - name="delivery_cycle", - field=models.CharField( - choices=[ - ("no_delivery", "Keine Lieferung/Abholung"), - ("weekly", "1x pro Woche"), - ("even_weeks", "2x pro Woche (2. und 4. Woche)"), - ("odd_weeks", "2x pro Woche (1. und 3. Woche)"), - ("monthly", "1x pro Monat"), - ], - default="no_delivery", - max_length=16, - verbose_name="Lieferzyklus", - ), - ), - migrations.AlterField( - model_name="producttype", - name="name", - field=models.CharField(max_length=128, verbose_name="Produkt Name"), - ), - ] diff --git a/tapir/wirgarten/migrations/0023_alter_paymenttransaction_created_at.py b/tapir/wirgarten/migrations/0023_alter_paymenttransaction_created_at.py deleted file mode 100644 index 80ef1c29..00000000 --- a/tapir/wirgarten/migrations/0023_alter_paymenttransaction_created_at.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-08 13:24 - -import datetime -from django.db import migrations, models -import functools - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0022_auto_20221108_1254"), - ] - - operations = [ - migrations.AlterField( - model_name="paymenttransaction", - name="created_at", - field=models.DateTimeField( - default=functools.partial(datetime.datetime.now, *(), **{}) - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0024_paymenttransaction_file.py b/tapir/wirgarten/migrations/0024_paymenttransaction_file.py deleted file mode 100644 index e18b98fe..00000000 --- a/tapir/wirgarten/migrations/0024_paymenttransaction_file.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-08 13:28 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0023_alter_paymenttransaction_created_at"), - ] - - operations = [ - migrations.AddField( - model_name="paymenttransaction", - name="file", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.exportedfile", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0025_alter_paymenttransaction_file.py b/tapir/wirgarten/migrations/0025_alter_paymenttransaction_file.py deleted file mode 100644 index ab4da651..00000000 --- a/tapir/wirgarten/migrations/0025_alter_paymenttransaction_file.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-08 13:29 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0024_paymenttransaction_file"), - ] - - operations = [ - migrations.AlterField( - model_name="paymenttransaction", - name="file", - field=models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.exportedfile", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0026_merge_20221109_1136.py b/tapir/wirgarten/migrations/0026_merge_20221109_1136.py deleted file mode 100644 index 54343233..00000000 --- a/tapir/wirgarten/migrations/0026_merge_20221109_1136.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-09 10:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0022_auto_20221108_1312"), - ("wirgarten", "0025_alter_paymenttransaction_file"), - ] - - operations = [] diff --git a/tapir/wirgarten/migrations/0027_auto_20221109_1136.py b/tapir/wirgarten/migrations/0027_auto_20221109_1136.py deleted file mode 100644 index ff6ba466..00000000 --- a/tapir/wirgarten/migrations/0027_auto_20221109_1136.py +++ /dev/null @@ -1,67 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-09 10:36 - -from django.db import migrations, models -import django.db.models.deletion -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0026_merge_20221109_1136"), - ] - - operations = [ - migrations.AlterField( - model_name="producttype", - name="delivery_cycle", - field=models.CharField( - choices=[ - ("no_delivery", "Keine Lieferung/Abholung"), - ("weekly", "1x pro Woche"), - ("odd_weeks", "2x pro Monat (1. und 3. Woche)"), - ("even_weeks", "2x pro Monat (2. und 4. Woche)"), - ("monthly", "1x pro Monat"), - ], - default=("no_delivery", "Keine Lieferung/Abholung"), - max_length=16, - verbose_name="Lieferzyklus", - ), - ), - migrations.CreateModel( - name="PickupLocationCapability", - fields=[ - ( - "id", - models.CharField( - default=functools.partial( - tapir.core.models.generate_id, *(), **{} - ), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ( - "pickup_location", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.pickuplocation", - ), - ), - ( - "product_type", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.producttype", - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/tapir/wirgarten/migrations/0028_auto_20221121_1416.py b/tapir/wirgarten/migrations/0028_auto_20221121_1416.py deleted file mode 100644 index 4a06479f..00000000 --- a/tapir/wirgarten/migrations/0028_auto_20221121_1416.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-21 13:16 - -from django.db import migrations, models -import django.db.models.deletion -import functools -import tapir.core.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0027_auto_20221109_1136"), - ] - - operations = [ - migrations.RemoveField( - model_name="product", - name="price", - ), - migrations.CreateModel( - name="ProductPrice", - fields=[ - ( - "id", - models.CharField( - default=functools.partial( - tapir.core.models.generate_id, *(), **{} - ), - max_length=10, - primary_key=True, - serialize=False, - unique=True, - verbose_name="ID", - ), - ), - ( - "price", - models.DecimalField(decimal_places=2, editable=False, max_digits=8), - ), - ("valid_from", models.DateField(editable=False)), - ( - "product", - models.ForeignKey( - editable=False, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.producttype", - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/tapir/wirgarten/migrations/0029_alter_productprice_product.py b/tapir/wirgarten/migrations/0029_alter_productprice_product.py deleted file mode 100644 index f9112b64..00000000 --- a/tapir/wirgarten/migrations/0029_alter_productprice_product.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-21 13:30 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0028_auto_20221121_1416"), - ] - - operations = [ - migrations.AlterField( - model_name="productprice", - name="product", - field=models.ForeignKey( - editable=False, - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.product", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0030_auto_20221121_1758.py b/tapir/wirgarten/migrations/0030_auto_20221121_1758.py deleted file mode 100644 index 82793fbc..00000000 --- a/tapir/wirgarten/migrations/0030_auto_20221121_1758.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-21 16:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0029_alter_productprice_product"), - ] - - operations = [ - migrations.AlterField( - model_name="product", - name="deleted", - field=models.BooleanField(default=False), - ), - migrations.AddConstraint( - model_name="productprice", - constraint=models.UniqueConstraint( - fields=("product", "valid_from"), name="unique_product_price_date" - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0031_auto_20221123_1856.py b/tapir/wirgarten/migrations/0031_auto_20221123_1856.py deleted file mode 100644 index f403fd3b..00000000 --- a/tapir/wirgarten/migrations/0031_auto_20221123_1856.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-23 17:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0030_auto_20221121_1758"), - ] - - operations = [ - migrations.AlterField( - model_name="producttype", - name="delivery_cycle", - field=models.CharField( - choices=[ - ("no_delivery", "Keine Lieferung/Abholung"), - ("weekly", "1x pro Woche"), - ("odd_weeks", "2x pro Monat (ungerade KW)"), - ("even_weeks", "2x pro Monat (gerade KW)"), - ("monthly", "1x pro Monat"), - ], - default=("no_delivery", "Keine Lieferung/Abholung"), - max_length=16, - verbose_name="Lieferzyklus", - ), - ), - migrations.AddConstraint( - model_name="producttype", - constraint=models.UniqueConstraint( - fields=("name",), name="unique_product_Type" - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0032_auto_20221127_1418.py b/tapir/wirgarten/migrations/0032_auto_20221127_1418.py deleted file mode 100644 index 5c181b47..00000000 --- a/tapir/wirgarten/migrations/0032_auto_20221127_1418.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-27 13:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0031_auto_20221123_1856"), - ] - - operations = [ - migrations.AddIndex( - model_name="mandatereference", - index=models.Index(fields=["member"], name="idx_mandatereference_mamber"), - ), - migrations.AddIndex( - model_name="payment", - index=models.Index(fields=["mandate_ref"], name="idx_payment_mandate_ref"), - ), - migrations.AddIndex( - model_name="productprice", - index=models.Index(fields=["product"], name="idx_productprice_product"), - ), - migrations.AddIndex( - model_name="shareownership", - index=models.Index(fields=["member"], name="idx_shareownership_member"), - ), - migrations.AddIndex( - model_name="taxrate", - index=models.Index( - fields=["product_type"], name="idx_tax_rate_product_type" - ), - ), - migrations.AddConstraint( - model_name="taxrate", - constraint=models.UniqueConstraint( - fields=("product_type", "valid_from"), - name="unique_tax_rate_product_type_valid_from", - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0033_transfercoopshareslogentry.py b/tapir/wirgarten/migrations/0033_transfercoopshareslogentry.py deleted file mode 100644 index 6474eea0..00000000 --- a/tapir/wirgarten/migrations/0033_transfercoopshareslogentry.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-27 17:08 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("log", "0001_initial"), - ("wirgarten", "0032_auto_20221127_1418"), - ] - - operations = [ - migrations.CreateModel( - name="TransferCoopSharesLogEntry", - fields=[ - ( - "logentry_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="log.logentry", - ), - ), - ("quantity", models.PositiveSmallIntegerField()), - ( - "target_member", - models.ForeignKey( - on_delete=django.db.models.deletion.DO_NOTHING, - to="wirgarten.member", - ), - ), - ], - bases=("log.logentry",), - ), - ] diff --git a/tapir/wirgarten/migrations/0034_auto_20221201_1139.py b/tapir/wirgarten/migrations/0034_auto_20221201_1139.py deleted file mode 100644 index 6a0d6c30..00000000 --- a/tapir/wirgarten/migrations/0034_auto_20221201_1139.py +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by Django 3.2.16 on 2022-12-01 10:39 - -from django.db import migrations, models -import localflavor.generic.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0033_transfercoopshareslogentry"), - ] - - operations = [ - migrations.AlterField( - model_name="member", - name="account_owner", - field=models.CharField( - max_length=150, null=True, verbose_name="Account owner" - ), - ), - migrations.AlterField( - model_name="member", - name="bic", - field=localflavor.generic.models.BICField( - max_length=11, null=True, verbose_name="BIC" - ), - ), - migrations.AlterField( - model_name="member", - name="iban", - field=localflavor.generic.models.IBANField( - include_countries=None, - max_length=34, - null=True, - use_nordea_extensions=False, - verbose_name="IBAN", - ), - ), - migrations.AlterField( - model_name="member", - name="privacy_consent", - field=models.DateTimeField(null=True, verbose_name="Privacy consent"), - ), - migrations.AlterField( - model_name="member", - name="sepa_consent", - field=models.DateTimeField(null=True, verbose_name="SEPA Consent"), - ), - migrations.AlterField( - model_name="member", - name="withdrawal_consent", - field=models.DateTimeField( - null=True, verbose_name="Right of withdrawal consent" - ), - ), - ] diff --git a/tapir/wirgarten/migrations/0035_receivedcoopshareslogentry.py b/tapir/wirgarten/migrations/0035_receivedcoopshareslogentry.py deleted file mode 100644 index 63f55a33..00000000 --- a/tapir/wirgarten/migrations/0035_receivedcoopshareslogentry.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 3.2.16 on 2022-12-01 11:06 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0034_auto_20221201_1139"), - ] - - operations = [ - migrations.CreateModel( - name="ReceivedCoopSharesLogEntry", - fields=[ - ( - "transfercoopshareslogentry_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wirgarten.transfercoopshareslogentry", - ), - ), - ], - bases=("wirgarten.transfercoopshareslogentry",), - ), - ] diff --git a/tapir/wirgarten/migrations/0037_shareownership_created_at.py b/tapir/wirgarten/migrations/0037_shareownership_created_at.py deleted file mode 100644 index 403a147e..00000000 --- a/tapir/wirgarten/migrations/0037_shareownership_created_at.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-12-03 10:31 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0036_waitinglistentry"), - ] - - operations = [ - migrations.AddField( - model_name="shareownership", - name="created_at", - field=models.DateTimeField( - auto_now_add=True, default=django.utils.timezone.now - ), - preserve_default=False, - ), - ] diff --git a/tapir/wirgarten/migrations/0038_subscription_created_at.py b/tapir/wirgarten/migrations/0038_subscription_created_at.py deleted file mode 100644 index af8b3284..00000000 --- a/tapir/wirgarten/migrations/0038_subscription_created_at.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-12-06 12:38 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0037_shareownership_created_at"), - ] - - operations = [ - migrations.AddField( - model_name="subscription", - name="created_at", - field=models.DateTimeField( - auto_now_add=True, default=django.utils.timezone.now - ), - preserve_default=False, - ), - ] diff --git a/tapir/wirgarten/migrations/0039_member_created_at.py b/tapir/wirgarten/migrations/0039_member_created_at.py deleted file mode 100644 index 7b0502d5..00000000 --- a/tapir/wirgarten/migrations/0039_member_created_at.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.16 on 2022-12-27 14:24 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0038_subscription_created_at"), - ] - - operations = [ - migrations.AddField( - model_name="member", - name="created_at", - field=models.DateTimeField( - auto_now_add=True, default=django.utils.timezone.now - ), - preserve_default=False, - ), - ] diff --git a/tapir/wirgarten/migrations/0040_subscription_wirgarten_s_period__f679c5_idx.py b/tapir/wirgarten/migrations/0040_subscription_wirgarten_s_period__f679c5_idx.py deleted file mode 100644 index cf771c24..00000000 --- a/tapir/wirgarten/migrations/0040_subscription_wirgarten_s_period__f679c5_idx.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.2.16 on 2023-01-02 17:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("wirgarten", "0039_member_created_at"), - ] - - operations = [ - migrations.AddIndex( - model_name="subscription", - index=models.Index( - fields=["period_id", "created_at"], - name="wirgarten_s_period__f679c5_idx", - ), - ), - ] diff --git a/tapir/wirgarten/parameters.py b/tapir/wirgarten/parameters.py index 4800534e..1b891ca8 100644 --- a/tapir/wirgarten/parameters.py +++ b/tapir/wirgarten/parameters.py @@ -1,5 +1,4 @@ -from importlib.resources import _ - +from django.utils.translation import gettext_lazy as _ from django.core.validators import ( MinValueValidator, MaxValueValidator, @@ -8,15 +7,6 @@ ) from localflavor.generic.validators import IBANValidator, BICValidator -from tapir.configuration.models import ( - TapirParameterDatatype, - TapirParameterDefinitionImporter, -) -from tapir.configuration.parameter import parameter_definition, ParameterMeta -from tapir.wirgarten.constants import ProductTypes -from tapir.wirgarten.models import ProductType -from tapir.wirgarten.validators import validate_format_string, validate_html - OPTIONS_WEEKDAYS = [ (0, _("Montag")), (1, _("Dienstag")), @@ -102,8 +92,19 @@ class Parameter: ) +from tapir.configuration.models import ( + TapirParameterDatatype, + TapirParameterDefinitionImporter, +) + + class ParameterDefinitions(TapirParameterDefinitionImporter): def import_definitions(self): + from tapir.configuration.parameter import parameter_definition, ParameterMeta + from tapir.wirgarten.constants import ProductTypes + from tapir.wirgarten.models import ProductType + from tapir.wirgarten.validators import validate_format_string, validate_html + parameter_definition( key=Parameter.SITE_NAME, label="Standort Name", diff --git a/tapir/wirgarten/service/file_export.py b/tapir/wirgarten/service/file_export.py index 2efc5071..b0684bf1 100644 --- a/tapir/wirgarten/service/file_export.py +++ b/tapir/wirgarten/service/file_export.py @@ -1,5 +1,5 @@ import csv -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django.core.mail import EmailMultiAlternatives diff --git a/tapir/wirgarten/service/member.py b/tapir/wirgarten/service/member.py index 830bf583..3f41694a 100644 --- a/tapir/wirgarten/service/member.py +++ b/tapir/wirgarten/service/member.py @@ -6,7 +6,7 @@ from django.core.mail import EmailMultiAlternatives from tapir import settings -from tapir.accounts.models import TapirUser, LdapPerson +from tapir.accounts.models import TapirUser from tapir.configuration.parameter import get_parameter_value from tapir.wirgarten.models import ( ShareOwnership, @@ -91,7 +91,7 @@ def transfer_coop_shares( ).save() -def create_mandate_ref(member: int | str | Member, coop_shares: bool): +def create_mandate_ref(member: str | Member, coop_shares: bool): """ Generates and persists a new mandate reference for a member. @@ -106,18 +106,17 @@ def create_mandate_ref(member: int | str | Member, coop_shares: bool): ) -def resolve_member_id(member: int | str | Member): - return member.id if type(member) is Member else member +def resolve_member_id(member: str | Member | TapirUser) -> str: + return member.id if type(member) is not str and member.id else member def get_or_create_mandate_ref( - member: int | str | Member, coop_shares: bool = False + member: str | Member, coop_shares: bool = False ) -> MandateReference: - member_id = member.id if type(member) is Member else member - if coop_shares: raise NotImplementedError("Coop share mandate references can not be reused.") + member_id = resolve_member_id(member) mandate_ref = False for row in ( get_future_subscriptions() @@ -134,32 +133,6 @@ def get_or_create_mandate_ref( return mandate_ref -@transaction.atomic -def create_member(member: Member): - """ - Persists the given member instance together with the necessary Ldap objects. - - :param member: the new member instance - """ - - if not member.username: - # FIXME: this leads to duplicate usernames (see #8) - member.username = member.first_name.lower() + "." + member.last_name.lower() - - if member.has_ldap(): - ldap_user = member.get_ldap() - else: - ldap_user = LdapPerson(uid=member.username) - - ldap_user.sn = member.last_name or member.username - ldap_user.cn = member.get_full_name() or member.username - ldap_user.mail = member.email - ldap_user.save() - - member.save() - return member - - def get_next_contract_start_date(ref_date=date.today()): """ Gets the next start date for a contract. Usually the first of the next month. @@ -230,7 +203,7 @@ def create_wait_list_entry( def get_next_trial_end_date(reference_date: date = date.today()): - return reference_date + relativedelta(day=1, months=1) + relativedelta(days=-1) + return reference_date + relativedelta(day=1, months=2) + relativedelta(days=-1) def get_subscriptions_in_trial_period(member: int | str | Member): diff --git a/tapir/wirgarten/service/payment.py b/tapir/wirgarten/service/payment.py index 991e009f..48667e14 100644 --- a/tapir/wirgarten/service/payment.py +++ b/tapir/wirgarten/service/payment.py @@ -21,7 +21,7 @@ SUBS_SUFFIX = "/PROD" -def generate_mandate_ref(member_id: int | str, coop_shares: bool): +def generate_mandate_ref(member_id: str, coop_shares: bool): """ Generates a new mandate reference string. diff --git a/tapir/wirgarten/service/products.py b/tapir/wirgarten/service/products.py index ca036ab5..2c89094e 100644 --- a/tapir/wirgarten/service/products.py +++ b/tapir/wirgarten/service/products.py @@ -66,7 +66,7 @@ def product_type_order_by(id_field: str = "id", name_field: str = "name"): ), name_field, ] - except Exception: + except BaseException: return [name_field] diff --git a/tapir/wirgarten/static/wirgarten/css/loading-spinner.css b/tapir/wirgarten/static/wirgarten/css/loading-spinner.css index 4a42d9ff..0c79011d 100644 --- a/tapir/wirgarten/static/wirgarten/css/loading-spinner.css +++ b/tapir/wirgarten/static/wirgarten/css/loading-spinner.css @@ -2,8 +2,8 @@ color: official; display: inline-block; position: relative; - width: 80px; - height: 80px; + width: 100px; + height: 100px; } .lds-spinner div { transform-origin: 40px 40px; @@ -125,3 +125,16 @@ opacity: 0; } } + +#loading-screen { + transition: opacity 0.25s; + opacity: 1; + display: flex; + justify-content: center; + width: 100%; + height: 100%; + position: absolute; + margin-top: 20%; + z-index: 1100; + background-color: #FFFFFF; +} \ No newline at end of file diff --git a/tapir/wirgarten/templates/wirgarten/member/member_detail.html b/tapir/wirgarten/templates/wirgarten/member/member_detail.html index 0f8e76e4..dbf4b6b0 100644 --- a/tapir/wirgarten/templates/wirgarten/member/member_detail.html +++ b/tapir/wirgarten/templates/wirgarten/member/member_detail.html @@ -19,16 +19,22 @@ {% include 'wirgarten/member/member_detail_alert.html' with renewal_status=renewal_status contract_end_date=contract_end_date member=object next_period=next_period show_trial_period_notice=show_trial_period_notice next_trial_end_date=next_trial_end_date show_renewal_warning=show_renewal_warning %} + {% if email_change_request %} +
+

Bitte bestätige deine neue Email Adresse

+

Wir haben dir eine Mail geschickt an {{email_change_request.new_email}}.
+ Bitte klicke den Link um deine Änderung wirksam zu machen.

+
+ {% endif %} +
{% translate "Persönliche Daten" %} - {% if perms.accounts.manage %} - {% endif %}
@@ -37,7 +43,15 @@
{% translate "Email" %}:
+ {% if email_change_request %} +
+ {{ object.email }}
+ {{ email_change_request.new_email }}   Nicht verifiziert +
+ {% else %}
{{ object.email }}
+ {% endif %}
{% translate "Telefon" %}:
@@ -70,14 +84,13 @@
-
-
- {% if object.pk == request.user.pk %} - +
+
vpn_key{% translate "Passwort ändern" %} - {% endif %} -
+ {% endif %}
@@ -123,7 +136,8 @@
{{ val.0.start_date|format_date }} - {{ val.0.end_date|format_date }}
{% for sub in val %} - + {{ sub.quantity }}  ×  {{ sub.product.name }}  {% if sub.solidarity_price is not None and sub.solidarity_price != 0 %} ({% if sub.solidarity_price > 0%}+{% endif %} {% widthratio sub.solidarity_price 1 100 %}%) @@ -193,12 +207,10 @@
{% translate "Abholung" %} - {% if perms.accounts.manage %} - {% endif %}
@@ -231,12 +243,10 @@
{% translate "Bankverbindung" %} - {% if perms.accounts.manage %} - {% endif %}
@@ -303,6 +313,10 @@
FormModal.load('{% url 'wirgarten:member_add_harvest_shares' object.pk %}', {% if available_product_types|get_value:"Ernteanteile"|default:False %} 'Ernteanteile dazubuchen' {% else %} 'Warteliste', 'Aktuell sind leider keine Ernteanteile verfügbar. Wenn du möchtest informieren wir dich sobald du wieder neue Anteile zeichnen kannst.', 'warning' {% endif %}); } {% endif %} + + + + {% endblock %} \ No newline at end of file diff --git a/tapir/wirgarten/templates/wirgarten/member/member_detail_alert.html b/tapir/wirgarten/templates/wirgarten/member/member_detail_alert.html index 5de8d6dd..8c071bfc 100644 --- a/tapir/wirgarten/templates/wirgarten/member/member_detail_alert.html +++ b/tapir/wirgarten/templates/wirgarten/member/member_detail_alert.html @@ -4,10 +4,24 @@ + + @@ -97,10 +111,15 @@

Ernteanteil verlängern

\ No newline at end of file diff --git a/tapir/wirgarten/templates/wirgarten/pickup_location/pickup_location_config.html b/tapir/wirgarten/templates/wirgarten/pickup_location/pickup_location_config.html index 0cf819a6..f0d80631 100644 --- a/tapir/wirgarten/templates/wirgarten/pickup_location/pickup_location_config.html +++ b/tapir/wirgarten/templates/wirgarten/pickup_location/pickup_location_config.html @@ -15,10 +15,12 @@

{{ pickup_locations|length }} Abholorte

+ {% if perms.coop.manage %} {% include 'wirgarten/generic/action-button.html' with onclick='handleAddPickupLocation()' title='Abholort hinzufügen' type='success' icon='add_location_alt' %} {% include 'wirgarten/generic/action-button.html' with onclick='handleEdit()' id='edit-location' title='Abholort bearbeiten' disabled=True type='primary' icon='edit' %}
{% include 'wirgarten/generic/action-button.html' with onclick='handleDelete()' id='delete-location' title='Abholort löschen' disabled=True type='danger' icon='delete'%} + {% endif %}
diff --git a/tapir/wirgarten/tests.py b/tapir/wirgarten/tests.py index 738a6178..dc7c0f4f 100644 --- a/tapir/wirgarten/tests.py +++ b/tapir/wirgarten/tests.py @@ -1,17 +1,11 @@ from django.test import TestCase # Create your tests here. -from tapir.configuration.models import TapirParameterDefinitionImporter from tapir.wirgarten.service.payment import ( generate_mandate_ref, MANDATE_REF_LENGTH, is_mandate_ref_for_coop_shares, ) -from tapir.wirgarten.tasks import ( - export_pick_list_csv, - export_supplier_list_csv, - export_sepa_payments, -) class MandateReferenceTestCase(TestCase): diff --git a/tapir/wirgarten/urls.py b/tapir/wirgarten/urls.py index 931f044a..917cf5bf 100644 --- a/tapir/wirgarten/urls.py +++ b/tapir/wirgarten/urls.py @@ -1,4 +1,5 @@ from django.urls import path +from django.views import generic from tapir.wirgarten.views import ( RegistrationWizardView, @@ -153,69 +154,69 @@ path("members", MemberListView.as_view(), name="member_list"), path("members/create", get_member_personal_data_create_form, name="member_create"), path( - "members//edit", get_member_personal_data_edit_form, name="member_edit" + "members//edit", get_member_personal_data_edit_form, name="member_edit" ), path( - "members//editpaymentdetails", + "members//editpaymentdetails", get_member_payment_data_edit_form, name="member_edit_payment_details", ), path( - "members//editpickuplocation", + "members//editpickuplocation", get_pickup_location_choice_form, name="member_pickup_location_choice", ), path( - "members//cancelcontract", + "members//cancelcontract", cancel_contract_at_period_end, name="member_cancel_contract", ), path( - "members//renewcontract", + "members//renewcontract", renew_contract_same_conditions, name="member_renew_same_conditions", ), path( - "members//addharvestshares", + "members//addharvestshares", get_add_harvest_shares_form, name="member_add_harvest_shares", ), path( - "members//addchickenshares", + "members//addchickenshares", get_add_chicken_shares_form, name="member_add_chicken_shares", ), path( - "members//addbestellcoop", + "members//addbestellcoop", get_add_bestellcoop_form, name="member_add_bestellcoop", ), path( - "members//addcoopshares", + "members//addcoopshares", get_add_coop_shares_form, name="member_add_coop_shares", ), path( - "members//canceltrial", + "members//canceltrial", get_cancel_trial_form, name="member_cancel_trial", ), - path("members/", MemberDetailView.as_view(), name="member_detail"), + path("members/", MemberDetailView.as_view(), name="member_detail"), path( - "members//coopsharestransfer", + "members//coopsharestransfer", get_coop_share_transfer_form, name="member_coopshare_transfer", ), path("contracts", SubscriptionListView.as_view(), name="subscription_list"), - path("payments/", MemberPaymentsView.as_view(), name="member_payments"), + path("payments/", MemberPaymentsView.as_view(), name="member_payments"), path( - "payments//edit//", + "payments//edit//", get_payment_amount_edit_form, name="member_payments_edit", ), path("sepa", PaymentTransactionListView.as_view(), name="payment_transactions"), path( - "deliveries/", MemberDeliveriesView.as_view(), name="member_deliveries" + "deliveries/", MemberDeliveriesView.as_view(), name="member_deliveries" ), ] app_name = "wirgarten" diff --git a/tapir/wirgarten/validators.py b/tapir/wirgarten/validators.py index 819ba848..44a1aae8 100644 --- a/tapir/wirgarten/validators.py +++ b/tapir/wirgarten/validators.py @@ -1,7 +1,7 @@ import io import re from datetime import date -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError diff --git a/tapir/wirgarten/views/default_redirect.py b/tapir/wirgarten/views/default_redirect.py index bcdfb936..6cfa504d 100644 --- a/tapir/wirgarten/views/default_redirect.py +++ b/tapir/wirgarten/views/default_redirect.py @@ -1,27 +1,56 @@ -from django.http import HttpResponseRedirect +from django.utils.translation import gettext_lazy as _ + +from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse_lazy from django.views.decorators.http import require_http_methods from tapir.wirgarten.constants import Permission -from tapir.wirgarten.models import Member + + +class RequestUserType: + ANONYMOUS = 0 + MEMBER = 1 + STAFF = 2 + + +def get_user_type(request) -> int: + # Not authenticated + if request.user is None or request.user.id is None: + return RequestUserType.ANONYMOUS + # User is Admin + elif request.user.has_perm(Permission.Coop.VIEW): + return RequestUserType.STAFF + # User is Member + else: + return RequestUserType.MEMBER + + +def handle_403(request, exception): + user_type = get_user_type(request) + if user_type == RequestUserType.ANONYMOUS: + return HttpResponseRedirect(reverse_lazy("login")) + if user_type == RequestUserType.MEMBER: + return HttpResponseRedirect( + reverse_lazy("wirgarten:member_detail", kwargs={"pk": request.user.id}) + ) + if user_type == RequestUserType.STAFF: + return HttpResponse( + _("Du bist nicht authorisiert diese Seite zu sehen."), status=403 + ) @require_http_methods(["GET"]) def wirgarten_redirect_view(request): - # Not logged in - if request.user.pk is None: - return HttpResponseRedirect(reverse_lazy("login") + "?next=/") + user_type = get_user_type(request) + if user_type == RequestUserType.ANONYMOUS: + return HttpResponseRedirect(reverse_lazy("login")) # User is Admin --> redirect to dashboard - elif request.user.has_perm(Permission.Coop.VIEW): + if user_type == RequestUserType.STAFF: return HttpResponseRedirect(reverse_lazy("wirgarten:admin_dashboard")) # User is Member --> redirect to member detail view - elif Member.objects.filter(pk=request.user.pk).exists(): + else: return HttpResponseRedirect( - reverse_lazy("wirgarten:member_detail", kwargs={"pk": request.user.pk}) + reverse_lazy("wirgarten:member_detail", kwargs={"pk": request.user.id}) ) - - # User is TapirUser but not Member --> redirect to accounts/me - else: - return HttpResponseRedirect(reverse_lazy("accounts:index")) diff --git a/tapir/wirgarten/views/member.py b/tapir/wirgarten/views/member.py index 9f9406cc..e3c4b3e2 100644 --- a/tapir/wirgarten/views/member.py +++ b/tapir/wirgarten/views/member.py @@ -1,14 +1,18 @@ +import base64 import itertools +import json import mimetypes from copy import copy -from datetime import date, datetime -from importlib.resources import _ +from datetime import date, datetime, timezone +from django.utils.translation import gettext_lazy as _ from dateutil.relativedelta import relativedelta +from django.core.mail import EmailMultiAlternatives from django.contrib.auth.decorators import permission_required, login_required from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied from django.db import transaction +from django.forms import CheckboxInput from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import render from django.urls import reverse_lazy @@ -25,8 +29,9 @@ BooleanFilter, ) from django_filters.views import FilterView -from django.forms import CheckboxInput +from tapir import settings +from tapir.accounts.models import EmailChangeRequest, TapirUser from tapir.configuration.parameter import get_parameter_value from tapir.wirgarten.constants import ( WEEKLY, @@ -74,7 +79,6 @@ from tapir.wirgarten.service.file_export import begin_csv_string from tapir.wirgarten.service.member import ( transfer_coop_shares, - create_member, create_wait_list_entry, buy_cooperative_shares, get_next_trial_end_date, @@ -97,6 +101,7 @@ is_bestellcoop_available, get_next_growing_period, product_type_order_by, + get_active_subscriptions, ) from tapir.wirgarten.utils import format_date from tapir.wirgarten.views.mixin import PermissionOrSelfRequiredMixin @@ -241,7 +246,7 @@ def get_context_data(self, **kwargs): else: break - next_payments = generate_future_payments(self.object.pk, prev_payments, 1) + next_payments = generate_future_payments(self.object.id, prev_payments, 1) if len(next_payments) > 0: if "next_payment" not in context or ( @@ -252,9 +257,17 @@ def get_context_data(self, **kwargs): self.add_renewal_notice_context(context, next_month, today) context["next_trial_end_date"] = get_next_trial_end_date() - if get_subscriptions_in_trial_period(self.object.pk).exists(): + if get_subscriptions_in_trial_period(self.object.id).exists(): context["show_trial_period_notice"] = True + email_change_requests = EmailChangeRequest.objects.filter( + user_id=self.object.id + ) + if email_change_requests.exists(): + context["email_change_request"] = { + "new_email": email_change_requests[0].new_email + } + return context def add_renewal_notice_context(self, context, next_month, today): @@ -264,6 +277,8 @@ def add_renewal_notice_context(self, context, next_month, today): - add_shares_disallowed = less than 1 month - renewal_status = "unknown", "renewed", "cancelled" """ + if not get_active_subscriptions().filter(member_id=self.object.id).exists(): + return next_growing_period = get_next_growing_period(today) if ( @@ -299,7 +314,7 @@ def add_renewal_notice_context(self, context, next_month, today): context["renewal_status"] = "cancelled" elif ( get_future_subscriptions(next_growing_period.start_date) - .filter(member_id=self.object.pk) + .filter(member_id=self.object.id) .exists() ): context["renewal_status"] = "renewed" @@ -710,7 +725,7 @@ def get_member_personal_data_edit_form(request, **kwargs): check_permission_or_self(pk, request) def update_member( - member_id, + instance, first_name, last_name, email, @@ -721,16 +736,17 @@ def update_member( city, birthdate, ): - instance = Member.objects.get(pk=member_id) instance.first_name = first_name instance.last_name = last_name instance.email = email + instance.username = email instance.phone_number = phone_number instance.street = street instance.street_2 = street_2 instance.postcode = postcode instance.city = city instance.birthdate = birthdate + instance.save() return instance @@ -738,18 +754,19 @@ def update_member( request=request, form=PersonalDataForm, instance=Member.objects.get(pk=pk), - handler=lambda x: update_member( - member_id=pk, - first_name=x.cleaned_data["first_name"], - last_name=x.cleaned_data["last_name"], - email=x.cleaned_data["email"], - phone_number=x.cleaned_data["phone_number"], - street=x.cleaned_data["street"], - street_2=x.cleaned_data["street_2"], - postcode=x.cleaned_data["postcode"], - city=x.cleaned_data["city"], - birthdate=x.cleaned_data["birthdate"], - ), + handler=lambda x: x.instance.save(), + # update_member( + # instance=x.instance, + # first_name=x.cleaned_data["first_name"], + # last_name=x.cleaned_data["last_name"], + # email=x.cleaned_data["email"], + # phone_number=x.cleaned_data["phone_number"], + # street=x.cleaned_data["street"], + # street_2=x.cleaned_data["street_2"], + # postcode=x.cleaned_data["postcode"], + # city=x.cleaned_data["city"], + # birthdate=x.cleaned_data["birthdate"], + # ), redirect_url_resolver=lambda _: member_detail_url(pk), **kwargs, ) @@ -832,7 +849,7 @@ def get_member_personal_data_create_form(request, **kwargs): return get_form_modal( request=request, form=PersonalDataForm, - handler=lambda x: create_member(x.instance), + handler=lambda x: x.instance.save(), redirect_url_resolver=lambda x: reverse_lazy("wirgarten:member_list"), **kwargs, ) @@ -1144,3 +1161,53 @@ def get_context_data(self, **kwargs): "next_trial_end_date" ] + relativedelta(day=1) return context + + +EMAIL_CHANGE_LINK_VALIDITY_MINUTES = 4 * 60 + + +@transaction.atomic +def change_email(request, **kwargs): + data = json.loads(base64.b64decode(kwargs["token"])) + user_id = data["user"] + new_email = data["new_email"] + matching_change_request = EmailChangeRequest.objects.filter( + new_email=new_email, secret=data["secret"], user_id=user_id + ).order_by("-created_at") + + link_validity = relativedelta(minutes=EMAIL_CHANGE_LINK_VALIDITY_MINUTES) + now = datetime.now(tz=timezone.utc) + if matching_change_request.exists() and now < ( + matching_change_request[0].created_at + link_validity + ): + # token is valid -> actually change email + user = TapirUser.objects.get(id=user_id) + orig_email = user.email + user.change_email(new_email) + + # delete other change requests for this user + EmailChangeRequest.objects.filter(user_id=user_id).delete() + # delete expired change requests + EmailChangeRequest.objects.filter(created_at__lte=now - link_validity).delete() + + # send confirmation to old email address + email = EmailMultiAlternatives( + subject=_("Deine Email Adresse wurde geändert"), + body=_( + f"Hallo {user.first_name},

" + f"deine Email Adresse wurde erfolgreich zu {new_email} geändert.
" + f"""Falls du das nicht warst, ändere sofort dein Passwort im Mitgliederbereich und kontaktiere uns indem du einfach auf diese Mail antwortest.""" + f"

Grüße, dein WirGarten Team" + ), + to=[orig_email], + from_email=get_parameter_value(Parameter.SITE_ADMIN_EMAIL), + ) + email.content_subtype = "html" + email.send() + + return HttpResponseRedirect( + reverse_lazy("wirgarten:member_detail", kwargs={"pk": user.id}) + + "?email_changed=true" + ) + + return HttpResponseRedirect(reverse_lazy("link_expired")) diff --git a/tapir/wirgarten/views/mixin.py b/tapir/wirgarten/views/mixin.py index 5546e1ed..410f5a30 100644 --- a/tapir/wirgarten/views/mixin.py +++ b/tapir/wirgarten/views/mixin.py @@ -6,7 +6,7 @@ def get_permission_required(self): return super(PermissionOrSelfRequiredMixin, self).get_permission_required() def has_permission(self): - return ( + return self.request.user is not None and ( self.request.user.pk == self.get_user_pk() or super(PermissionOrSelfRequiredMixin, self).has_permission() ) diff --git a/tapir/wirgarten/views/modal.py b/tapir/wirgarten/views/modal.py index d51931dd..fe18190d 100644 --- a/tapir/wirgarten/views/modal.py +++ b/tapir/wirgarten/views/modal.py @@ -14,7 +14,11 @@ def get_form_modal( # if this is a POST request we need to process the modal data if request.method == "POST": # create a modal instance and populate it with data from the request: - form = form(request.POST, **kwargs) + form = ( + form(request.POST, instance=instance, **kwargs) + if instance + else form(request.POST, **kwargs) + ) # check whether it's valid: if form.is_valid(): @@ -29,7 +33,7 @@ def get_form_modal( {"url": redirect_url}, ) else: - print("Form not valid!") + print("Form not valid! ", form.errors) # if a GET (or any other method) we'll create a blank modal else: diff --git a/tapir/wirgarten/views/registration_view.py b/tapir/wirgarten/views/registration_view.py index 7753b58b..27a60fe1 100644 --- a/tapir/wirgarten/views/registration_view.py +++ b/tapir/wirgarten/views/registration_view.py @@ -1,4 +1,4 @@ -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django.db import transaction from django.http import HttpResponseRedirect @@ -27,7 +27,6 @@ from tapir.wirgarten.parameters import Parameter from tapir.wirgarten.service.member import ( create_mandate_ref, - create_member, buy_cooperative_shares, get_next_contract_start_date, ) @@ -107,7 +106,8 @@ def save_member(form_dict): member.withdrawal_consent = now member.privacy_consent = now - return create_member(member) + member.save() + return member def init_conditions(): @@ -141,6 +141,7 @@ def init_conditions(): except Exception as e: print("Could not init registration wizard conditions: ", e) + @method_decorator(xframe_options_exempt, name="dispatch") @method_decorator(xframe_options_exempt, name="post") class RegistrationWizardView(CookieWizardView): @@ -170,7 +171,7 @@ def get_template_names(self): elif self.steps.current == STEP_SUMMARY: return ["wirgarten/registration/steps/summary.html"] return ["wirgarten/registration/registration_form.html"] - + # gather data from dependent forms def get_form_initial(self, step=None): initial = self.initial_dict