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..299baa2e 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.9.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 deleted file mode 100644 index a8a0650d..00000000 --- a/tapir/accounts/forms.py +++ /dev/null @@ -1,54 +0,0 @@ -from django import forms -from django.contrib.auth import forms as auth_forms -from django.forms import TextInput - -from tapir.accounts.models import TapirUser -from tapir.utils.forms import DateInput, TapirPhoneNumberField - - -class TapirUserForm(forms.ModelForm): - phone_number = TapirPhoneNumberField(required=False) - - class Meta: - model = TapirUser - fields = [ - "first_name", - "last_name", - "username", - "phone_number", - "email", - "birthdate", - "street", - "street_2", - "postcode", - "city", - "preferred_language", - ] - widgets = { - "birthdate": DateInput(), - "username": TextInput(attrs={"readonly": True}), - } - - -class PasswordResetForm(auth_forms.PasswordResetForm): - def get_users(self, email): - """Given an email, return matching user(s) who should receive a reset. - This allows subclasses to more easily customize the default policies - that prevent inactive users and users with unusable passwords from - resetting their password. - """ - email_field_name = auth_forms.UserModel.get_email_field_name() - active_users = auth_forms.UserModel._default_manager.filter( - **{ - "%s__iexact" % email_field_name: email, - "is_active": True, - } - ) - return ( - u - for u in active_users - # Users with unusable passwords in the DB should be able to reset their passwords, the new password will be - # set in the LDAP instead. See models.LdapUser - # if u.has_usable_password() and - if auth_forms._unicode_ci_compare(email, getattr(u, email_field_name)) - ) diff --git a/tapir/accounts/management/commands/create_admin.py b/tapir/accounts/management/commands/create_admin.py new file mode 100644 index 00000000..e627e8cd --- /dev/null +++ b/tapir/accounts/management/commands/create_admin.py @@ -0,0 +1,48 @@ +import sys + +from django.core.management import BaseCommand + +from tapir.accounts.models import TapirUser + + +class Command(BaseCommand): + help = "Create the initial admin account" + + def handle(self, *args, **options): + if TapirUser.objects.filter(is_superuser=True).exists(): + sys.stderr.write( + "There is already an admin account in the system, this command is disabled.\n" + ) + return + + admin = TapirUser( + first_name=options["first_name"], + last_name=options["last_name"], + email=options["email"], + is_staff=True, + is_superuser=True, + ) + admin.save(initial_password=options["password"]) + + def add_arguments(self, parser): + parser.add_argument( + "--first-name", + help="First name", + type=str, + required=True, + ) + parser.add_argument( + "--last-name", + help="Last name", + type=str, + required=True, + ) + parser.add_argument( + "--email", + help="Email address", + type=str, + required=True, + ) + parser.add_argument( + "--password", help="Initial Password", type=str, required=True + ) diff --git a/tapir/accounts/middleware.py b/tapir/accounts/middleware.py index 8f60ad62..626c62d5 100644 --- a/tapir/accounts/middleware.py +++ b/tapir/accounts/middleware.py @@ -1,22 +1,81 @@ -import datetime +import logging +import time 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}) + if data["exp"] < int(time.time()): + return self.auth_failed("Token expired on: ", data["exp"]) + 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..2162a1e1 100644 --- a/tapir/accounts/models.py +++ b/tapir/accounts/models.py @@ -1,219 +1,198 @@ +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) +class KeycloakUserManager(models.Manager.from_queryset(KeycloakUserQuerySet)): + def normalize_email(self, email: str) -> str: + return email.strip() - def save( - self, force_insert=False, force_update=False, using=None, update_fields=None - ): - - 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, + } + print("Creating Keycloak user: ", data) + + initial_password = kwargs.pop("initial_password", None) + if initial_password: + data["credentials"] = [{"value": initial_password, "type": "password"}] + data["emailVerified"] = True + else: + data["requiredActions"] = ["VERIFY_EMAIL", "UPDATE_PASSWORD"] + + if self.is_superuser: + group = kk.get_group_by_path(path="/superuser") + if group: + data["groups"] = ["superuser"] + + 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"], + 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, + }, ) - 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"], - ) - 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=settings.EMAIL_HOST_SENDER, + ) + 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 +208,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 +229,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 +250,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 +288,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/email/welcome_email.default.html b/tapir/accounts/templates/accounts/email/welcome_email.default.html deleted file mode 100644 index 2c0a12bb..00000000 --- a/tapir/accounts/templates/accounts/email/welcome_email.default.html +++ /dev/null @@ -1,41 +0,0 @@ -{% load i18n %} - - - - - - - -{% blocktranslate with user_display_name=tapir_user.get_display_name %} -

Dear {{ user_display_name }},

-

- we've just created an account for you in our {{ coop_name }} member system. Here you can view your upcoming shifts, - book an additional shift if you'd like or mark your shift as “looking for a stand-in” (for example when you go - on vacation). -

-{% endblocktranslate %} - -

- {% if tapir_user.shift_user_data.attendance_mode == "regular" %} - {% if tapir_user.shift_attendance_templates.first %} - {% blocktranslate with slot_display_name=tapir_user.shift_attendance_templates.first.slot_template.get_display_name %} - Your regular ABCD-shift is: {{ slot_display_name }}. You will receive a reminder email in advance of your first shift. - {% endblocktranslate %} - {% endif %} - {% else %} - {% blocktranslate %} - Flying members: Please keep in mind that you must have at least one shift “banked” for each shift cycle. - {% endblocktranslate %} - {% endif %} -

- -{% url "password_reset_confirm" uidb64=uid token=token as password_reset_confirm_url %} -{% url "password_reset" as password_reset_request_url %} -

- {% blocktranslate with username=tapir_user.username %} -

Your username is {{ username }}. In order to log in to your account, you first have to set a password: Click here to set your password

-

This link is only valid for a few weeks. Should it expire, you can get a new one here : Click here to get a new link

- {% endblocktranslate %} -

- - diff --git a/tapir/accounts/templates/accounts/email/welcome_email.html b/tapir/accounts/templates/accounts/email/welcome_email.html deleted file mode 100644 index f932d20a..00000000 --- a/tapir/accounts/templates/accounts/email/welcome_email.html +++ /dev/null @@ -1,47 +0,0 @@ -{% load i18n %} - - - - - - {% translate 'Welcome' %} - - -{% blocktranslate with user_display_name=tapir_user.get_display_name %} -

Dear {{ user_display_name }},

-

we just created an account for you in our SuperCoop member system. Here you can view your upcoming shifts, book an additional shift if you'd like or mark your shift as “looking for a stand-in” (for example when you go on vacation).
- Please see the Member Manual section III for more information on the Stand-in System.

-{% endblocktranslate %} - -

- {% if tapir_user.shift_user_data.attendance_mode == "regular" %} - {% if tapir_user.shift_attendance_templates.first %} - {% blocktranslate with slot_display_name=tapir_user.shift_attendance_templates.first.slot_template.get_display_name %} - Your regular ABCD-shift is: {{ slot_display_name }}. You will receive a reminder email in advance of your first shift. - {% endblocktranslate %} - {% endif %} - {% else %} - {% blocktranslate %} - Flying members: Please keep in mind that you must have at least one shift “banked” for each shift cycle. For more information please see the Member Manual . - {% endblocktranslate %} - {% endif %} -

- -{% url "password_reset_confirm" uidb64=uid token=token as password_reset_confirm_url %} -{% url "password_reset" as password_reset_request_url %} -

- {% blocktranslate with username=tapir_user.username %} -

Your username is *{{ username }}* . In order to login to your account, you first have to set a password : Click here to set your password

-

This link is only valid for a few weeks. Should it expire, you can get a new one here : Click here to get a new link

-

You can also login to the SuperCoop wiki with that account.

-

Alternatively, as for any other question, you can always contact the Member Office

- {% endblocktranslate %} -

-

- {% blocktranslate %} - Cooperative regards,
- Your SuperCoop Berlin Member Office team. - {% endblocktranslate %} -

- - diff --git a/tapir/accounts/templates/accounts/email/welcome_email_subject.default.html b/tapir/accounts/templates/accounts/email/welcome_email_subject.default.html deleted file mode 100644 index 15f830fa..00000000 --- a/tapir/accounts/templates/accounts/email/welcome_email_subject.default.html +++ /dev/null @@ -1,2 +0,0 @@ -{% load i18n %} -{% translate "Your account in the member system" %} \ 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..e772284b --- /dev/null +++ b/tapir/accounts/templates/accounts/keycloak_script.html @@ -0,0 +1,101 @@ +{% load keycloak %} +{% keycloak_config as conf %} + + + \ 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..e85e2d98 --- /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/accounts/user_detail.html b/tapir/accounts/templates/accounts/user_detail.html deleted file mode 100644 index 2e895190..00000000 --- a/tapir/accounts/templates/accounts/user_detail.html +++ /dev/null @@ -1,106 +0,0 @@ -{% extends "accounts/base.html" %} - -{% load django_bootstrap5 %} -{% load tapir_static %} -{% load i18n %} -{% load log %} -{% load accounts %} - -{% block head %} - {{ block.super }} - -{% endblock %} - -{% block content %} -
-
-
-
- {% translate "Personal Data" %} - {% if perms.accounts.manage %} - - edit{% translate 'Edit' %} - - {% endif %} -
-
-
-
{% translate "Name" %}:
-
{{ object.get_display_name }}
-
-
-
{% translate "Username" %}:
-
{{ object.username }}
-
-
-
{% translate "Email" %}:
-
{{ object.email }}
-
-
-
{% translate "Phone number" %}:
-
- {% if object.phone_number %} - {{ object.phone_number|format_phone_number }} - {% else %} - {% translate "Missing" %} - {% endif %} -
-
-
-
{% translate "Birthdate" %}:
-
- {% if object.birthdate %} - {{ object.birthdate|date:"d.m.Y" }} - {% else %} - {% translate "Missing" %} - {% endif %} -
-
-
-
{% translate "Address" %}:
-
- {% if object.street and object.city %} - {{ object.get_display_address }} - {% else %} - {% translate "Missing" %} - {% endif %} -
-
-
-
{% translate "Preferred Language" %}:
-
{{ object.get_preferred_language_display }}
-
- {% if perms.accounts.manage %} -
-
{% translate "Permissions" %}:
-
{{ object.get_permissions_display }}
-
- {% endif %} - -
-
- {% if perms.accounts.manage %} -
- {% csrf_token %} - -
- {% endif %} - {% if object.pk == request.user.pk %} - vpn_key{% translate "Change Password" %} - {% endif %} -
-
-
-
-
- -
- {% user_log_entry_list object %} -
-
-{% endblock %} - diff --git a/tapir/accounts/templates/accounts/user_form.html b/tapir/accounts/templates/accounts/user_form.html deleted file mode 100644 index 76133604..00000000 --- a/tapir/accounts/templates/accounts/user_form.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends "accounts/base.html" %} - -{% load django_bootstrap5 %} -{% load i18n %} - -{% block content %} - - - -
-
-
-
- {% csrf_token %} - {% bootstrap_form form %} -
- -
-
-
-
-
-{% endblock %} diff --git a/tapir/accounts/templates/registration/email/password_reset_email.html b/tapir/accounts/templates/registration/email/password_reset_email.html deleted file mode 100644 index e7f24839..00000000 --- a/tapir/accounts/templates/registration/email/password_reset_email.html +++ /dev/null @@ -1,25 +0,0 @@ -{% load i18n %} - - - - - - {% translate 'Password reset' %} - - - -{% url 'password_reset_confirm' uidb64=uid token=token as password_reset_url %} - -{% language user.preferred_language %} - {% blocktranslate with first_name=user.first_name username=user.username %} -

