diff --git a/.dockerignore b/.dockerignore index 8d2fc856..0cde6c62 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,59 @@ -node_modules -Dockerfile* -docker-compose* -compose.* -.dockerignore -.git -.gitignore -README.md -LICENSE -.vscode -build +# Using "**/" prefix to make ignores work in root and in subdirectories at any level. +# +# From https://docs.docker.com/build/concepts/context/#dockerignore-files +# "Docker also supports a special wildcard string ** that matches any +# number of directories (including zero). For example, **/*.go excludes +# all files that end with .go found anywhere in the build context." +# +# NOTE: +# You can add an exception for a specific file by prefixing the line with an ! +# if you need to include a file that would otherwise be ignored. + +# Environment variable files +**/*.env +**/*.env.example +**/.env +**/.env.* + +# Certificate/keystore/key files +**/*.ca-bundle +**/*.cer +**/*.cert +**/*.crt +**/*.jks +**/*.key +**/*.keystore +**/*.p7b +**/*.p7c +**/*.p7s +**/*.p12 +**/*.pem +**/*.pfx +**/*.ppk +**/*.pvk + +# Build files +**/build +**/dist +**/target + +# Miscellaneous +**/*.lock +**/*.log +**/*.temp +**/*.tmp +**/.DS_Store +**/.git +**/.gitignore +**/.idea +**/.pytest_cache +**/.ruff_cache +**/.ssh +**/.venv +**/.vscode +**/__pycache__ +**/compose.* +**/node_modules +**/temp +**/tmp +**/venv diff --git a/.env.keycloak.example b/.env.example similarity index 53% rename from .env.keycloak.example rename to .env.example index 06fa85bb..744d09e7 100644 --- a/.env.keycloak.example +++ b/.env.example @@ -1,9 +1,32 @@ +SECRET_KEY= +CORS_ORIGIN_ALLOW_ALL=1 +APPLY_MIGRATIONS=1 +ADD_DEFAULT_LANGUAGES=1 DEBUG=1 -DATABASE_URL=postgres://kukkuu:kukkuu@localhost:5434/kukkuu +# Values in DATABASE_* and POSTGRES_* variables must match! +# DATABASE_URL is used by Django +# DATABASE_HOST is used by docker-entrypoint.sh +# POSTGRES_* variables are used by the Postgres Docker container +# i.e. DATABASE_URL should be: +# postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB} +# or optionally contain a port number e.g. ":5434" after the host +# Database settings for Docker + Docker compose based local development: +DATABASE_URL=postgres://kukkuu:kukkuu@kukkuu-db.helsinki/kukkuu +DATABASE_HOST=kukkuu-db.helsinki +# Database settings for non-Docker local development: +# DATABASE_URL=postgres://kukkuu:kukkuu@localhost/kukkuu +# DATABASE_HOST=localhost +POSTGRES_USER=kukkuu +POSTGRES_PASSWORD=kukkuu +POSTGRES_DB=kukkuu +SKIP_DATABASE_CHECK=0 +ALLOWED_HOSTS=* CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002 CORS_ORIGIN_ALLOW_ALL=True -ALLOWED_HOSTS=* +# For Keycloak test environment authentication service: TOKEN_AUTH_AUTHSERVER_URL=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus +# For local Tunnistamo authentication service: +# TOKEN_AUTH_AUTHSERVER_URL=http://tunnistamo-backend:8000/openid # For local Kukkuu API: TOKEN_AUTH_ACCEPTED_AUDIENCE=kukkuu-api-dev,profile-api-test # For test env Kukkuu API: @@ -14,9 +37,10 @@ GDPR_API_QUERY_SCOPE=gdprquery GDPR_API_DELETE_SCOPE=gdprdelete GDPR_API_AUTHORIZATION_FIELD=authorization.permissions.scopes HELUSERS_BACK_CHANNEL_LOGOUT_ENABLED=True +HELUSERS_PASSWORD_LOGIN_DISABLED=False KUKKUU_HASHID_SALT=abcdefg123456 KUKKUU_TICKET_VERIFICATION_URL=http://localhost:3000/ticket-verification-endpoint/{reference_id} -MAIL_MAILGUN_KEY +MAIL_MAILGUN_KEY= MAIL_MAILGUN_DOMAIN=hel.fi MAIL_MAILGUN_API=https://api.eu.mailgun.net/v3 KUKKUU_NOTIFICATIONS_SHEET_ID=1TkdQsO50DHOg5pi1JhzudOL1GKpiK-V2DCIoAipKj-M @@ -26,7 +50,6 @@ TOKEN_AUTH_BROWSER_TEST_JWT_ISSUER=https://kukkuu-ui.test.hel.ninja,https://kukk # Django-admin Keycloak login related variables: SOCIAL_AUTH_TUNNISTAMO_KEY=kukkuu-django-admin-dev -# Get secret from development-kv library → hki-CpsLjsaY-dev-kv keyvault → SOCIAL-AUTH-TUNNISTAMO-SECRET: -# https://portal.azure.com/#@helsinginkaupunki.onmicrosoft.com/asset/Microsoft_Azure_KeyVault/Secret/https://hki-cpsljsay-dev-kv.vault.azure.net/secrets/SOCIAL-AUTH-TUNNISTAMO-SECRET -SOCIAL_AUTH_TUNNISTAMO_SECRET=please-get-secret-from-keyvault +# Get secret from keyvault, see README.md for instructions: +SOCIAL_AUTH_TUNNISTAMO_SECRET= SOCIAL_AUTH_TUNNISTAMO_OIDC_ENDPOINT=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus diff --git a/.gitignore b/.gitignore index 8ff27272..a343f63d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,13 @@ .DS_Store .eggs/ .env +.env.development +.env.development.local +.env.local +.env.production +.env.production.local +.env.test +.env.test.local .grunt .hypothesis/ .idea diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef3d2fb0..ba561ffd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: # the ruff's version line, it is used by test_pre_commit_ruff_version.py # test case in order not to have to add a YAML library dependency just # to test this version: - rev: v0.9.6 # ruff-pre-commit version + rev: v0.9.9 # ruff-pre-commit version hooks: # Run the linter - id: ruff diff --git a/Dockerfile b/Dockerfile index 582a5aba..f6c70d1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,13 @@ WORKDIR /app RUN mkdir /entrypoint -COPY --chown=default:root requirements.txt /app/requirements.txt -COPY --chown=default:root requirements-prod.txt /app/requirements-prod.txt +# chmod=755 = rwxr-xr-x i.e. owner can read, write and execute, group and others can read and execute. +# +# Related to SonarCloud security hotspot docker:S6470 i.e. +# "Recursively copying context directories is security-sensitive" i.e. +# https://rules.sonarsource.com/docker/RSPEC-6470/ +# see .dockerignore for info on what is not copied here: +COPY --chown=root:root --chmod=755 . /app/ RUN yum update -y && yum install -y \ nc \ @@ -16,23 +21,20 @@ RUN yum update -y && yum install -y \ && pip install --no-cache-dir -r /app/requirements.txt \ && pip install --no-cache-dir -r /app/requirements-prod.txt -COPY --chown=default:root docker-entrypoint.sh /entrypoint/docker-entrypoint.sh +# fatal: detected dubious ownership in repository at '/app' +RUN git config --system --add safe.directory /app + +COPY --chown=root:root --chmod=755 docker-entrypoint.sh /entrypoint/docker-entrypoint.sh ENTRYPOINT ["/entrypoint/docker-entrypoint.sh"] # ============================== FROM appbase AS development # ============================== -COPY --chown=default:root requirements-dev.txt /app/requirements-dev.txt RUN pip install --no-cache-dir -r /app/requirements-dev.txt ENV DEV_SERVER=1 -COPY --chown=default:root . /app/ - -# fatal: detected dubious ownership in repository at '/app' -RUN git config --system --add safe.directory /app - USER default EXPOSE 8081/tcp @@ -40,11 +42,6 @@ EXPOSE 8081/tcp FROM appbase AS production # ============================== -COPY --chown=default:root . /app/ - -# fatal: detected dubious ownership in repository at '/app' -RUN git config --system --add safe.directory /app - RUN SECRET_KEY="only-used-for-collectstatic" KUKKUU_HASHID_SALT="only-used-for-collectstatic" python manage.py collectstatic USER default diff --git a/README.md b/README.md index e6ee9c9c..d03d12da 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ - [Database](#database) - [Notification import](#notification-import) - [Daily running, Debugging](#daily-running-debugging) + - [Generating secret key for Django](#generating-secret-key-for-django) + - [Getting secret for django-admin login](#getting-secret-for-django-admin-login) - [Keeping Python requirements up to date](#keeping-python-requirements-up-to-date) - [Code linting & formatting](#code-linting--formatting) - [Pre-commit hooks](#pre-commit-hooks) @@ -101,9 +103,10 @@ Optionally if you want to use pre-commit hooks: ### Development with Docker -1. Copy `docker-compose.env.example` to `docker-compose.env`, then modify it as needed. At a minimum, ensure that `SOCIAL_AUTH_TUNNISTAMO_SECRET` is set using the secret from keyvault. - -2. Run `docker compose up` +1. Copy `.env.example` to `.env` +2. Set value for `SECRET_KEY` to `.env` with [Generating secret key for Django](#generating-secret-key-for-django) instructions +3. Set value for `SOCIAL_AUTH_TUNNISTAMO_SECRET` to `.env` with [Getting secret for django admin login](#getting-secret-for-django-admin-login) instructions +4. Run `docker compose up` If you do not have a super user / admin to administrate the API yet, you can create one with: @@ -116,7 +119,11 @@ The project is now running at http://localhost:8081 and using public Keycloak te ### Development without Docker -Start by installing the [requirements](#requirements). +1. Install [requirements](#requirements) +2. Copy `.env.example` to `.env` +3. Set value for `SECRET_KEY` to `.env` with [Generating secret key for Django](#generating-secret-key-for-django) instructions +4. Set value for `SOCIAL_AUTH_TUNNISTAMO_SECRET` to `.env` with [Getting secret for django admin login](#getting-secret-for-django-admin-login) instructions +5. Modify `DATABASE_URL` and `DATABASE_HOST` in your `.env` file based on where your PostgreSQL database is set up #### Installing Python requirements @@ -169,6 +176,40 @@ KUKKUU_NOTIFICATIONS_SHEET_ID=1TkdQsO50DHOg5pi1JhzudOL1GKpiK-V2DCIoAipKj-M - Run `python manage.py runserver localhost:8081` - The project is now running at http://localhost:8081 +### Generating secret key for Django + +Django needs a value for [SECRET_KEY](https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key) to start. + +For production, you should use a strong, long, randomly generated key. + +For local development, if you prefer, you can alternatively use a shorter, manually generated key. + +Here's how you can generate a value for `SECRET_KEY` using Python (Based on Django v5.1.6's +[get_random_secret_key](https://github.com/django/django/blob/5.1.6/django/core/management/utils.py#L79C5-L84) & +[get_random_string](https://github.com/django/django/blob/5.1.6/django/utils/crypto.py#L51-L62)): +```python +import secrets, string +allowed_chars = string.ascii_lowercase + string.digits + "!@#$%^&*(-_=+)" +"".join(secrets.choice(allowed_chars) for i in range(50)) +``` + +### Getting secret for django-admin login + +To be able to log in to django-admin with Keycloak you need to set `SOCIAL_AUTH_TUNNISTAMO_SECRET` +in your environment—it is for Keycloak even though it's named Tunnistamo. Here's how you can get a +value for local development i.e. `kukkuu-django-admin-dev` client: + +- Go to City of Helsinki's Azure DevOps [kukkuu project](https://dev.azure.com/City-of-Helsinki/kukkuu) +- Open `Pipelines > Library > development-kv` for the development keyvault library +- Read `Key vault name` value +- Open [Azure Portal](https://portal.azure.com/) +- Search Azure Portal with the key vault name you just read, and open the found key vault +- Open `Objects > Secrets` +- Find `SOCIAL-AUTH-TUNNISTAMO-SECRET` (may need pressing `Load more`), and open it +- Click on the hexadecimal current version value to open the secret's current version +- Click on the "Copy to clipboard" icon after the `Secret value` to copy it to clipboard +- Paste the value into your `.env` file as `SOCIAL_AUTH_TUNNISTAMO_SECRET=paste-the-copied-value-here` + ### Keeping Python requirements up to date If you're using Docker, spin up the container using `docker compose up` diff --git a/compose.yaml b/compose.yaml index f9d75d4b..07e49c5e 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,10 +2,8 @@ services: postgres: image: postgres:13 restart: on-failure - environment: - POSTGRES_USER: kukkuu - POSTGRES_PASSWORD: kukkuu - POSTGRES_DB: kukkuu + env_file: + - .env # For POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB ports: - 5434:5432 volumes: @@ -15,13 +13,9 @@ services: django: build: context: . - target: ${DOCKER_TARGET:-development} + target: ${DOCKER_TARGET:-development} # stage of Dockerfile to build env_file: - - docker-compose.env - environment: - DATABASE_URL: postgres://kukkuu:kukkuu@kukkuu-db/kukkuu - DATABASE_HOST: kukkuu-db.helsinki - SKIP_DATABASE_CHECK: 1 + - .env volumes: - .:/app ports: diff --git a/docker-compose.env.example b/docker-compose.env.example deleted file mode 100644 index 665d1974..00000000 --- a/docker-compose.env.example +++ /dev/null @@ -1,33 +0,0 @@ -CORS_ORIGIN_ALLOW_ALL=1 -APPLY_MIGRATIONS=1 -ADD_DEFAULT_LANGUAGES=1 -DEBUG=1 -DATABASE_URL=postgres://kukkuu:kukkuu@localhost:5434/kukkuu -ALLOWED_HOSTS=* -CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002 -CORS_ORIGIN_ALLOW_ALL=True -# For Keycloak test environment authentication service: -TOKEN_AUTH_AUTHSERVER_URL=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus -# For local Tunnistamo authentication service: -# TOKEN_AUTH_AUTHSERVER_URL=http://tunnistamo-backend:8000/openid -TOKEN_AUTH_ACCEPTED_AUDIENCE=kukkuu-api-dev,profile-api-test -TOKEN_AUTH_ACCEPTED_SCOPE_PREFIX= -TOKEN_AUTH_REQUIRE_SCOPE_PREFIX=False -GDPR_API_QUERY_SCOPE=gdprquery -GDPR_API_DELETE_SCOPE=gdprdelete -GDPR_API_AUTHORIZATION_FIELD=authorization.permissions.scopes -HELUSERS_BACK_CHANNEL_LOGOUT_ENABLED=True -HELUSERS_PASSWORD_LOGIN_DISABLED=False -KUKKUU_HASHID_SALT=ULGd5YeRv6yVtvoj -KUKKUU_TICKET_VERIFICATION_URL=http://localhost:3000/ticket-verification-endpoint/{reference_id} -MAIL_MAILGUN_KEY= -MAIL_MAILGUN_DOMAIN=hel.fi -MAIL_MAILGUN_API=https://api.eu.mailgun.net/v3 -KUKKUU_NOTIFICATIONS_SHEET_ID=1TkdQsO50DHOg5pi1JhzudOL1GKpiK-V2DCIoAipKj-M - -# Django-admin Keycloak login related variables: -SOCIAL_AUTH_TUNNISTAMO_KEY=kukkuu-django-admin-dev -# Get secret from development-kv library → hki-CpsLjsaY-dev-kv keyvault → SOCIAL-AUTH-TUNNISTAMO-SECRET: -# https://portal.azure.com/#@helsinginkaupunki.onmicrosoft.com/asset/Microsoft_Azure_KeyVault/Secret/https://hki-cpsljsay-dev-kv.vault.azure.net/secrets/SOCIAL-AUTH-TUNNISTAMO-SECRET -SOCIAL_AUTH_TUNNISTAMO_SECRET=please-get-secret-from-keyvault -SOCIAL_AUTH_TUNNISTAMO_OIDC_ENDPOINT=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 99a4b7f1..f64c1fdf 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -# -z for empty / not assigned variable or -o to check whether the value is 0 (=should be skipped) +# -z for empty / not assigned variable or -o to check whether the value is 0 (=should not be skipped) if [ -z "$SKIP_DATABASE_CHECK" -o "$SKIP_DATABASE_CHECK" = "0" ]; then until nc -z -v -w30 "$DATABASE_HOST" 5432 do diff --git a/docs/setup-keycloak.md b/docs/setup-keycloak.md index c8ae4db3..dc57ad7b 100644 --- a/docs/setup-keycloak.md +++ b/docs/setup-keycloak.md @@ -25,8 +25,7 @@ HELUSERS_PASSWORD_LOGIN_DISABLED=False # Django-admin Keycloak login related variables: SOCIAL_AUTH_TUNNISTAMO_KEY=kukkuu-django-admin-dev -# Get secret from development-kv library → hki-CpsLjsaY-dev-kv keyvault → SOCIAL-AUTH-TUNNISTAMO-SECRET: -# https://portal.azure.com/#@helsinginkaupunki.onmicrosoft.com/asset/Microsoft_Azure_KeyVault/Secret/https://hki-cpsljsay-dev-kv.vault.azure.net/secrets/SOCIAL-AUTH-TUNNISTAMO-SECRET -SOCIAL_AUTH_TUNNISTAMO_SECRET=please-get-secret-from-keyvault +# Get secret from keyvault, see README.md for instructions: +SOCIAL_AUTH_TUNNISTAMO_SECRET= SOCIAL_AUTH_TUNNISTAMO_OIDC_ENDPOINT=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus ``` diff --git a/events/factories.py b/events/factories.py index 21a75303..07b12753 100644 --- a/events/factories.py +++ b/events/factories.py @@ -1,4 +1,3 @@ -import random from zoneinfo import ZoneInfo import factory @@ -42,14 +41,12 @@ class Meta: skip_postgeneration_save = True # Not needed after factory v4.0.0 -def get_external_ticket_system(): - "Return a random external ticket system from available choices" - return random.choice(list(zip(*Event.EXTERNAL_TICKET_SYSTEM_CHOICES))[0]) - - class RandomExternalTicketSystemEventFactory(EventFactory): published_at = factory.LazyFunction(lambda: timezone.now()) - ticket_system = factory.LazyFunction(get_external_ticket_system) + ticket_system = factory.Faker( + "random_element", + elements=[choice[0] for choice in Event.EXTERNAL_TICKET_SYSTEM_CHOICES], + ) ticket_system_url = factory.Faker("url") capacity_per_occurrence = None duration = None diff --git a/gdpr/tests/keys.py b/gdpr/tests/keys.py index 44e9031a..9e480b80 100644 --- a/gdpr/tests/keys.py +++ b/gdpr/tests/keys.py @@ -1,3 +1,5 @@ +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa from jose import jwk from jose.constants import ALGORITHMS @@ -22,71 +24,17 @@ class _Key: def _get_pem_key_pair(): - """Get a PEM-key pair. - - NOTE: Follow the following instructions to create a new pair of PEM keys. - - You can install ``cryptography`` with ``pip``: - - ``pip install cryptography`` - - The following script can be used to create a new pair of PEM keys. - - >>> # doctest: +SKIP - ... from cryptography.hazmat.primitives.asymmetric import rsa - ... from cryptography.hazmat.primitives import serialization - ... - ... private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) - ... pem_private_key = private_key.private_bytes( - ... encoding=serialization.Encoding.PEM, - ... format=serialization.PrivateFormat.PKCS8, - ... encryption_algorithm=serialization.NoEncryption(), - ... ) - ... pem_public_key = private_key.public_key().public_bytes( - ... encoding=serialization.Encoding.PEM, - ... format=serialization.PublicFormat.SubjectPublicKeyInfo, - ... ) - ... print(pem_private_key.decode("utf-8")) - ... print(pem_public_key.decode("utf-8")) - """ - return ( - """-----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/2Fgt2rRchA7t -GP6FG3t9bcbQLPlZa3XEFTndv/snAaUBlP8rDiTvoToymNV82prOheYTdmN/sLP4 -a9c6nMcbilQjhkHGDZKxC9lOCakd4qL0iyfRO30IL7M5sX/sZP0tnAAOohEY1ljB -MjNNmp45jIZE9LD0/fZ9pM1lv3BzURkKt/Diww8H9EfPG6Y5z44TYO0kfATUBF2z -IZAJzgHAiIWS8BJ2lyidGGi2+Gx4K7f5fFmotjwMPYORiqbwpgSshVefUapedvJC -VFGKPY6f+Beb6PzLE4kEK8+/ctnRdZt2pgbRrom776gUdg59OKL60EL5LExpb/kA -DtRto/txAgMBAAECggEAMa9JHw8OOQumhfc8K6Lzd4ePvuh255ayGEdbBjgrRm3h -myhIcZEnNbxuwx3b5IsFHsmEzbOSj0ZnRcZAJpjl5BcONWkW7cEkJaAo9lIAL5I7 -m9PSSxj6B7260A1NUR7ShxZo2WFVxjX1JIvox4dsxQDE4WTx03FWfjHJVDmhWOvL -L0DRjXgTrYj1dBBEnUAWwkIlFDNFzqAVsiZHF+6XOuRUT+vEZPv02IkmUrn3uwos -mBELDelOqL/m5t7EYkxW7apTuGXoxc8NOctDj10+OVcus0Kia8v1REJpkL2hF+AB -gnAQSI8x6OeWjFOZuhUEUsSZLARnkT/K9EFxM4IOsQKBgQD8Ek5/SCAAUT7CFtmM -MdZFpQ0I9ZjywtyrTZFY1g405HpGvzc+sBxu3+K1TPPO+GDz+zmHspaVKFw+4W6J -i8oO6hf1H9N16xR+EqTMosSDX3ud8P1Awswu2TUdNMbCzqx2ICZauLjY1ArZnuPI -BaJMwTY1z12wlZSgB8Gc7wIETQKBgQDC1cBp+5/yvbKoI2kUl/GABVaCumW7ieTz -Yog31RwLR6mvef5LNFomONvDrNxKxSEHj3fo409ZoqNzArVGz3A0iK/CHKVjKpUp -YMXOYMWBOHidC5D0wI3sb/UGoDj7CjCboBvvHbBCB+RVA5atuodLDUoshrVSNrgQ -28JQBR81tQKBgQCHTqBaTHH5GaNxdeiDC8F0EvvjQko+jYD8Zx/NKuHnXHmSflP+ -T3SDw6QjI9J/1+3bKZChGakhGdAiZMn8BVCKHviLOPE+i9itL/7MZdbMmjV1+4VF -/QqzXx7WtZy3t071/Z349s0qfu/wDw1AMl4Di3c4/T3SawijumYggl93xQKBgDjs -EDImlJ1rtKWQ6uNcXO8lIBhDcvNunxhIYjnFplLZVgbxYk0Ad5IRLIunlhi2LScz -UDoXJxit/ojccq/EbSi8AnV4vw0Q5NFY95GLDkjpgbuIJIqNMymvr+uGpf8aBAeD -qIWcq+EuwxPfX4dUJrDTqicGGDVzzSUHv2Z3iJ29AoGBAK/8kuvvz7fl7nvR80zG -xv4qkLETZPmGcssma1TskdNn8eYADcDgnnzI3Lldr6DoouGQ8eKNOUQC6X53GU12 -0k/+6od92VNGkQI8g7DCPwPC8cTPvvUGFkxg3J8v2nBcaLMmDVUaXrDfj+QG+jUB -MG7YFa0LPJ8R79pFieleUm1O ------END PRIVATE KEY-----""", - """-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9hYLdq0XIQO7Rj+hRt7 -fW3G0Cz5WWt1xBU53b/7JwGlAZT/Kw4k76E6MpjVfNqazoXmE3Zjf7Cz+GvXOpzH -G4pUI4ZBxg2SsQvZTgmpHeKi9Isn0Tt9CC+zObF/7GT9LZwADqIRGNZYwTIzTZqe -OYyGRPSw9P32faTNZb9wc1EZCrfw4sMPB/RHzxumOc+OE2DtJHwE1ARdsyGQCc4B -wIiFkvASdpconRhotvhseCu3+XxZqLY8DD2DkYqm8KYErIVXn1GqXnbyQlRRij2O -n/gXm+j8yxOJBCvPv3LZ0XWbdqYG0a6Ju++oFHYOfTii+tBC+SxMaW/5AA7UbaP7 -cQIDAQAB ------END PUBLIC KEY-----""", + private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + pem_private_key = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + pem_public_key = private_key.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, ) + return pem_private_key.decode("utf-8"), pem_public_key.decode("utf-8") rsa_key = _build_key(*_get_pem_key_pair()) diff --git a/kukkuu/settings.py b/kukkuu/settings.py index deb6f55f..8e7a976f 100644 --- a/kukkuu/settings.py +++ b/kukkuu/settings.py @@ -1,6 +1,8 @@ import os import subprocess +import tempfile from datetime import datetime +from pathlib import Path import environ import sentry_sdk @@ -34,10 +36,10 @@ STATIC_URL=(str, "/static/"), ALLOWED_HOSTS=(list, []), USE_X_FORWARDED_HOST=(bool, False), - DATABASE_URL=(str, "postgres://kukkuu:kukkuu@localhost/kukkuu"), + DATABASE_URL=(str, ""), CACHE_URL=(str, "locmemcache://"), MAILER_EMAIL_BACKEND=(str, "django.core.mail.backends.console.EmailBackend"), - MAILER_LOCK_PATH=(str, "/tmp/mailer_lockfile"), + MAILER_LOCK_PATH=(str, ""), DEFAULT_FROM_EMAIL=(str, "kukkuu@example.com"), DEFAULT_SMS_SENDER=(str, "Hel.fi"), ILMOITIN_TRANSLATED_FROM_EMAIL=(dict, {}), @@ -110,8 +112,11 @@ DEBUG = env.bool("DEBUG") SECRET_KEY = env.str("SECRET_KEY") -if DEBUG and not SECRET_KEY: - SECRET_KEY = "xxx" +if not SECRET_KEY: + raise ImproperlyConfigured( + "The SECRET_KEY setting must not be empty. " + "See README.md for instructions how to generate a new secret key." + ) ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") USE_X_FORWARDED_HOST = env.bool("USE_X_FORWARDED_HOST") @@ -130,8 +135,22 @@ } EMAIL_BACKEND = "mailer.backend.DbBackend" MAILER_EMAIL_BACKEND = env.str("MAILER_EMAIL_BACKEND") + +_tmp_mailer_dir = None if env("MAILER_LOCK_PATH"): MAILER_LOCK_PATH = env.str("MAILER_LOCK_PATH") +else: + # Create a temporary directory for mailer lock file. + # Stored as a module-level variable to keep the TemporaryDirectory instance + # alive for the entire process lifetime. + # + # From https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory + # "This class securely creates a temporary directory" and + # "On completion of the context or destruction of the temporary directory object, + # the newly created temporary directory and all its contents are removed from + # the filesystem." + _tmp_mailer_dir = tempfile.TemporaryDirectory(prefix="kukkuu-mailer-") + MAILER_LOCK_PATH = Path(_tmp_mailer_dir.name) / "mailer_lockfile" ILMOITIN_TRANSLATED_FROM_EMAIL = env("ILMOITIN_TRANSLATED_FROM_EMAIL") ILMOITIN_QUEUE_NOTIFICATIONS = env("ILMOITIN_QUEUE_NOTIFICATIONS") diff --git a/kukkuu/urls.py b/kukkuu/urls.py index fe511adb..158b4bd5 100644 --- a/kukkuu/urls.py +++ b/kukkuu/urls.py @@ -5,6 +5,7 @@ from django.urls import include, path, re_path from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView from helusers.admin_site import admin from rest_framework import routers @@ -68,6 +69,7 @@ def graphql_view(request, *args, **kwargs): # +@require_http_methods(["GET", "HEAD"]) def readiness(*args, **kwargs): response_json = { "status": "ok", diff --git a/requirements-dev.in b/requirements-dev.in index 9cce0bc2..d8afd64b 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -1,4 +1,5 @@ -c requirements.txt +cryptography freezegun ipython pytest diff --git a/requirements-dev.txt b/requirements-dev.txt index adf32840..ca4dbfbc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,13 +10,21 @@ certifi==2025.1.31 # via # -c /app/requirements.txt # requests +cffi==1.17.1 + # via + # -c /app/requirements.txt + # cryptography charset-normalizer==3.4.1 # via # -c /app/requirements.txt # requests coverage[toml]==7.6.12 # via pytest-cov -decorator==5.1.1 +cryptography==44.0.2 + # via + # -c /app/requirements.txt + # -r requirements-dev.in +decorator==5.2.1 # via ipython executing==2.2.0 # via stack-data @@ -30,8 +38,10 @@ idna==3.10 # requests iniconfig==2.0.0 # via pytest -ipython==8.32.0 +ipython==9.0.1 # via -r requirements-dev.in +ipython-pygments-lexers==1.1.1 + # via ipython jedi==0.19.2 # via ipython matplotlib-inline==0.1.7 @@ -52,9 +62,15 @@ ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data +pycparser==2.22 + # via + # -c /app/requirements.txt + # cffi pygments==2.19.1 - # via ipython -pytest==8.3.4 + # via + # ipython + # ipython-pygments-lexers +pytest==8.3.5 # via # -r requirements-dev.in # pytest-cov @@ -80,7 +96,7 @@ requests-mock==1.12.1 # via -r requirements-dev.in responses==0.25.6 # via -r requirements-dev.in -ruff==0.9.6 +ruff==0.9.9 # via -r requirements-dev.in six==1.17.0 # via diff --git a/requirements.txt b/requirements.txt index 4da73b5a..e5719317 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ attrs==25.1.0 # via # jsonschema # referencing -authlib==1.4.1 +authlib==1.5.1 # via drf-oidc-auth azure-core==1.32.0 # via @@ -20,7 +20,7 @@ azure-core==1.32.0 # django-storages azure-storage-blob==12.24.1 # via django-storages -cachetools==5.5.1 +cachetools==5.5.2 # via django-helusers certifi==2025.1.31 # via @@ -30,7 +30,7 @@ cffi==1.17.1 # via cryptography charset-normalizer==3.4.1 # via requests -cryptography==44.0.1 +cryptography==44.0.2 # via # authlib # azure-storage-blob @@ -212,7 +212,7 @@ requests==2.32.3 # social-auth-core requests-oauthlib==2.0.0 # via social-auth-core -rpds-py==0.22.3 +rpds-py==0.23.1 # via # jsonschema # referencing