2025-01-18 17:22:08 -05:00
|
|
|
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)
|
2025-01-18 17:22:08 -05:00
|
|
|
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
|
|
|
|
|
2025-01-18 17:22:08 -05:00
|
|
|
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
|
2025-01-18 17:22:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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}
|
|
|
|
}
|