Hi {{ first_name }},

- -

- Someone asked for password reset for {{ email }}.
- Your username is {{ username }}
- Follow this link to reset your password: {{ protocol }}://{{ domain }}{{ password_reset_url }} -

- {% endblocktranslate %} -{% endlanguage %} - - \ No newline at end of file diff --git a/tapir/accounts/templates/registration/email/password_reset_subject.html b/tapir/accounts/templates/registration/email/password_reset_subject.html deleted file mode 100644 index 7e7506af..00000000 --- a/tapir/accounts/templates/registration/email/password_reset_subject.html +++ /dev/null @@ -1,4 +0,0 @@ -{% load i18n %} -{% blocktranslate %} - Reset your password -{% endblocktranslate %} \ No newline at end of file diff --git a/tapir/accounts/templates/registration/login.html b/tapir/accounts/templates/registration/login.html deleted file mode 100644 index 20246d8e..00000000 --- a/tapir/accounts/templates/registration/login.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends "core/base.html" %} - -{% load django_bootstrap5 %} -{% load tapir_static %} -{% load i18n %} - -{% block head %} - {{ block.super }} - -{% endblock %} - -{% block content %} -
- -
- - -{% endblock %} diff --git a/tapir/accounts/templates/registration/password_reset_complete.html b/tapir/accounts/templates/registration/password_reset_complete.html deleted file mode 100644 index c21f4b32..00000000 --- a/tapir/accounts/templates/registration/password_reset_complete.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "core/base.html" %} - -{% load django_bootstrap5 %} -{% load tapir_static %} -{% load i18n %} - -{% block content %} -
-

