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 }