Files
oslstats/internal/rbac/checker.go
2026-02-14 19:48:59 +11:00

137 lines
3.5 KiB
Go

package rbac
import (
"context"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/contexts"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/permissions"
"git.haelnorr.com/h/oslstats/internal/roles"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
type Checker struct {
conn *db.DB
s *hws.Server
}
func NewChecker(conn *db.DB, s *hws.Server) (*Checker, error) {
if conn == nil {
return nil, errors.New("conn cannot be nil")
}
if s == nil {
return nil, errors.New("server cannot be nil")
}
return &Checker{conn: conn, s: s}, nil
}
// UserHasPermission checks if user has a specific permission (uses cache)
func (c *Checker) UserHasPermission(ctx context.Context, user *db.User, permission permissions.Permission) (bool, error) {
if user == nil {
return false, nil
}
// Check if we're in preview mode
previewRole := contexts.GetPreviewRole(ctx)
// Try cache first
cache := contexts.Permissions(ctx)
if cache != nil {
if cache.HasWildcard {
return true, nil
}
if has, exists := cache.Permissions[permission]; exists {
return has, nil
}
}
// If in preview mode, DO NOT fallback to database - use ONLY preview role permissions
// This ensures admins cannot bypass preview mode restrictions
if previewRole != nil {
// Not in cache and in preview mode = permission denied
return false, nil
}
// Not in preview mode: fallback to database for actual user permissions
var has bool
if err := c.conn.WithTxFailSilently(ctx, func(ctx context.Context, tx bun.Tx) error {
var err error
has, err = user.HasPermission(ctx, tx, permission)
if err != nil {
return errors.Wrap(err, "user.HasPermission")
}
return nil
}); err != nil {
return false, err
}
return has, nil
}
// UserHasRole checks if user has a specific role (uses cache)
func (c *Checker) UserHasRole(ctx context.Context, user *db.User, role roles.Role) (bool, error) {
if user == nil {
return false, nil
}
// Check if we're in preview mode
previewRole := contexts.GetPreviewRole(ctx)
cache := contexts.Permissions(ctx)
if cache != nil {
if has, exists := cache.Roles[role]; exists {
return has, nil
}
}
// If in preview mode, DO NOT fallback to database - use ONLY preview role
// This ensures admins cannot bypass preview mode restrictions
if previewRole != nil {
// Not in cache and in preview mode = role not assigned
return false, nil
}
// Not in preview mode: fallback to database for actual user roles
var has bool
if err := c.conn.WithTxFailSilently(ctx, func(ctx context.Context, tx bun.Tx) error {
var err error
has, err = user.HasRole(ctx, tx, role)
if err != nil {
return errors.Wrap(err, "user.HasRole")
}
return nil
}); err != nil {
return false, err
}
return has, nil
}
// UserHasAnyPermission checks if user has ANY of the given permissions
func (c *Checker) UserHasAnyPermission(ctx context.Context, user *db.User, permissions ...permissions.Permission) (bool, error) {
for _, perm := range permissions {
has, err := c.UserHasPermission(ctx, user, perm)
if err != nil {
return false, err
}
if has {
return true, nil
}
}
return false, nil
}
// UserHasAllPermissions checks if user has ALL of the given permissions
func (c *Checker) UserHasAllPermissions(ctx context.Context, user *db.User, permissions ...permissions.Permission) (bool, error) {
for _, perm := range permissions {
has, err := c.UserHasPermission(ctx, user, perm)
if err != nil {
return false, err
}
if !has {
return false, nil
}
}
return true, nil
}