-- PROJ: permission-model-group-acl -- Doku-only (siehe README.md in diesem Verzeichnis) — die tatsächliche -- Ausführung passiert idempotent über internal/storage/permissions.go -- initPermissionsSchema(), aufgerufen aus (*Store).New(). -- -- Berechtigungsmodell (gruppenaufgelöste, geschichtete Dokument-ACL): -- Zugriff wird NIE direkt pro User vergeben, sondern über permission_groups. -- Auflösung über drei Ebenen, spezifischer schlägt allgemeiner: -- 1. document_grants (pro Dokument, 'deny' entfernt eine Gruppe komplett) -- 2. tag_grants (über die Tags des Dokuments) -- 3. document_type_grants (über documents.doc_type_id) -- Ergebnis wird von RecomputeVisibility() nach document_visibility -- materialisiert (DELETE+INSERT je Dokument in einer Transaktion). -- -- Rollen bleiben Außengrenze: superadmin sieht alles tenant-übergreifend, -- domain_admin sieht per Default alles im eigenen Tenant (bypasst ACL), nur -- Rolle 'user' wird in ListDocuments gegen document_visibility gefiltert. -- -- RecomputeVisibility MUSS neu laufen, wenn sich Grants, die Tags eines -- Dokuments (AttachTag/DetachTag) oder der Dokumenttyp (SetDocumentDocType) -- ändern — diese Aufrufe sind im Go-Code angehängt. -- Hinweis: tenant_id / user_id / granted_by tragen bewusst KEINE FK auf -- tenants(id) bzw. users(id) — konsistent mit documents/taxonomy (plain BIGINT -- tenant_id) und weil tenants/users von anderen Stores NACH storage.New() -- angelegt werden (Reihenfolge in cmd/archivdms/main.go). Tenant-Ownership wird -- applikationsseitig geprüft (permissions.go). CREATE TABLE IF NOT EXISTS permission_groups ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, name) ); CREATE TABLE IF NOT EXISTS permission_group_members ( group_id BIGINT NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, user_id BIGINT NOT NULL, PRIMARY KEY (group_id, user_id) ); CREATE TABLE IF NOT EXISTS document_type_grants ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, doc_type_id BIGINT NOT NULL REFERENCES document_types(id) ON DELETE CASCADE, group_id BIGINT NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, access TEXT NOT NULL DEFAULT 'read' CHECK (access IN ('read','write')), UNIQUE (doc_type_id, group_id) ); CREATE TABLE IF NOT EXISTS tag_grants ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE, group_id BIGINT NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, access TEXT NOT NULL DEFAULT 'read' CHECK (access IN ('read','write')), UNIQUE (tag_id, group_id) ); CREATE TABLE IF NOT EXISTS document_grants ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE, group_id BIGINT NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, access TEXT NOT NULL CHECK (access IN ('read','write','deny')), granted_by BIGINT NOT NULL, granted_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (document_id, group_id) ); CREATE TABLE IF NOT EXISTS document_visibility ( document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE, group_id BIGINT NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE, access TEXT NOT NULL CHECK (access IN ('read','write')), PRIMARY KEY (document_id, group_id) ); CREATE INDEX IF NOT EXISTS idx_doc_visibility_group ON document_visibility(group_id, document_id); CREATE INDEX IF NOT EXISTS idx_pgm_user ON permission_group_members(user_id, group_id);