6.0 KiB
6.0 KiB
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 aufitems/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 derUser
-Struktur.matching.go
:matchScope
-Funktion.evaluator.go
:EffectivePermissions
- undHasPermission
-Funktionen.
2. Go-Strukturen
permissions.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
:
package permissions
type Policy struct {
Name string
Scopes map[string]Permission
}
role.go
:
package permissions
type Role struct {
Name string
Policies []Policy
}
user.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
:
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
:
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
undpolicies
. roles
:id
(ULID, Primärschlüssel)name
(VARCHAR, eindeutig)
policies
:id
(ULID, Primärschlüssel)name
(VARCHAR, eindeutig)role_id
(ULID, Fremdschlüssel, der aufroles.id
verweist)scopes
(JSONB oder TEXT, speichert diemap[string]Permission
als JSON)
- Beziehung: 1:n zwischen
roles
undpolicies
.
2. Go-Strukturen (Anpassungen)
role.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
:
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
undHasPermission
inevaluator.go
für Datenbankzugriff.
Mermaid Diagramm
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