fix: remove backward compat, migrate old access to proper RBAC grants

Removed the backward compatibility fallback that checked the old
dossier_access table from CanManageDossier/CanAccessDossier - it was
a security risk (hidden path that bypassed the new RBAC system).

Instead, added MigrateOldAccess() that converts existing dossier_access
entries to proper access grants on startup (idempotent - skips existing).

Migration rules:
- Self-references (accessor == target) skipped (owner access is automatic)
- can_edit = 1 → "rwdm" root grant
- can_edit = 0 → "r" root grant
- Role set to "Migrated" for tracking

Result: 12 grants migrated from old table.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
James 2026-02-07 23:27:23 -05:00
parent 08139ada28
commit 7192f39bc1
3 changed files with 49 additions and 32 deletions

View File

@ -329,43 +329,13 @@ func EnsureCategoryEntry(dossierID string, category int) (string, error) {
} }
// CanAccessDossier returns true if accessor can read dossier (for quick checks) // CanAccessDossier returns true if accessor can read dossier (for quick checks)
// Falls back to old dossier_access for backward compatibility
func CanAccessDossier(accessorID, dossierID string) bool { func CanAccessDossier(accessorID, dossierID string) bool {
// Check new RBAC system first return CheckAccess(accessorID, dossierID, "", 'r') == nil
if CheckAccess(accessorID, dossierID, "", 'r') == nil {
return true
}
// Fallback: check old dossier_access table
var result []struct {
Status int `db:"status"`
}
err := Query(
"SELECT status FROM dossier_access WHERE accessor_dossier_id = ? AND target_dossier_id = ? AND status = 1",
[]any{accessorID, dossierID},
&result,
)
return err == nil && len(result) > 0 && result[0].Status == 1
} }
// CanManageDossier returns true if accessor can manage permissions for dossier // CanManageDossier returns true if accessor can manage permissions for dossier
// Falls back to old dossier_access.can_edit for backward compatibility
func CanManageDossier(accessorID, dossierID string) bool { func CanManageDossier(accessorID, dossierID string) bool {
// Check new RBAC system first return CheckAccess(accessorID, dossierID, "", 'm') == nil
if CheckAccess(accessorID, dossierID, "", 'm') == nil {
return true
}
// Fallback: check old dossier_access table
var result []struct {
CanEdit int `db:"can_edit"`
}
err := Query(
"SELECT can_edit FROM dossier_access WHERE accessor_dossier_id = ? AND target_dossier_id = ? AND status = 1",
[]any{accessorID, dossierID},
&result,
)
return err == nil && len(result) > 0 && result[0].CanEdit == 1
} }
// GrantAccess creates an access grant // GrantAccess creates an access grant

View File

@ -800,6 +800,47 @@ func AccessGrantRemove(ids ...string) error {
return nil return nil
} }
// MigrateOldAccess converts dossier_access entries to access grants (one-time migration)
func MigrateOldAccess() int {
type oldAccess struct {
AccessorID string `db:"accessor_dossier_id"`
TargetID string `db:"target_dossier_id"`
CanEdit int `db:"can_edit"`
}
var entries []oldAccess
err := Query("SELECT accessor_dossier_id, target_dossier_id, can_edit FROM dossier_access WHERE status = 1", nil, &entries)
if err != nil {
return 0
}
migrated := 0
for _, e := range entries {
// Skip self-references (owner access is automatic)
if e.AccessorID == e.TargetID {
continue
}
// Skip if grant already exists
existing, _ := AccessGrantList(&PermissionFilter{DossierID: e.TargetID, GranteeID: e.AccessorID})
if len(existing) > 0 {
continue
}
// Create root-level grant
ops := "r"
if e.CanEdit == 1 {
ops = "rwdm"
}
AccessGrantWrite(&Access{
DossierID: e.TargetID,
GranteeID: e.AccessorID,
EntryID: "",
Role: "Migrated",
Ops: ops,
})
migrated++
}
return migrated
}
// AccessGrantGet retrieves a single access grant by ID // AccessGrantGet retrieves a single access grant by ID
func AccessGrantGet(id string) (*Access, error) { func AccessGrantGet(id string) (*Access, error) {
a := &Access{} a := &Access{}

View File

@ -2097,6 +2097,12 @@ func main() {
fmt.Printf("Warning: could not ensure bridge client: %v\n", err) fmt.Printf("Warning: could not ensure bridge client: %v\n", err)
} }
fmt.Println("lib.DBInit successful") fmt.Println("lib.DBInit successful")
lib.ConfigInit()
// Migrate old dossier_access to new RBAC grants (idempotent)
if n := lib.MigrateOldAccess(); n > 0 {
fmt.Printf("Migrated %d access grants from dossier_access\n", n)
}
loadTranslations() loadTranslations()
lib.TranslateInit("lang") // also init lib translations for CategoryTranslate lib.TranslateInit("lang") // also init lib translations for CategoryTranslate