wip
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user