Release govoplan-access v0.1.25: harden authentication and repair identity mappings
Module Package Release / publish-packages (push) Successful in 15s
Module Package Release / publish-packages (push) Successful in 15s
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Authentication cache boundary hardening
|
||||
|
||||
The authentication cache is an optimization, never an additional authentication
|
||||
method or permission source. The September 2026 review found and reproduced
|
||||
three violations of that boundary in isolated SQLite tests.
|
||||
|
||||
| Finding | Consequence | Resolution |
|
||||
| --- | --- | --- |
|
||||
| Service-account credentials passed through ordinary principal-summary refresh | A narrowed service-account ceiling could be replaced by backing membership roles; the service-account identifier and authentication method were lost. | Dedicated service-account resolution retains its provenance and checks the current ceiling/lifecycle on every request. It does not enter the interactive principal-summary cache. |
|
||||
| A warmed API-key summary accepted the same secret through a session cookie | The warm path accepted a cookie-authenticated mutation without the CSRF rule applied to real browser sessions; the cold path rejected the credential. | API keys require an explicit Bearer or X-API-Key header on both paths. Session cookies still require matching CSRF cookie/header/hash for mutations. |
|
||||
| Tenant-key intersection excluded only the historical `system:` spelling | Module-native system permissions and retained module wildcards could survive the tenant-only intersection. | Resolve wildcard grants to concrete registered tenant permissions and exclude system permissions by catalogue and compatibility aliases. |
|
||||
|
||||
No existing secrets, sessions, assignments, or database schema are changed.
|
||||
Normal header-authenticated API keys and concrete tenant aliases remain
|
||||
compatible. Clients relying on API keys in browser cookies, implicit unknown
|
||||
wildcards, or accidental instance-level rights must correct their authentication
|
||||
method or permission configuration; these are not preserved as compatibility
|
||||
exceptions. Stored grants are not rewritten. Service-account access continues
|
||||
to narrow immediately when its ceiling is reduced or its lifecycle blocks use.
|
||||
|
||||
Regression coverage is in `tests/test_auth_cache_security.py` and
|
||||
`tests/test_permission_catalog_contract.py`; the tests exercise the full
|
||||
credential resolver with principal caching enabled, not only the lower-level
|
||||
API-key lookup. They also verify that valid session CSRF and concrete legacy
|
||||
tenant aliases still work.
|
||||
|
||||
## Remaining coordinated password-change workflow
|
||||
|
||||
`Account.password_reset_required` is currently advisory metadata, not an
|
||||
enforced sign-in restriction. The administrator UI states this limitation,
|
||||
but the authentication-fields documentation previously claimed mandatory
|
||||
replacement; its English and German text now reflects the implementation.
|
||||
A generated password is disclosed once but is not a single-use login secret.
|
||||
|
||||
A follow-up must deliver the password-change endpoint, current-password
|
||||
verification and replacement policy, CSRF and attempt limits, session
|
||||
revocation/rotation and cache invalidation, a restricted reset-required
|
||||
principal, and the corresponding accessible UI/recovery path together.
|
||||
Enabling only a rejection gate would lock affected accounts out without any
|
||||
supported way to finish the change. This review does not enable such a gate or
|
||||
change existing passwords.
|
||||
|
||||
## Betriebshinweise
|
||||
|
||||
API-Schlüssel werden ausschließlich über `Authorization: Bearer` oder
|
||||
`X-API-Key` gesendet, nicht über das Sitzungscookie. Ändernde Cookie-Anfragen
|
||||
benötigen weiterhin einen passenden CSRF-Header samt Cookie und serverseitigem
|
||||
Prüfwert. Dienstkonten behalten ihre eigene Herkunft und werden bei jeder
|
||||
Anfrage gegen den aktuellen Berechtigungsrahmen und Lebenszyklus geprüft.
|
||||
Mitgliedschaftsrollen oder zwischengespeicherte interaktive Rechte dürfen diesen
|
||||
Rahmen nicht ersetzen.
|
||||
|
||||
Mandantenschlüssel erhalten keine instanzweiten Rechte, auch nicht unter
|
||||
modulbezogenen Berechtigungsnamen. Platzhalter werden in konkrete registrierte
|
||||
Mandantenrechte aufgelöst. Bestehende konkrete Mandantenrechte und ihre
|
||||
Kompatibilitätsnamen bleiben erhalten; gespeicherte Geheimnisse und
|
||||
Rollenzuweisungen werden nicht geändert.
|
||||
|
||||
Das Kennzeichen `password_reset_required` erzwingt derzeit keinen
|
||||
Passwortwechsel. Ein einmal angezeigtes Anfangspasswort bleibt zur Anmeldung
|
||||
verwendbar. Die Nachfolgeumsetzung muss Passwortänderung, eng begrenzten
|
||||
Zwischenzugriff, Sitzungswechsel beziehungsweise Widerruf und eine bedienbare
|
||||
Wiederherstellung gemeinsam liefern; eine alleinige Zugriffssperre würde
|
||||
betroffene Konten ohne durchführbaren Passwortwechsel aussperren.
|
||||
@@ -0,0 +1,79 @@
|
||||
# External function mapping schema repair
|
||||
|
||||
Access owns `access_external_function_role_assignments`. Some older databases
|
||||
record the Access baseline (`4a5b6c7d8e9f`) without this table. The mapping list
|
||||
and `/api/v1/admin/external-function-role-mappings/delta` then fail with an
|
||||
undefined-table error. This is a schema/history mismatch, not a reason to change
|
||||
user permissions or recreate tenant data.
|
||||
|
||||
Forward repair revision `d8f1b4e7a0c3` follows Access `c7e0a3d6f9b2` on the
|
||||
release track and `b6d9f2a5c8e1` on the disposable-development track. The latter
|
||||
also requires Core's existing scope-table rename `4f2a9c8e7b6d`; this is a Core
|
||||
contract and does not require the optional Tenancy or Organizations modules.
|
||||
|
||||
Before applying deployment migrations, back up and verify the database backup.
|
||||
Use the configured migration track and ordinary deployment migration workflow,
|
||||
including its deployment-wide advisory lock. Inspect the pending revision plan
|
||||
before any targeted repair. Never replay or stamp the baseline, initialize dev
|
||||
data, reset the database, or switch migration tracks to bypass the error.
|
||||
|
||||
The development launcher can run pending migrations when its file watcher
|
||||
reloads the backend. Prepare and test a migration outside the watched source
|
||||
tree, and complete the backup/preflight before placing a new migration file in
|
||||
that tree. Do not assume that waiting to invoke a migration command prevents a
|
||||
running development instance from applying it automatically.
|
||||
|
||||
The repair:
|
||||
|
||||
- Creates the absent mapping table only, with its baseline columns, role/scope
|
||||
cascade foreign keys, primary key, tenant/source/function/role uniqueness,
|
||||
and four lookup indexes.
|
||||
- Does nothing if the table already exists. It does not alter partial tables;
|
||||
any other schema mismatch needs separate inspection.
|
||||
- Never invents mappings or changes roles, memberships, permissions, or other
|
||||
application records. An empty list means no mappings have been configured.
|
||||
- Keeps the table and any stored mappings on downgrade, because the table
|
||||
belongs to the baseline and removing it would delete authorization policy.
|
||||
|
||||
After migration, verify both mapping list endpoints return success for an
|
||||
authorized user in the active tenant, and check the table's constraints and
|
||||
indexes. Existing read scopes and tenant isolation remain enforced. A missing
|
||||
table cannot reveal whether historical mappings were once removed: this repair
|
||||
does not reconstruct lost policy; investigate backups if mappings were expected.
|
||||
|
||||
Regression coverage in `tests/test_external_function_mapping_migration.py`
|
||||
recreates the observed missing-table failure in isolated databases on both
|
||||
tracks. It checks the HTTP list/delta responses, repeated upgrades, no-op
|
||||
upgrades with existing mappings, downgrade/re-upgrade preservation, unchanged
|
||||
parent rows/permissions, constraints, denied unprivileged reads, and tenant
|
||||
isolation.
|
||||
|
||||
## Deutsch
|
||||
|
||||
Bei älteren Datenbanken kann die Access-Basismigration als angewendet vermerkt
|
||||
sein, obwohl `access_external_function_role_assignments` fehlt. Die Liste der
|
||||
Funktions-Rollenzuordnungen und ihre Delta-API melden dann einen internen Fehler.
|
||||
Dies ist ein Widerspruch zwischen Schema und Migrationsstand, kein Anlass zur
|
||||
Erweiterung von Berechtigungen oder zum Neuerstellen von Mandantendaten.
|
||||
|
||||
Vor der regulären, vorwärtsgerichteten Migration `d8f1b4e7a0c3` eine überprüfte
|
||||
Datenbanksicherung erstellen. Den konfigurierten Migrationstrack und den
|
||||
regulären Bereitstellungsablauf mit installationsweiter Migrationssperre nutzen;
|
||||
bei einer gezielten Reparatur zuvor die ausstehenden Revisionen prüfen.
|
||||
Basismigrationen nicht erneut ausführen oder lediglich als angewendet markieren,
|
||||
keine Entwicklungsdaten initialisieren und die Datenbank nicht zurücksetzen.
|
||||
Der Entwicklungsstarter kann ausstehende Migrationen bereits beim automatischen
|
||||
Neuladen des Backends anwenden. Neue Migrationsdateien deshalb außerhalb des
|
||||
überwachten Quellbaums vorbereiten und testen; Sicherung und Vorprüfung vor dem
|
||||
Kopieren in den überwachten Quellbaum abschließen. Das Warten mit einem manuellen
|
||||
Migrationsaufruf verhindert die automatische Anwendung nicht.
|
||||
|
||||
Die Reparatur erstellt nur die fehlende Tabelle einschließlich Fremdschlüsseln,
|
||||
Eindeutigkeitsbedingung und Indizes. Vorhandene Tabellen und Datensätze bleiben
|
||||
unverändert; auch ein Downgrade entfernt keine Zuordnungsdaten. Teilweise
|
||||
vorhandene Tabellen werden nicht umgebaut und erfordern eine gesonderte Prüfung.
|
||||
Es entstehen keine automatischen Zuordnungen oder neuen Rechte. Anschließend
|
||||
beide Listenendpunkte im aktiven Mandanten mit einer berechtigten Person prüfen.
|
||||
Eine leere Liste bedeutet, dass keine Zuordnungen konfiguriert sind. Falls früher
|
||||
Zuordnungen erwartet wurden, Sicherungen prüfen: Verlorene Berechtigungsregeln
|
||||
lassen sich aus einer fehlenden Tabelle nicht rekonstruieren.
|
||||
@@ -11,6 +11,21 @@ session is deliberately protected by these operations; use normal logout to end
|
||||
it. Repeating a revocation is safe. Revoked sessions fail authentication on the
|
||||
next request, including when a principal summary was previously cached.
|
||||
|
||||
The shared WebUI clears reusable API response data on explicit authentication,
|
||||
account, tenant, and permission transitions, changed session/CSRF cookies, and
|
||||
authentication-expiry responses. Late reads cannot repopulate caches after those
|
||||
transitions or after a write finishes. `no-store` responses are not retained;
|
||||
`no-cache` responses require server revalidation, with ETags retained only where
|
||||
storage is allowed. Reload bypasses older cached responses. These safeguards do
|
||||
not erase content already displayed by a page: reload that page to reflect
|
||||
remote changes. The server remains authoritative for every permission check.
|
||||
|
||||
Successful interactive sign-in, including re-login, and local sign-out clear
|
||||
the saved automation API key. It must not shadow the newly established cookie
|
||||
session with a different principal. Explicitly applying an API key in connection
|
||||
settings still selects that credential's identity and triggers a new shell
|
||||
authentication check. Ordinary profile updates in API-key mode retain the key.
|
||||
|
||||
Tenant administrators may list sessions only for a membership in their governed
|
||||
tenant and may revoke only a session belonging to that membership and tenant.
|
||||
The mutation requires both the central membership-update permission and an
|
||||
|
||||
Reference in New Issue
Block a user