This commit is contained in:
2025-03-31 19:07:30 +00:00
parent 21c9233058
commit fcdeedf7e9
21 changed files with 634 additions and 168 deletions
+35
View File
@@ -0,0 +1,35 @@
package permissions
import (
"context"
)
func (u *User) EffectivePermissions(ctx context.Context, scope string) (Permission, error) {
if u.ActiveRole == nil {
return 0, nil
}
// Load the role and its associated policies using the helper function.
role, err := LoadRoleWithPolicies(ctx, u.ActiveRole.ID)
if err != nil {
return 0, err
}
var perm Permission
for _, policy := range role.Policies {
for pat, p := range policy.Scopes {
if MatchScope(pat, scope) {
perm |= p
}
}
}
return perm, nil
}
func (u *User) HasPermission(ctx context.Context, scope string, requiredPerm Permission) (bool, error) {
effective, err := u.EffectivePermissions(ctx, scope)
if err != nil {
return false, err
}
return (effective & requiredPerm) == requiredPerm, nil
}
+23
View File
@@ -0,0 +1,23 @@
package permissions
import (
"context"
"fmt"
"github.com/oklog/ulid/v2"
"github.com/timetracker/backend/internal/db"
"gorm.io/gorm"
)
// LoadRoleWithPolicies loads a role with its associated policies from the database.
func LoadRoleWithPolicies(ctx context.Context, roleID ulid.ULID) (*Role, error) {
var role Role
err := db.GetEngine(ctx).Preload("Policies").First(&role, "id = ?", roleID).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("role with ID %s not found", roleID)
}
return nil, fmt.Errorf("failed to load role: %w", err)
}
return &role, nil
}
+11
View File
@@ -0,0 +1,11 @@
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
}
@@ -0,0 +1,13 @@
package permissions
type Permission uint64
const (
PermRead Permission = 1 << iota // 1
PermWrite // 2
PermCreate // 4
PermList // 8
PermDelete // 16
PermModerate // 32
PermSuperadmin // 64
)
+40
View File
@@ -0,0 +1,40 @@
package permissions
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"github.com/oklog/ulid/v2"
)
type Policy struct {
ID ulid.ULID `gorm:"primaryKey;type:bytea"`
Name string `gorm:"not null"`
RoleID ulid.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)
}
+11
View File
@@ -0,0 +1,11 @@
package permissions
import (
"github.com/oklog/ulid/v2"
)
type Role struct {
ID ulid.ULID `gorm:"primaryKey;type:bytea"`
Name string `gorm:"unique;not null"`
Policies []Policy `gorm:"foreignKey:RoleID"`
}
+10
View File
@@ -0,0 +1,10 @@
package permissions
import (
"github.com/oklog/ulid/v2"
)
type User struct {
ActiveRole *Role `gorm:"foreignKey:UserID"` // Beziehung zur aktiven Rolle
UserID ulid.ULID `gorm:"type:bytea"` //Fremdschlüssel
}