{% translate "Password set" %}

-

- {% translate "Your password has been set. You may go ahead and sign in now." %}
- {% translate "Go to login page" %} -

-
-{% endblock %} diff --git a/tapir/accounts/templates/registration/password_reset_confirm.html b/tapir/accounts/templates/registration/password_reset_confirm.html deleted file mode 100644 index c75d4b68..00000000 --- a/tapir/accounts/templates/registration/password_reset_confirm.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "core/base.html" %} - -{% load django_bootstrap5 %} -{% load tapir_static %} -{% load i18n %} - -{% block content %} - {% if validlink %} -
-
{% translate "Set a new password" %}
-
-

{% translate "Please enter a new password" %}

-
- {% csrf_token %} - {% bootstrap_form form %} - -
-
-
- {% else %} -

- {% translate "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %} -

- {% endif %} -{% endblock %} diff --git a/tapir/accounts/templates/registration/password_reset_done.html b/tapir/accounts/templates/registration/password_reset_done.html deleted file mode 100644 index b68f5af4..00000000 --- a/tapir/accounts/templates/registration/password_reset_done.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "core/base.html" %} - -{% load django_bootstrap5 %} -{% load tapir_static %} -{% load i18n %} - -{% block content %} -
-

{% translate "Password reset instructions have been sent." %}

