stillbox/pkg/rbac/rbac.go

173 lines
3.2 KiB
Go
Raw Normal View History

package rbac
import (
"context"
"errors"
"github.com/el-mike/restrict/v2"
"github.com/el-mike/restrict/v2/adapters"
)
var (
ErrBadSubject = errors.New("bad subject in token")
)
type subjectContextKey string
const SubjectCtxKey subjectContextKey = "sub"
func CtxWithSubject(ctx context.Context, sub Subject) context.Context {
return context.WithValue(ctx, SubjectCtxKey, sub)
}
func ErrAccessDenied(err error) *restrict.AccessDeniedError {
if accessErr, ok := err.(*restrict.AccessDeniedError); ok {
return accessErr
}
return nil
}
func SubjectFrom(ctx context.Context) Subject {
sub, ok := ctx.Value(SubjectCtxKey).(Subject)
if ok {
return sub
}
return new(PublicSubject)
}
type rbacCtxKey string
const RBACCtxKey rbacCtxKey = "rbac"
func FromCtx(ctx context.Context) RBAC {
rbac, ok := ctx.Value(RBACCtxKey).(RBAC)
if !ok {
panic("no RBAC in context")
}
return rbac
}
func CtxWithRBAC(ctx context.Context, rbac RBAC) context.Context {
return context.WithValue(ctx, RBACCtxKey, rbac)
}
var (
ErrNotAuthorized = errors.New("not authorized")
)
type checkOptions struct {
actions []string
context restrict.Context
}
type CheckOption func(*checkOptions)
func WithActions(actions ...string) CheckOption {
return func(o *checkOptions) {
o.actions = append(o.actions, actions...)
}
}
func WithContext(ctx restrict.Context) CheckOption {
return func(o *checkOptions) {
o.context = ctx
}
}
func UseResource(rsc string) restrict.Resource {
return restrict.UseResource(rsc)
}
type Subject interface {
restrict.Subject
GetName() string
}
type Resource interface {
restrict.Resource
}
type RBAC interface {
Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error)
}
type rbac struct {
policy *restrict.PolicyManager
access *restrict.AccessManager
}
func New() (*rbac, error) {
2025-01-20 22:38:27 -05:00
adapter := adapters.NewInMemoryAdapter(Policy)
polMan, err := restrict.NewPolicyManager(adapter, true)
if err != nil {
return nil, err
}
accMan := restrict.NewAccessManager(polMan)
return &rbac{
policy: polMan,
access: accMan,
}, nil
}
// Check is a convenience function to pull the RBAC instance out of ctx and Check.
func Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error) {
return FromCtx(ctx).Check(ctx, res, opts...)
}
func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error) {
sub := SubjectFrom(ctx)
o := checkOptions{}
for _, opt := range opts {
opt(&o)
}
2025-01-20 22:38:27 -05:00
if o.context == nil {
o.context = make(restrict.Context)
}
o.context["ctx"] = ctx
req := &restrict.AccessRequest{
Subject: sub,
Resource: res,
Actions: o.actions,
Context: o.context,
}
2025-01-21 09:21:46 -05:00
err := r.access.Authorize(req)
if err != nil {
return nil, err
}
return sub, nil
}
type PublicSubject struct {
RemoteAddr string
}
func (s *PublicSubject) GetName() string {
return "PUBLIC:" + s.RemoteAddr
}
func (s *PublicSubject) GetRoles() []string {
return []string{RolePublic}
}
type SystemServiceSubject struct {
Name string
}
func (s *SystemServiceSubject) GetName() string {
return "SYSTEM:" + s.Name
}
func (s *SystemServiceSubject) GetRoles() []string {
return []string{RoleSystem}
}