Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

# set !*.sql in folders where you want to include sql files
*.sql
*.pgdump
*.pgdump.partial

dev-secrets/
# Byte-compiled / optimized / DLL files
Expand Down
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ export NEXT_PUBLIC_KOMPASSI_BASE_URL=http://localhost:8000
npm run dev # starts Next.js at localhost:3000 with GraphQL codegen watch
```

To develop against production data, fetch a pseudonymized copy and load it into the docker compose database (replaces it; `mahti`/`mahti` is recreated):

```bash
scripts/pseudonymized-dump.sh # needs kubectl access; writes kompassi-production-YYYYMMDD.pgdump
scripts/load-dump.sh kompassi-production-YYYYMMDD.pgdump
```

Never load a raw production dump locally. The pseudonymization rules live in `kompassi/core/pseudonymization.py`; a new model field that could hold personal data fails `kompassi/core/test_pseudonymization.py` until it has a rule or an entry in `NOT_PERSONAL` (`python manage.py pseudonymize_db --check` lists them).

## Backend commands

All backend commands assume the virtualenv is active (`source .venv/bin/activate`) or are run via Docker.
Expand Down
45 changes: 45 additions & 0 deletions kompassi/core/management/commands/pseudonymize_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction


class Command(BaseCommand):
help = (
"Pseudonymize all personal data and delete all secrets in the database. "
"Refuses to run unless the database name contains 'pseudo'."
)

def add_arguments(self, parser):
parser.add_argument(
"--really",
action="store_true",
help="Confirm that you want to pseudonymize this database.",
)
parser.add_argument(
"--check",
action="store_true",
help="List fields that may hold personal data but are neither pseudonymized nor reviewed as safe.",
)

def handle(self, *args, **options):
from kompassi.core.pseudonymization import find_stale_classifications, find_unclassified_fields, pseudonymize

if options["check"]:
problems = find_unclassified_fields() + [f"{name} (stale)" for name in find_stale_classifications()]
for problem in problems:
self.stdout.write(problem)
if problems:
raise CommandError(f"{len(problems)} fields need a rule or an entry in NOT_PERSONAL")
return

database_name = settings.DATABASES["default"]["NAME"]
if "pseudo" not in database_name:
raise CommandError(f"Database name {database_name!r} does not contain 'pseudo'.")
if not options["really"]:
raise CommandError(f"Pass --really to confirm you want to pseudonymize {database_name!r}.")

with transaction.atomic():
for model_label, summary in pseudonymize():
self.stdout.write(f"{model_label}: {summary}")

self.stdout.write(self.style.SUCCESS("Pseudonymization complete."))
Loading
Loading