-

- {% translate "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly. If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %} -

-
-{% endblock %} diff --git a/tapir/accounts/templates/registration/password_reset_form.html b/tapir/accounts/templates/registration/password_reset_form.html deleted file mode 100644 index b62267fe..00000000 --- a/tapir/accounts/templates/registration/password_reset_form.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "core/base.html" %} - -{% load django_bootstrap5 %} -{% load tapir_static %} -{% load i18n %} - -{% block content %} -
-
-
- {% translate "Problems with logging in?" %} -
-
-
- {% csrf_token %} -

{% translate "Please enter your email address, we will send you instructions to reset your password." %}

- {% bootstrap_form form %} -

- {% translate "Back to login form" %} -

- -
-
-
-
-{% endblock %} 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/templatetags/keycloak.py b/tapir/accounts/templatetags/keycloak.py new file mode 100644 index 00000000..02ff0144 --- /dev/null +++ b/tapir/accounts/templatetags/keycloak.py @@ -0,0 +1,24 @@ +import json + +from django import template + +from tapir import settings + +register = template.Library() + + +@register.simple_tag +def keycloak_config() -> str: + return json.dumps( + { + "url": settings.KEYCLOAK_ADMIN_CONFIG["PUBLIC_URL"], + "realm": settings.KEYCLOAK_ADMIN_CONFIG["USER_REALM_NAME"], + "clientId": settings.KEYCLOAK_ADMIN_CONFIG["FRONTEND_CLIENT_ID"], + "publicClient": True, + } + ) + + +@register.simple_tag +def keycloak_public_url() -> str: + return settings.KEYCLOAK_ADMIN_CONFIG["PUBLIC_URL"] 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..e39a1628 100644 --- a/tapir/settings.py +++ b/tapir/settings.py @@ -25,23 +25,22 @@ # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env( +SECRET_KEY = env.str( "SECRET_KEY", default="fl%20e9dbkh4mosi5$i$!5&+f^ic5=7^92hrchl89x+)k0ctsn" ) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env("DEBUG", cast=bool, default=False) +DEBUG = env.bool("DEBUG", default=False) -ALLOWED_HOSTS = env("ALLOWED_HOSTS", cast=list, default=["*"]) +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["*"]) -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" -) +TAPIR_VERSION = env.str("TAPIR_VERSION", default="dev") +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 +86,10 @@ "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", - "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 +124,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 = { @@ -208,17 +201,13 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" elif EMAIL_ENV == "prod": EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" - EMAIL_HOST = env("EMAIL_HOST", default="smtp-relay.gmail.com") - EMAIL_HOST_USER = env("EMAIL_HOST_USER", default="mitglied@supercoop.de") - EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") + EMAIL_HOST = env.str("EMAIL_HOST") + EMAIL_HOST_SENDER = env.str("EMAIL_HOST_SENDER") + EMAIL_HOST_USER = env.str("EMAIL_HOST_USER") + EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD") EMAIL_PORT = 587 EMAIL_USE_TLS = True -EMAIL_ADDRESS_MEMBER_OFFICE = "mitglied@supercoop.de" -COOP_NAME = "WirGarten Lüneburg" -FROM_EMAIL_MEMBER_OFFICE = f"{COOP_NAME} Mitgliederbüro <{EMAIL_ADDRESS_MEMBER_OFFICE}>" -DEFAULT_FROM_EMAIL = FROM_EMAIL_MEMBER_OFFICE - # DJANGO_ADMINS="Blake , Alice Judge " ADMINS = tuple(email.utils.parseaddr(x) for x in env.list("DJANGO_ADMINS", default=[])) # Crash emails will come from this address. @@ -237,40 +226,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 +241,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"), - 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"), + SERVER_URL=env.str("KEYCLOAK_ADMIN_SERVER_URL", default="http://keycloak:8080"), + PUBLIC_URL=env.str("KEYCLOAK_PUBLIC_URL", default="http://localhost:8080"), + CLIENT_ID=env.str("KEYCLOAK_CLIENT_ID", default="tapir-backend"), + FRONTEND_CLIENT_ID=env.str("KEYCLOAK_FRONTEND_CLIENT_ID", default="tapir-frontend"), + REALM_NAME=env.str("KEYCLOAK_ADMIN_REALM_NAME", default="master"), + USER_REALM_NAME=env.str("KEYCLOAK_ADMIN_USER_REALM_NAME", default="tapir"), + CLIENT_SECRET_KEY=env.str("KEYCLOAK_ADMIN_CLIENT_SECRET_KEY", default="**********"), ) -AUTHENTICATION_BACKENDS = [ - 'tapir.accounts.backends.KeycloakAuthorizationCredentialsBackend', -] \ No newline at end of file + +CSP_FRAME_SRC = ["'self'", KEYCLOAK_ADMIN_CONFIG["PUBLIC_URL"]] + +# 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..20cc1ad9 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(initial_password="roberto.cortes") + 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..220b7575 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().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..900bf32a 100644 --- a/tapir/wirgarten/service/file_export.py +++ b/tapir/wirgarten/service/file_export.py @@ -1,8 +1,9 @@ import csv -from importlib.resources import _ +from django.utils.translation import gettext_lazy as _ from django.core.mail import EmailMultiAlternatives +from tapir import settings from tapir.configuration.parameter import get_parameter_value from tapir.wirgarten.models import ExportedFile from tapir.wirgarten.parameters import Parameter @@ -32,7 +33,7 @@ def __send_email(file: ExportedFile, recipient: str | None = None): "Hallo Admin,

