Add RBAC functions for workflow for check&grant
This commit is contained in:
@@ -38,12 +38,13 @@ func (s session) Context() context.Context { return s.ctx }
|
||||
var _ Session = &session{}
|
||||
|
||||
func ContextToSession(ctx context.Context) *session {
|
||||
i := auth.GetIdentityFromContext(ctx)
|
||||
s := &session{
|
||||
return NewSession(ctx, auth.GetIdentityFromContext(ctx))
|
||||
}
|
||||
|
||||
func NewSession(ctx context.Context, i auth.Identifiable) *session {
|
||||
return &session{
|
||||
id: i.Identity(),
|
||||
rr: i.Roles(),
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -230,6 +230,9 @@ func (s *Session) Result() *expr.Vars {
|
||||
}
|
||||
|
||||
func (s *Session) Exec(ctx context.Context, step Step, scope *expr.Vars) error {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
err := func() error {
|
||||
if s.g.Len() == 0 {
|
||||
return fmt.Errorf("refusing to execute without steps")
|
||||
@@ -466,7 +469,9 @@ func (s *Session) worker(ctx context.Context) {
|
||||
st.err,
|
||||
)
|
||||
|
||||
// when the error handler is defined, the error was handled and should not kill the workflow
|
||||
s.mux.Lock()
|
||||
|
||||
// when the err handler is defined, the error was handled and should not kill the workflow
|
||||
if !st.errHandled {
|
||||
// We need to force failed session status
|
||||
// because it's not set early enough to pick it up with s.Status()
|
||||
@@ -476,6 +481,8 @@ func (s *Session) worker(ctx context.Context) {
|
||||
// to break worker loop
|
||||
s.qErr <- st.err
|
||||
}
|
||||
|
||||
s.mux.Unlock()
|
||||
}
|
||||
|
||||
s.log.Debug(
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -13,9 +14,19 @@ type (
|
||||
Grant(ctx context.Context, rules ...*rbac.Rule) (err error)
|
||||
}
|
||||
|
||||
rbacUserService interface {
|
||||
FindByAny(ctx context.Context, identifier interface{}) (*types.User, error)
|
||||
}
|
||||
|
||||
rbacRoleService interface {
|
||||
Membership(ctx context.Context, userID uint64) (types.RoleMemberSet, error)
|
||||
}
|
||||
|
||||
rbacHandler struct {
|
||||
reg rbacHandlerRegistry
|
||||
svc rbacService
|
||||
reg rbacHandlerRegistry
|
||||
rbac rbacService
|
||||
user rbacUserService
|
||||
role rbacRoleService
|
||||
}
|
||||
|
||||
auxResource struct{ res string }
|
||||
@@ -27,10 +38,12 @@ func (r auxResource) RbacResource() string {
|
||||
return r.res
|
||||
}
|
||||
|
||||
func RbacHandler(reg rbacHandlerRegistry, svc rbacService) *rbacHandler {
|
||||
func RbacHandler(reg rbacHandlerRegistry, rbac rbacService, user rbacUserService, role rbacRoleService) *rbacHandler {
|
||||
h := &rbacHandler{
|
||||
reg: reg,
|
||||
svc: svc,
|
||||
reg: reg,
|
||||
rbac: rbac,
|
||||
user: user,
|
||||
role: role,
|
||||
}
|
||||
|
||||
h.register()
|
||||
@@ -56,16 +69,41 @@ func (h rbacHandler) grant(ctx context.Context, r *rbac.Rule) (err error) {
|
||||
// no extra metadata hidden under it,
|
||||
cmpRes := &auxResource{rbac.ResourceComponent(r.Resource)}
|
||||
|
||||
if !h.svc.Can(rbac.ContextToSession(ctx), "grant", cmpRes) {
|
||||
if !h.rbac.Can(rbac.ContextToSession(ctx), "grant", cmpRes) {
|
||||
return fmt.Errorf("not allowed to grant %s", r.String())
|
||||
}
|
||||
|
||||
return h.svc.Grant(ctx, r)
|
||||
return h.rbac.Grant(ctx, r)
|
||||
}
|
||||
|
||||
func (h rbacHandler) check(ctx context.Context, args *rbacCheckArgs) (b *rbacCheckResults, err error) {
|
||||
var secses rbac.Session
|
||||
|
||||
b = &rbacCheckResults{}
|
||||
|
||||
b.Can = h.svc.Can(rbac.ContextToSession(ctx), args.Operation, args.Resource)
|
||||
if args.hasUser {
|
||||
// user was explicitly set to rbac checking function
|
||||
//
|
||||
// load all roles that this user is member of
|
||||
// and create new security session out of it
|
||||
var (
|
||||
mm types.RoleMemberSet
|
||||
ids []uint64
|
||||
)
|
||||
|
||||
mm, err = h.role.Membership(ctx, args.User.ID)
|
||||
_ = mm.Walk(func(m *types.RoleMember) error {
|
||||
ids = append(ids, m.RoleID)
|
||||
return nil
|
||||
})
|
||||
|
||||
args.User.SetRoles(ids...)
|
||||
|
||||
secses = rbac.NewSession(ctx, args.User)
|
||||
} else {
|
||||
secses = rbac.ContextToSession(ctx)
|
||||
}
|
||||
|
||||
b.Can = h.rbac.Can(secses, args.Operation, args.Resource)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -202,6 +202,8 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
|
||||
automation.RbacHandler(
|
||||
automationService.Registry(),
|
||||
rbac.Global(),
|
||||
DefaultUser,
|
||||
DefaultRole,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
34
tests/workflows/0002_rbac_fn_test.go
Normal file
34
tests/workflows/0002_rbac_fn_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package workflows
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/automation/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test0002_rbac_fn(t *testing.T) {
|
||||
var (
|
||||
ctx = bypassRBAC(context.Background())
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
loadScenario(ctx, t)
|
||||
|
||||
req.Len(rbac.Global().Rules(), 0)
|
||||
|
||||
var (
|
||||
aux = struct {
|
||||
CanCurrentRead string
|
||||
CanOtherRead string
|
||||
}{}
|
||||
vars, _ = mustExecWorkflow(ctx, t, "check-and-grant", types.WorkflowExecParams{})
|
||||
)
|
||||
|
||||
req.NoError(vars.Decode(&aux))
|
||||
req.Equal("y", aux.CanCurrentRead)
|
||||
req.Equal("n", aux.CanOtherRead)
|
||||
req.Len(rbac.Global().Rules(), 1)
|
||||
}
|
||||
@@ -39,6 +39,7 @@ func TestMain(m *testing.M) {
|
||||
ctx := context.Background()
|
||||
|
||||
defApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
|
||||
//app.Opt.Workflow.ExecDebug = true
|
||||
defStore = app.Store
|
||||
eventbus.Set(eventBus)
|
||||
return nil
|
||||
|
||||
66
tests/workflows/testdata/S0002_rbac_fn/workflow.yaml
vendored
Normal file
66
tests/workflows/testdata/S0002_rbac_fn/workflow.yaml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
roles:
|
||||
testers:
|
||||
|
||||
users:
|
||||
tester:
|
||||
email: tester@testing-samples.tld
|
||||
|
||||
workflows:
|
||||
check-and-grant:
|
||||
enabled: true
|
||||
trace: true
|
||||
triggers:
|
||||
- enabled: true
|
||||
stepID: 1
|
||||
|
||||
steps:
|
||||
- stepID: 1
|
||||
kind: function
|
||||
ref: rolesLookup
|
||||
arguments:
|
||||
- { target: lookup, type: Handle, value: "testers" }
|
||||
results:
|
||||
- { target: res, expr: role }
|
||||
|
||||
- stepID: 2
|
||||
kind: function
|
||||
ref: rbacCheck
|
||||
arguments:
|
||||
- { target: resource, type: RbacResource, expr: "res" }
|
||||
- { target: operation, type: String, value: "read" }
|
||||
results:
|
||||
- { target: canCurrentRead, type: String, expr: 'can ? "y":"n"' }
|
||||
|
||||
|
||||
- stepID: 3
|
||||
kind: function
|
||||
ref: usersLookup
|
||||
arguments:
|
||||
- { target: lookup, type: Handle, value: "tester" }
|
||||
results:
|
||||
- { target: usr, expr: user }
|
||||
|
||||
- stepID: 4
|
||||
kind: function
|
||||
ref: rbacCheck
|
||||
arguments:
|
||||
- { target: resource, type: RbacResource, expr: "res" }
|
||||
- { target: operation, type: String, value: "read" }
|
||||
- { target: user, type: User, expr: "usr" }
|
||||
results:
|
||||
- { target: canOtherRead, type: String, expr: 'can ? "y":"n"' }
|
||||
|
||||
|
||||
- stepID: 5
|
||||
kind: function
|
||||
ref: rbacAllow
|
||||
arguments:
|
||||
- { target: resource, type: RbacResource, expr: "res" }
|
||||
- { target: operation, type: String, value: "read" }
|
||||
- { target: role, type: Role, expr: "res" }
|
||||
|
||||
paths:
|
||||
- { parentID: 1, childID: 2 }
|
||||
- { parentID: 2, childID: 3 }
|
||||
- { parentID: 3, childID: 4 }
|
||||
- { parentID: 4, childID: 5 }
|
||||
Reference in New Issue
Block a user