# Berechtigungssystem Plan Dieser Plan beschreibt die Implementierung eines scope-basierten Berechtigungssystems für das TimeTracker-Projekt. ## Grundkonzept - Ein **Benutzer** kann eine **Rolle** annehmen, aber immer nur eine ist aktiv. - Eine **Rolle** besteht aus mehreren **Policies**. - Eine **Policy** hat einen Namen und eine Map, die **Scopes** (z. B. `items/books`) einem **Berechtigungsschlüssel** (Bitflag) zuordnet. - Berechtigungsschlüssel sind Bitflags, die Permissions wie `read`, `write`, `create`, `list`, `delete`, `moderate`, `superadmin` usw. repräsentieren. - Scopes können **Wildcards** enthalten, z. B. `items/*`, das auf `items/books` vererbt wird. - Ziel: Berechtigungen sowohl im Go-Backend (für API-Sicherheit) als auch im TypeScript-Frontend (für UI-Anpassung) evaluieren. ## Implementierung im Go-Backend ### 1. Ordnerstruktur - Neuer Ordner: `backend/internal/permissions` - Dateien: - `permissions.go`: `Permission`-Konstanten (Bitflags). - `policy.go`: `Policy`-Struktur. - `role.go`: `Role`-Struktur. - `user.go`: Erweiterung der `User`-Struktur. - `matching.go`: `matchScope`-Funktion. - `evaluator.go`: `EffectivePermissions`- und `HasPermission`-Funktionen. ### 2. Go-Strukturen - `permissions.go`: ```go package permissions type Permission uint64 const ( PermRead Permission = 1 << iota // 1 PermWrite // 2 PermCreate // 4 PermList // 8 PermDelete // 16 PermModerate // 32 PermSuperadmin // 64 ) ``` - `policy.go`: ```go package permissions type Policy struct { Name string Scopes map[string]Permission } ``` - `role.go`: ```go package permissions type Role struct { Name string Policies []Policy } ``` - `user.go`: ```go package permissions import "github.com/your-org/your-project/backend/internal/models" // Pfad anpassen type User struct { models.User // Einbettung ActiveRole *Role } ``` ### 3. Funktionen - `matching.go`: ```go package permissions import "strings" func MatchScope(pattern, scope string) bool { if strings.HasSuffix(pattern, "/*") { prefix := strings.TrimSuffix(pattern, "/*") return strings.HasPrefix(scope, prefix) } return pattern == scope } ``` - `evaluator.go`: ```go package permissions func (u *User) EffectivePermissions(scope string) Permission { if u.ActiveRole == nil { return 0 } var perm Permission for _, policy := range u.ActiveRole.Policies { for pat, p := range policy.Scopes { if MatchScope(pat, scope) { perm |= p } } } return perm } func (u *User) HasPermission(scope string, requiredPerm Permission) bool { effective := u.EffectivePermissions(scope) return (effective & requiredPerm) == requiredPerm } ``` ### 4. Integration in die API-Handler - Anpassung der `jwt_auth.go` Middleware. - Verwendung von `HasPermission` in den API-Handlern. ## Persistierung (Datenbank) ### 1. Datenbankmodell - Zwei neue Tabellen: `roles` und `policies`. - `roles`: - `id` (ULID, Primärschlüssel) - `name` (VARCHAR, eindeutig) - `policies`: - `id` (ULID, Primärschlüssel) - `name` (VARCHAR, eindeutig) - `role_id` (ULID, Fremdschlüssel, der auf `roles.id` verweist) - `scopes` (JSONB oder TEXT, speichert die `map[string]Permission` als JSON) - Beziehung: 1:n zwischen `roles` und `policies`. ### 2. Go-Strukturen (Anpassungen) - `role.go`: ```go package permissions import ( "github.com/your-org/your-project/backend/internal/types" // Pfad anpassen ) type Role struct { ID types.ULID `gorm:"primaryKey;type:bytea"` Name string `gorm:"unique;not null"` Policies []Policy `gorm:"foreignKey:RoleID"` } ``` - `policy.go`: ```go package permissions import ( "encoding/json" "database/sql/driver" "errors" "fmt" "github.com/your-org/your-project/backend/internal/types" // Pfad anpassen "gorm.io/gorm" "gorm.io/gorm/clause" ) type Policy struct { ID types.ULID `gorm:"primaryKey;type:bytea"` Name string `gorm:"not null"` RoleID types.ULID `gorm:"type:bytea"` //Fremdschlüssel Scopes Scopes `gorm:"type:jsonb;not null"` // JSONB-Spalte } //Scopes type to handle JSON marshalling type Scopes map[string]Permission // Scan scan value into Jsonb, implements sql.Scanner interface func (j *Scopes) Scan(value interface{}) error { bytes, ok := value.([]byte) if !ok { return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value)) } var scopes map[string]Permission if err := json.Unmarshal(bytes, &scopes); err != nil { return err } *j = scopes return nil } // Value return json value, implement driver.Valuer interface func (j Scopes) Value() (driver.Value, error) { return json.Marshal(j) } ``` ### 3. Migration - Verwendung des vorhandenen Migrations-Frameworks (`backend/cmd/migrate/main.go`). ### 4. Seed-Daten - Optionale Seed-Daten (`backend/cmd/seed/main.go`). ### 5. Anpassung der Funktionen - Anpassung von `EffectivePermissions` und `HasPermission` in `evaluator.go` für Datenbankzugriff. ## Mermaid Diagramm ```mermaid graph LR subgraph Benutzer U[User] --> AR(ActiveRole) end subgraph Rolle AR --> R(Role) R --> P1(Policy 1) R --> P2(Policy 2) R --> Pn(Policy n) end subgraph Policy P1 --> S1(Scope 1: Permissions) P1 --> S2(Scope 2: Permissions) P2 --> S3(Scope 3: Permissions) Pn --> Sm(Scope m: Permissions) end S1 -- Permissions --> PR(PermRead) S1 -- Permissions --> PW(PermWrite) S2 -- Permissions --> PL(PermList) Sm -- Permissions --> PD(PermDelete) style U fill:#f9f,stroke:#333,stroke-width:2px style R fill:#ccf,stroke:#333,stroke-width:2px style P1,P2,Pn fill:#ddf,stroke:#333,stroke-width:2px style S1,S2,S3,Sm fill:#eef,stroke:#333,stroke-width:1px style PR,PW,PL,PD fill:#ff9,stroke:#333,stroke-width:1px