im Anhang findest du die aktuelle {filename}.


(Automatisch von Tapir versendet)" ).format(filename=filename), to=recipient, - from_email=get_parameter_value(Parameter.SITE_ADMIN_EMAIL), + from_email=settings.EMAIL_HOST_SENDER, ) email.content_subtype = "html" email.attach(filename, file.file) diff --git a/tapir/wirgarten/service/member.py b/tapir/wirgarten/service/member.py index 830bf583..610368f3 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): @@ -262,7 +235,7 @@ def send_cancellation_confirmation_email( contract_list=f"{'
'.join(map(lambda x: '- ' + str(x), subs_to_cancel))}
", ), to=[member.email], - from_email=get_parameter_value(Parameter.SITE_ADMIN_EMAIL), + from_email=settings.EMAIL_HOST_SENDER, ) email.content_subtype = "html" email.send() 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/tasks.py b/tapir/wirgarten/tasks.py index 2f078832..9623a243 100644 --- a/tapir/wirgarten/tasks.py +++ b/tapir/wirgarten/tasks.py @@ -327,7 +327,7 @@ def send_email_member_contract_end_reminder(member_id): site_name=get_parameter_value(Parameter.SITE_NAME), ), to=[member.email], - from_email=get_parameter_value(Parameter.SITE_ADMIN_EMAIL), + from_email=settings.EMAIL_HOST_SENDER, ) email.content_subtype = "html" email.send() 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..d60dd07e 100644 --- a/tapir/wirgarten/views/default_redirect.py +++ b/tapir/wirgarten/views/default_redirect.py @@ -1,4 +1,6 @@ -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 @@ -6,22 +8,54 @@ 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(): + if user_type == RequestUserType.MEMBER: 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")) + return handle_403( + request, PermissionError(_("Du bist nicht authorisiert diese Seite zu sehen.")) + ) diff --git a/tapir/wirgarten/views/member.py b/tapir/wirgarten/views/member.py index 9f9406cc..22f88b2b 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=settings.EMAIL_HOST_SENDER, + ) + 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