Add general suport for new role types
This commit is contained in:
@@ -288,6 +288,7 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
|
||||
Storage: app.Opt.ObjStore,
|
||||
Template: app.Opt.Template,
|
||||
Auth: app.Opt.Auth,
|
||||
RBAC: app.Opt.RBAC,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
Generated
+41
@@ -0,0 +1,41 @@
|
||||
package options
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
// Definitions file that controls how this file is generated:
|
||||
// pkg/options/RBAC.yaml
|
||||
|
||||
type (
|
||||
RBACOpt struct {
|
||||
ServiceUser string `env:"RBAC_SERVICE_USER"`
|
||||
BypassRoles string `env:"RBAC_BYPASS_ROLES"`
|
||||
AuthenticatedRoles string `env:"RBAC_AUTHENTICATED_ROLES"`
|
||||
AnonymousRoles string `env:"RBAC_ANONYMOUS_ROLES"`
|
||||
}
|
||||
)
|
||||
|
||||
// RBAC initializes and returns a RBACOpt with default values
|
||||
func RBAC() (o *RBACOpt) {
|
||||
o = &RBACOpt{
|
||||
ServiceUser: "corteza",
|
||||
BypassRoles: "superadmin",
|
||||
AuthenticatedRoles: "authenticated",
|
||||
AnonymousRoles: "anonymous",
|
||||
}
|
||||
|
||||
fill(o)
|
||||
|
||||
// Function that allows access to custom logic inside the parent function.
|
||||
// The custom logic in the other file should be like:
|
||||
// func (o *RBAC) Defaults() {...}
|
||||
func(o interface{}) {
|
||||
if def, ok := o.(interface{ Defaults() }); ok {
|
||||
def.Defaults()
|
||||
}
|
||||
}(o)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
docs:
|
||||
title: RBAC options
|
||||
|
||||
props:
|
||||
# - name: enabled
|
||||
# type: bool
|
||||
# default: true
|
||||
# description: When disabled all operations on all resources are allowed
|
||||
|
||||
- name: serviceUser
|
||||
default: "corteza"
|
||||
description: |-
|
||||
Used at boot time, for provisioning and other service-level operations.
|
||||
Service user is automatically member of all bypass roles.
|
||||
|
||||
- name: bypassRoles
|
||||
default: "superadmin"
|
||||
description: |-
|
||||
Space delimited list of role handles.
|
||||
These roles causes short-circuiting access control check and allowing all operations.
|
||||
System will refuse to start if check-bypassing roles are also listed as authenticated or anonymous auto-assigned roles.
|
||||
|
||||
- name: authenticatedRoles
|
||||
default: "authenticated"
|
||||
description: |-
|
||||
Space delimited list of role handles.
|
||||
These roles are automatically assigned to authenticated user.
|
||||
Memberships can not be managed for these roles.
|
||||
System will refuse to start if roles listed here are also listed under anonymous roles
|
||||
|
||||
- name: anonymousRoles
|
||||
default: "anonymous"
|
||||
description: |-
|
||||
Space delimited list of role handles.
|
||||
These roles are automatically assigned to anonymous user.
|
||||
Memberships can not be managed for these roles.
|
||||
|
||||
@@ -23,6 +23,7 @@ type (
|
||||
Federation FederationOpt
|
||||
SCIM SCIMOpt
|
||||
Workflow WorkflowOpt
|
||||
RBAC RBACOpt
|
||||
}
|
||||
)
|
||||
|
||||
@@ -49,5 +50,6 @@ func Init() *Options {
|
||||
Federation: *Federation(),
|
||||
SCIM: *SCIM(),
|
||||
Workflow: *Workflow(),
|
||||
RBAC: *RBAC(),
|
||||
}
|
||||
}
|
||||
|
||||
+20
-3
@@ -8,7 +8,7 @@ type (
|
||||
ctxRoleCheckFn func(map[string]interface{}) bool
|
||||
|
||||
// role information, adapted for the needs of RBAC package
|
||||
role struct {
|
||||
Role struct {
|
||||
// all RBAC rules refer to role ID
|
||||
id uint64
|
||||
|
||||
@@ -34,8 +34,25 @@ const (
|
||||
BypassRole
|
||||
)
|
||||
|
||||
func (k roleKind) Make(id uint64, handle string) *Role {
|
||||
return &Role{
|
||||
kind: k,
|
||||
id: id,
|
||||
handle: handle,
|
||||
}
|
||||
}
|
||||
|
||||
func MakeContextRole(id uint64, handle string, fn ctxRoleCheckFn) *Role {
|
||||
return &Role{
|
||||
kind: ContextRole,
|
||||
id: id,
|
||||
handle: handle,
|
||||
check: fn,
|
||||
}
|
||||
}
|
||||
|
||||
// partitions roles by kind
|
||||
func partitionRoles(rr ...*role) partRoles {
|
||||
func partitionRoles(rr ...*Role) partRoles {
|
||||
out := make([]map[uint64]bool, len(roleKindsByPriority()))
|
||||
for _, r := range rr {
|
||||
if out[r.kind] == nil {
|
||||
@@ -66,7 +83,7 @@ func roleKindsByPriority() []roleKind {
|
||||
}
|
||||
|
||||
// compare list of session roles (ids) with preloaded roles and calculate the final list
|
||||
func getContextRoles(sRoles []uint64, res Resource, preloadedRoles []*role) (out partRoles) {
|
||||
func getContextRoles(sRoles []uint64, res Resource, preloadedRoles []*Role) (out partRoles) {
|
||||
var (
|
||||
mm = slice.ToUint64BoolMap(sRoles)
|
||||
attr = make(map[string]interface{})
|
||||
|
||||
+13
-13
@@ -9,11 +9,11 @@ func Test_partitionRoles(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
pr = partitionRoles(
|
||||
&role{id: 1, kind: BypassRole},
|
||||
&role{id: 2, kind: BypassRole},
|
||||
&role{id: 3, kind: BypassRole},
|
||||
&role{id: 4, kind: ContextRole},
|
||||
&role{id: 5, kind: CommonRole},
|
||||
&Role{id: 1, kind: BypassRole},
|
||||
&Role{id: 2, kind: BypassRole},
|
||||
&Role{id: 3, kind: BypassRole},
|
||||
&Role{id: 4, kind: ContextRole},
|
||||
&Role{id: 5, kind: CommonRole},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -44,33 +44,33 @@ func Test_getContextRoles(t *testing.T) {
|
||||
name string
|
||||
sessionRoles []uint64
|
||||
res Resource
|
||||
preloadRoles []*role
|
||||
output []*role
|
||||
preloadRoles []*Role
|
||||
output []*Role
|
||||
}{
|
||||
{
|
||||
"existing role",
|
||||
[]uint64{1},
|
||||
nil,
|
||||
[]*role{{id: 1, kind: BypassRole}},
|
||||
[]*role{{id: 1, kind: BypassRole}},
|
||||
[]*Role{{id: 1, kind: BypassRole}},
|
||||
[]*Role{{id: 1, kind: BypassRole}},
|
||||
},
|
||||
{
|
||||
"missing role",
|
||||
[]uint64{2},
|
||||
nil,
|
||||
[]*role{{id: 1, kind: BypassRole}},
|
||||
[]*role{},
|
||||
[]*Role{{id: 1, kind: BypassRole}},
|
||||
[]*Role{},
|
||||
},
|
||||
{
|
||||
"dynamic role",
|
||||
[]uint64{1, 2},
|
||||
nil,
|
||||
[]*role{
|
||||
[]*Role{
|
||||
{id: 1, kind: BypassRole},
|
||||
{id: 2, kind: ContextRole, check: dyCheck(true)},
|
||||
{id: 3, kind: ContextRole, check: dyCheck(false)},
|
||||
},
|
||||
[]*role{{id: 1, kind: BypassRole}, {id: 2, kind: ContextRole}},
|
||||
[]*Role{{id: 1, kind: BypassRole}, {id: 2, kind: ContextRole}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ func Test_check(t *testing.T) {
|
||||
exp Access
|
||||
res string
|
||||
op string
|
||||
rr []*role
|
||||
rr []*Role
|
||||
set RuleSet
|
||||
}{
|
||||
{"inherit when no roles or rules",
|
||||
@@ -25,7 +25,7 @@ func Test_check(t *testing.T) {
|
||||
Allow,
|
||||
"",
|
||||
"",
|
||||
[]*role{
|
||||
[]*Role{
|
||||
{id: 1, kind: BypassRole},
|
||||
},
|
||||
nil,
|
||||
@@ -35,7 +35,7 @@ func Test_check(t *testing.T) {
|
||||
Inherit,
|
||||
"",
|
||||
"",
|
||||
[]*role{
|
||||
[]*Role{
|
||||
{id: 1, kind: CommonRole},
|
||||
},
|
||||
[]*Rule{
|
||||
@@ -47,7 +47,7 @@ func Test_check(t *testing.T) {
|
||||
Allow,
|
||||
"",
|
||||
"",
|
||||
[]*role{
|
||||
[]*Role{
|
||||
|
||||
{id: 1, kind: CommonRole},
|
||||
},
|
||||
@@ -72,12 +72,12 @@ func benchmarkCheck(b *testing.B, c int) {
|
||||
rules = make(RuleSet, 0, c)
|
||||
|
||||
pr = partitionRoles(
|
||||
&role{id: 1, kind: CommonRole},
|
||||
&role{id: 2, kind: CommonRole},
|
||||
&role{id: 3, kind: CommonRole},
|
||||
&role{id: 4, kind: CommonRole},
|
||||
&role{id: 5, kind: CommonRole},
|
||||
&role{id: 6, kind: CommonRole},
|
||||
&Role{id: 1, kind: CommonRole},
|
||||
&Role{id: 2, kind: CommonRole},
|
||||
&Role{id: 3, kind: CommonRole},
|
||||
&Role{id: 4, kind: CommonRole},
|
||||
&Role{id: 5, kind: CommonRole},
|
||||
&Role{id: 6, kind: CommonRole},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+26
-5
@@ -19,7 +19,7 @@ type (
|
||||
rules RuleSet
|
||||
indexed OptRuleSet
|
||||
|
||||
roles []*role
|
||||
roles []*Role
|
||||
|
||||
store rbacRulesStore
|
||||
}
|
||||
@@ -27,7 +27,7 @@ type (
|
||||
// RuleFilter is a dummy struct to satisfy store codegen
|
||||
RuleFilter struct{}
|
||||
|
||||
ControllerV2 interface {
|
||||
Controller interface {
|
||||
Can(ses Session, op string, res Resource) bool
|
||||
Check(ses Session, op string, res Resource) (v Access)
|
||||
Grant(ctx context.Context, rules ...*Rule) (err error)
|
||||
@@ -35,12 +35,19 @@ type (
|
||||
FindRulesByRoleID(roleID uint64) (rr RuleSet)
|
||||
Rules() (rr RuleSet)
|
||||
Reload(ctx context.Context)
|
||||
UpdateRoles(rr ...*Role)
|
||||
}
|
||||
|
||||
RoleSettings struct {
|
||||
Bypass []uint64
|
||||
Authenticated []uint64
|
||||
Anonymous []uint64
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// Global RBAC service
|
||||
gRBAC ControllerV2
|
||||
gRBAC Controller
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -48,11 +55,11 @@ const (
|
||||
)
|
||||
|
||||
// Global returns global RBAC service
|
||||
func Global() ControllerV2 {
|
||||
func Global() Controller {
|
||||
return gRBAC
|
||||
}
|
||||
|
||||
func SetGlobal(svc ControllerV2) {
|
||||
func SetGlobal(svc Controller) {
|
||||
gRBAC = svc
|
||||
}
|
||||
|
||||
@@ -168,7 +175,10 @@ func (svc service) Rules() (rr RuleSet) {
|
||||
func (svc *service) Reload(ctx context.Context) {
|
||||
svc.l.Lock()
|
||||
defer svc.l.Unlock()
|
||||
svc.reloadRules(ctx)
|
||||
}
|
||||
|
||||
func (svc *service) reloadRules(ctx context.Context) {
|
||||
rr, _, err := svc.store.SearchRbacRules(ctx, RuleFilter{})
|
||||
svc.logger.Debug(
|
||||
"reloading rules",
|
||||
@@ -182,6 +192,17 @@ func (svc *service) Reload(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *service) UpdateRoles(rr ...*Role) {
|
||||
svc.l.Lock()
|
||||
defer svc.l.Unlock()
|
||||
svc.logger.Debug(
|
||||
"updating roles",
|
||||
zap.Int("before", len(svc.rules)),
|
||||
zap.Int("after", len(rr)),
|
||||
)
|
||||
svc.roles = rr
|
||||
}
|
||||
|
||||
func (svc service) flush(ctx context.Context) (err error) {
|
||||
d, u := flushable(svc.rules)
|
||||
|
||||
|
||||
+63
-6
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/slice"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/label"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/system/service/event"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
@@ -25,6 +25,12 @@ type (
|
||||
user UserService
|
||||
|
||||
store store.Storer
|
||||
|
||||
// list of all system roles
|
||||
system map[string]bool
|
||||
|
||||
// list of all closed roles
|
||||
closed map[string]bool
|
||||
}
|
||||
|
||||
roleAccessController interface {
|
||||
@@ -57,7 +63,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func Role(ctx context.Context) *role {
|
||||
func Role() *role {
|
||||
return &role{
|
||||
ac: DefaultAccessControl,
|
||||
eventbus: eventbus.Service(),
|
||||
@@ -66,9 +72,28 @@ func Role(ctx context.Context) *role {
|
||||
|
||||
user: DefaultUser,
|
||||
store: DefaultStore,
|
||||
|
||||
system: make(map[string]bool),
|
||||
closed: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// SetImmutable sets list of handles for all system roles
|
||||
//
|
||||
// System roles can not be changed or deleted
|
||||
func (svc role) SetSystem(hh ...string) {
|
||||
svc.system = slice.ToStringBoolMap(hh)
|
||||
delete(svc.system, "")
|
||||
}
|
||||
|
||||
// SetClosed sets list of handles for all closed roles
|
||||
//
|
||||
// Closed roles can not have members
|
||||
func (svc role) SetClosed(hh ...string) {
|
||||
svc.closed = slice.ToStringBoolMap(hh)
|
||||
delete(svc.closed, "")
|
||||
}
|
||||
|
||||
func (svc role) Find(ctx context.Context, filter types.RoleFilter) (rr types.RoleSet, f types.RoleFilter, err error) {
|
||||
var (
|
||||
raProps = &roleActionProps{filter: &filter}
|
||||
@@ -291,6 +316,10 @@ func (svc role) Update(ctx context.Context, upd *types.Role) (r *types.Role, err
|
||||
return
|
||||
}
|
||||
|
||||
if svc.system[r.Handle] {
|
||||
return RoleErrNotAllowedToUpdate()
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if err = svc.eventbus.WaitFor(ctx, event.RoleBeforeUpdate(upd, r)); err != nil {
|
||||
@@ -359,6 +388,10 @@ func (svc role) Delete(ctx context.Context, roleID uint64) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if svc.system[r.Handle] {
|
||||
return RoleErrNotAllowedToDelete()
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanDeleteRole(ctx, r) {
|
||||
@@ -394,6 +427,10 @@ func (svc role) Undelete(ctx context.Context, roleID uint64) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if svc.system[r.Handle] {
|
||||
return RoleErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanDeleteRole(ctx, r) {
|
||||
@@ -423,6 +460,10 @@ func (svc role) Archive(ctx context.Context, roleID uint64) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if svc.system[r.Handle] {
|
||||
return RoleErrNotAllowedToArchive()
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanUpdateRole(ctx, r) {
|
||||
@@ -451,10 +492,14 @@ func (svc role) Unarchive(ctx context.Context, roleID uint64) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if svc.system[r.Handle] {
|
||||
return RoleErrNotAllowedToUnarchive()
|
||||
}
|
||||
|
||||
raProps.setRole(r)
|
||||
|
||||
if !svc.ac.CanDeleteRole(ctx, r) {
|
||||
return RoleErrNotAllowedToDelete()
|
||||
return RoleErrNotAllowedToUndelete()
|
||||
}
|
||||
|
||||
r.ArchivedAt = nil
|
||||
@@ -483,10 +528,14 @@ func (svc role) MemberList(ctx context.Context, roleID uint64) (mm types.RoleMem
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
if roleID == rbac.EveryoneRoleID || roleID == 0 {
|
||||
if roleID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if svc.closed[r.Handle] {
|
||||
return RoleErrNotAllowedToManageMembers()
|
||||
}
|
||||
|
||||
if r, err = svc.findByID(ctx, roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -515,10 +564,14 @@ func (svc role) MemberAdd(ctx context.Context, roleID, memberID uint64) (err err
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if roleID == rbac.EveryoneRoleID || roleID == 0 || memberID == 0 {
|
||||
if roleID == 0 || memberID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if svc.closed[r.Handle] {
|
||||
return RoleErrNotAllowedToManageMembers()
|
||||
}
|
||||
|
||||
if r, err = svc.findByID(ctx, roleID); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -562,10 +615,14 @@ func (svc role) MemberRemove(ctx context.Context, roleID, memberID uint64) (err
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if roleID == rbac.EveryoneRoleID || roleID == 0 || memberID == 0 {
|
||||
if roleID == 0 || memberID == 0 {
|
||||
return RoleErrInvalidID()
|
||||
}
|
||||
|
||||
if svc.closed[r.Handle] {
|
||||
return RoleErrNotAllowedToManageMembers()
|
||||
}
|
||||
|
||||
if r, err = svc.findByID(ctx, roleID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+153
-2
@@ -3,6 +3,11 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
automationService "github.com/cortezaproject/corteza-server/automation/service"
|
||||
@@ -32,6 +37,7 @@ type (
|
||||
Storage options.ObjectStoreOpt
|
||||
Template options.TemplateOpt
|
||||
Auth options.AuthOpt
|
||||
RBAC options.RBACOpt
|
||||
}
|
||||
|
||||
eventDispatcher interface {
|
||||
@@ -68,7 +74,7 @@ var (
|
||||
DefaultAuth *auth
|
||||
DefaultAuthClient *authClient
|
||||
DefaultUser UserService
|
||||
DefaultRole RoleService
|
||||
DefaultRole *role
|
||||
DefaultApplication *application
|
||||
DefaultReminder ReminderService
|
||||
DefaultAttachment AttachmentService
|
||||
@@ -160,7 +166,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
|
||||
DefaultAuth = Auth()
|
||||
DefaultAuthClient = AuthClient(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
|
||||
DefaultUser = User(ctx)
|
||||
DefaultRole = Role(ctx)
|
||||
DefaultRole = Role()
|
||||
DefaultApplication = Application(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
|
||||
DefaultReminder = Reminder(ctx, DefaultLogger.Named("reminder"), ws)
|
||||
DefaultSink = Sink()
|
||||
@@ -211,6 +217,151 @@ func Watchers(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Configures RBAC with roles
|
||||
//
|
||||
// Sets all closed & im
|
||||
func initRoles(ctx context.Context, log *zap.Logger, opt options.RBACOpt) (err error) {
|
||||
var (
|
||||
// splits space separated string into map
|
||||
s = func(l string) (map[string]bool, error) {
|
||||
m := make(map[string]bool)
|
||||
for _, r := range strings.Split(l, " ") {
|
||||
if r = strings.TrimSpace(r); len(r) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !handle.IsValid(r) {
|
||||
return nil, fmt.Errorf("invalid handle '%s'", r)
|
||||
}
|
||||
|
||||
m[r] = true
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// joins map keys into string slice
|
||||
j = func(mm ...map[string]bool) []string {
|
||||
o := make([]string, 0)
|
||||
|
||||
for _, m := range mm {
|
||||
for r := range m {
|
||||
o = append(o, r)
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
system, service, bypass, authenticated, anonymous map[string]bool
|
||||
)
|
||||
|
||||
if bypass, err = s(opt.BypassRoles); err != nil {
|
||||
return fmt.Errorf("failed to process list of bypass roles (RBAC_BYPASS_ROLES): %w", err)
|
||||
}
|
||||
if authenticated, err = s(opt.AuthenticatedRoles); err != nil {
|
||||
return fmt.Errorf("failed to process list of authenticated roles (RBAC_AUTHENTICATED_ROLES): %w", err)
|
||||
}
|
||||
if anonymous, err = s(opt.AnonymousRoles); err != nil {
|
||||
return fmt.Errorf("failed to process list of anonymous roles (RBAC_ANONYMOUS_ROLES): %w", err)
|
||||
}
|
||||
|
||||
if len(service) != 1 {
|
||||
return fmt.Errorf("role %s used for authenticated users can not be used as bypass role")
|
||||
}
|
||||
|
||||
for r := range authenticated {
|
||||
if bypass[r] {
|
||||
return fmt.Errorf("role %s used for authenticated users must not be used as bypass role")
|
||||
}
|
||||
}
|
||||
|
||||
for r := range anonymous {
|
||||
if bypass[r] {
|
||||
return fmt.Errorf("role %s used for anonymous users must not be used as bypass role")
|
||||
}
|
||||
|
||||
if authenticated[r] {
|
||||
return fmt.Errorf("role %s used for anonymous users must not be used as bypass role")
|
||||
}
|
||||
}
|
||||
|
||||
DefaultRole.SetSystem(j(system)...)
|
||||
DefaultRole.SetClosed(j(authenticated, anonymous)...)
|
||||
|
||||
// Hook to role create, update & delete events and
|
||||
// re-apply all roles to RBAC
|
||||
eventbus.Service().Register(
|
||||
func(_ context.Context, ev eventbus.Event) (err error) {
|
||||
var (
|
||||
p = expr.NewParser()
|
||||
f = types.RoleFilter{}
|
||||
rr []*rbac.Role
|
||||
)
|
||||
|
||||
f.Paging.Total = 0
|
||||
roles, _, err := DefaultStore.SearchRoles(ctx, f)
|
||||
for _, r := range roles {
|
||||
log := log.With(
|
||||
zap.Uint64("id", r.ID),
|
||||
zap.String("handle", r.Handle),
|
||||
zap.String("expr", r.Meta.ContextExpr),
|
||||
)
|
||||
|
||||
switch {
|
||||
case bypass[r.Handle]:
|
||||
rr = append(rr, rbac.BypassRole.Make(r.ID, r.Handle))
|
||||
|
||||
case anonymous[r.Handle]:
|
||||
rr = append(rr, rbac.AnonymousRole.Make(r.ID, r.Handle))
|
||||
|
||||
case authenticated[r.Handle]:
|
||||
rr = append(rr, rbac.AuthenticatedRole.Make(r.ID, r.Handle))
|
||||
|
||||
case r.Meta != nil && len(r.Meta.ContextExpr) > 0:
|
||||
log := log.With(zap.String("expr", r.Meta.ContextExpr))
|
||||
eval, err := p.Parse(r.Meta.ContextExpr)
|
||||
if err != nil {
|
||||
log.Error("failed to parse role context expression", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
check := func(s map[string]interface{}) bool {
|
||||
vars, err := expr.NewVars(s)
|
||||
if err != nil {
|
||||
log.Error("failed to convert check scope to expr.Vars", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
test, err := eval.Test(ctx, vars)
|
||||
if err != nil {
|
||||
log.Error("failed to evaluate role context expression", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
return test
|
||||
}
|
||||
|
||||
rr = append(rr, rbac.MakeContextRole(r.ID, r.Handle, check))
|
||||
|
||||
default:
|
||||
rr = append(rr, rbac.CommonRole.Make(r.ID, r.Handle))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("role changed " + ev.EventType())
|
||||
rbac.Global().UpdateRoles(rr...)
|
||||
|
||||
return nil
|
||||
},
|
||||
eventbus.For("system:role"),
|
||||
eventbus.On("afterUpdate", "afterCreate", "afterDelete"),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isGeneric returns true if given error is generic
|
||||
func isGeneric(err error) bool {
|
||||
g, ok := err.(interface{ IsGeneric() bool })
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"time"
|
||||
)
|
||||
@@ -10,6 +13,7 @@ type (
|
||||
ID uint64 `json:"roleID,string"`
|
||||
Name string `json:"name"`
|
||||
Handle string `json:"handle"`
|
||||
Meta *RoleMeta
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
@@ -19,6 +23,11 @@ type (
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
RoleMeta struct {
|
||||
Description string
|
||||
ContextExpr string
|
||||
}
|
||||
|
||||
RoleFilter struct {
|
||||
RoleID []uint64 `json:"roleID"`
|
||||
MemberID uint64 `json:"memberID"`
|
||||
@@ -71,3 +80,27 @@ func (set RoleSet) FindByHandle(handle string) *Role {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vv *RoleMeta) Scan(value interface{}) error {
|
||||
//lint:ignore S1034 This typecast is intentional, we need to get []byte out of a []uint8
|
||||
switch value.(type) {
|
||||
case nil:
|
||||
*vv = RoleMeta{}
|
||||
case []uint8:
|
||||
b := value.([]byte)
|
||||
if err := json.Unmarshal(b, vv); err != nil {
|
||||
return fmt.Errorf("cannot scan '%v' into RoleMeta: %w", string(b), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scan on RoleMeta gracefully handles conversion from NULL
|
||||
func (vv *RoleMeta) Value() (driver.Value, error) {
|
||||
if vv == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
return json.Marshal(vv)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user