Add /permissions/evaluate endpoints to evaluate RBAC rules
This commit is contained in:
@@ -292,6 +292,22 @@ endpoints:
|
||||
type: string
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: evaluate
|
||||
path: "/evaluate"
|
||||
method: GET
|
||||
title: Evaluate rules for given user/role combo
|
||||
parameters:
|
||||
get:
|
||||
- name: resource
|
||||
type: "[]string"
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: userID
|
||||
type: uint64
|
||||
required: false
|
||||
- name: roleID
|
||||
type: "[]uint64"
|
||||
required: false
|
||||
- name: read
|
||||
path: "/{roleID}/rules"
|
||||
method: GET
|
||||
|
||||
@@ -21,6 +21,7 @@ type (
|
||||
PermissionsAPI interface {
|
||||
List(context.Context, *request.PermissionsList) (interface{}, error)
|
||||
Effective(context.Context, *request.PermissionsEffective) (interface{}, error)
|
||||
Evaluate(context.Context, *request.PermissionsEvaluate) (interface{}, error)
|
||||
Read(context.Context, *request.PermissionsRead) (interface{}, error)
|
||||
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
|
||||
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
|
||||
@@ -30,6 +31,7 @@ type (
|
||||
Permissions struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Effective func(http.ResponseWriter, *http.Request)
|
||||
Evaluate func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
@@ -70,6 +72,22 @@ func NewPermissions(h PermissionsAPI) *Permissions {
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Evaluate: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsEvaluate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Evaluate(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsRead()
|
||||
@@ -126,6 +144,7 @@ func (h Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler)
|
||||
r.Use(middlewares...)
|
||||
r.Get("/permissions/", h.List)
|
||||
r.Get("/permissions/effective", h.Effective)
|
||||
r.Get("/permissions/evaluate", h.Evaluate)
|
||||
r.Get("/permissions/{roleID}/rules", h.Read)
|
||||
r.Delete("/permissions/{roleID}/rules", h.Delete)
|
||||
r.Patch("/permissions/{roleID}/rules", h.Update)
|
||||
|
||||
@@ -2,6 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/automation/rest/request"
|
||||
"github.com/cortezaproject/corteza-server/automation/service"
|
||||
"github.com/cortezaproject/corteza-server/automation/types"
|
||||
@@ -14,8 +15,13 @@ type (
|
||||
ac permissionsAccessController
|
||||
}
|
||||
|
||||
rbacResWrap struct {
|
||||
res string
|
||||
}
|
||||
|
||||
permissionsAccessController interface {
|
||||
Effective(context.Context, ...rbac.Resource) rbac.EffectiveSet
|
||||
Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error)
|
||||
List() []map[string]string
|
||||
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
|
||||
Grant(ctx context.Context, rr ...*rbac.Rule) error
|
||||
@@ -32,6 +38,15 @@ func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEff
|
||||
return ctrl.ac.Effective(ctx, types.Component{}), nil
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Evaluate(ctx context.Context, r *request.PermissionsEvaluate) (interface{}, error) {
|
||||
in := make([]rbac.Resource, 0, len(r.Resource))
|
||||
for _, res := range r.Resource {
|
||||
in = append(in, rbacResWrap{res: res})
|
||||
}
|
||||
|
||||
return ctrl.ac.Evaluate(ctx, r.UserID, r.RoleID, in...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
|
||||
return ctrl.ac.List(), nil
|
||||
}
|
||||
@@ -61,3 +76,7 @@ func (ctrl Permissions) Update(ctx context.Context, r *request.PermissionsUpdate
|
||||
|
||||
return api.OK(), ctrl.ac.Grant(ctx, r.Rules...)
|
||||
}
|
||||
|
||||
func (ar rbacResWrap) RbacResource() string {
|
||||
return ar.res
|
||||
}
|
||||
|
||||
@@ -44,6 +44,23 @@ type (
|
||||
Resource string
|
||||
}
|
||||
|
||||
PermissionsEvaluate struct {
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource []string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
//
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// RoleID GET parameter
|
||||
//
|
||||
//
|
||||
RoleID []uint64
|
||||
}
|
||||
|
||||
PermissionsRead struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
@@ -122,6 +139,75 @@ func (r *PermissionsEffective) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsEvaluate request
|
||||
func NewPermissionsEvaluate() *PermissionsEvaluate {
|
||||
return &PermissionsEvaluate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resource": r.Resource,
|
||||
"userID": r.UserID,
|
||||
"roleID": r.RoleID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetResource() []string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetRoleID() []uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsEvaluate) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource[]"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resource"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["roleID[]"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["roleID"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsRead request
|
||||
func NewPermissionsRead() *PermissionsRead {
|
||||
return &PermissionsRead{}
|
||||
|
||||
Generated
+41
-6
@@ -20,8 +20,9 @@ type (
|
||||
accessControl struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
roleFinder func(ctx context.Context, id uint64) ([]uint64, error)
|
||||
rbac interface {
|
||||
Evaluate(rbac.Session, string, rbac.Resource) rbac.Evaluated
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error
|
||||
@@ -29,15 +30,16 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func AccessControl() *accessControl {
|
||||
func AccessControl(rf func(ctx context.Context, id uint64) ([]uint64, error)) *accessControl {
|
||||
return &accessControl{
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
roleFinder: rf,
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
return svc.rbac.Evaluate(rbac.ContextToSession(ctx), op, res).Can
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
@@ -52,6 +54,39 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate returns a list of permissions evaluated for the given user/roles combo
|
||||
func (svc accessControl) Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error) {
|
||||
// Reusing the grant permission since this is who the feature is for
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
// Load roles for this user
|
||||
//
|
||||
// User's roles take priority over specified ones
|
||||
if user != 0 {
|
||||
rr, err := svc.roleFinder(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles = append(rr, roles...)
|
||||
}
|
||||
|
||||
session := rbac.ParamsToSession(ctx, user, roles...)
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
eval := svc.rbac.Evaluate(session, op, res)
|
||||
|
||||
ee = append(ee, eval)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/objstore"
|
||||
"github.com/cortezaproject/corteza-server/pkg/options"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
sysTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -89,7 +90,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
|
||||
DefaultActionlog = actionlog.NewService(DefaultStore, log, tee, policy)
|
||||
}
|
||||
|
||||
DefaultAccessControl = AccessControl()
|
||||
DefaultAccessControl = AccessControl(RolesForUser(s))
|
||||
|
||||
DefaultSession = Session(DefaultLogger.Named("session"), c.Workflow, ws)
|
||||
DefaultWorkflow = Workflow(DefaultLogger.Named("workflow"), c.Corredor, c.Workflow)
|
||||
@@ -163,3 +164,19 @@ func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool {
|
||||
func trim1st(_ interface{}, err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// @note copied over from system/service/role@RolesForUser
|
||||
func RolesForUser(s store.Storer) func(ctx context.Context, userID uint64) ([]uint64, error) {
|
||||
return func(ctx context.Context, userID uint64) ([]uint64, error) {
|
||||
rr, _, err := store.SearchRoles(ctx, s, types.RoleFilter{MemberID: userID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]uint64, len(rr))
|
||||
for i, r := range rr {
|
||||
out[i] = r.ID
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ type (
|
||||
accessControl struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
roleFinder func(ctx context.Context, id uint64) ([]uint64, error)
|
||||
rbac interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Evaluate(rbac.Session, string, rbac.Resource) rbac.Evaluated
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error
|
||||
@@ -28,16 +29,17 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func AccessControl() *accessControl {
|
||||
func AccessControl(rf func(ctx context.Context, id uint64) ([]uint64, error)) *accessControl {
|
||||
return &accessControl{
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
roleFinder: rf,
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
return svc.rbac.Evaluate(rbac.ContextToSession(ctx), op, res).Can
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
@@ -52,6 +54,39 @@ func (svc accessControl) Effective(ctx context.Context, rr ... rbac.Resource) (e
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate returns a list of permissions evaluated for the given user/roles combo
|
||||
func (svc accessControl) Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error) {
|
||||
// Reusing the grant permission since this is who the feature is for
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
// Load roles for this user
|
||||
//
|
||||
// User's roles take priority over specified ones
|
||||
if user != 0 {
|
||||
rr, err := svc.roleFinder(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles = append(rr, roles...)
|
||||
}
|
||||
|
||||
session := rbac.ParamsToSession(ctx, user, roles...)
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
eval := svc.rbac.Evaluate(session, op, res)
|
||||
|
||||
ee = append(ee, eval)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{{- range .operations }}
|
||||
|
||||
@@ -1297,6 +1297,22 @@ endpoints:
|
||||
type: string
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: evaluate
|
||||
path: "/evaluate"
|
||||
method: GET
|
||||
title: Evaluate rules for given user/role combo
|
||||
parameters:
|
||||
get:
|
||||
- name: resource
|
||||
type: "[]string"
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: userID
|
||||
type: uint64
|
||||
required: false
|
||||
- name: roleID
|
||||
type: "[]uint64"
|
||||
required: false
|
||||
- name: read
|
||||
path: "/{roleID}/rules"
|
||||
method: GET
|
||||
|
||||
@@ -21,6 +21,7 @@ type (
|
||||
PermissionsAPI interface {
|
||||
List(context.Context, *request.PermissionsList) (interface{}, error)
|
||||
Effective(context.Context, *request.PermissionsEffective) (interface{}, error)
|
||||
Evaluate(context.Context, *request.PermissionsEvaluate) (interface{}, error)
|
||||
Read(context.Context, *request.PermissionsRead) (interface{}, error)
|
||||
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
|
||||
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
|
||||
@@ -30,6 +31,7 @@ type (
|
||||
Permissions struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Effective func(http.ResponseWriter, *http.Request)
|
||||
Evaluate func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
@@ -70,6 +72,22 @@ func NewPermissions(h PermissionsAPI) *Permissions {
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Evaluate: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsEvaluate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Evaluate(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsRead()
|
||||
@@ -126,6 +144,7 @@ func (h Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler)
|
||||
r.Use(middlewares...)
|
||||
r.Get("/permissions/", h.List)
|
||||
r.Get("/permissions/effective", h.Effective)
|
||||
r.Get("/permissions/evaluate", h.Evaluate)
|
||||
r.Get("/permissions/{roleID}/rules", h.Read)
|
||||
r.Delete("/permissions/{roleID}/rules", h.Delete)
|
||||
r.Patch("/permissions/{roleID}/rules", h.Update)
|
||||
|
||||
@@ -2,6 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza-server/compose/service"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
@@ -14,8 +15,13 @@ type (
|
||||
ac permissionsAccessController
|
||||
}
|
||||
|
||||
rbacResWrap struct {
|
||||
res string
|
||||
}
|
||||
|
||||
permissionsAccessController interface {
|
||||
Effective(context.Context, ...rbac.Resource) rbac.EffectiveSet
|
||||
Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error)
|
||||
List() []map[string]string
|
||||
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
|
||||
Grant(ctx context.Context, rr ...*rbac.Rule) error
|
||||
@@ -32,6 +38,15 @@ func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEff
|
||||
return ctrl.ac.Effective(ctx, types.Component{}), nil
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Evaluate(ctx context.Context, r *request.PermissionsEvaluate) (interface{}, error) {
|
||||
in := make([]rbac.Resource, 0, len(r.Resource))
|
||||
for _, res := range r.Resource {
|
||||
in = append(in, rbacResWrap{res: res})
|
||||
}
|
||||
|
||||
return ctrl.ac.Evaluate(ctx, r.UserID, r.RoleID, in...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
|
||||
return ctrl.ac.List(), nil
|
||||
}
|
||||
@@ -61,3 +76,7 @@ func (ctrl Permissions) Update(ctx context.Context, r *request.PermissionsUpdate
|
||||
|
||||
return api.OK(), ctrl.ac.Grant(ctx, r.Rules...)
|
||||
}
|
||||
|
||||
func (ar rbacResWrap) RbacResource() string {
|
||||
return ar.res
|
||||
}
|
||||
|
||||
@@ -44,6 +44,23 @@ type (
|
||||
Resource string
|
||||
}
|
||||
|
||||
PermissionsEvaluate struct {
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource []string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
//
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// RoleID GET parameter
|
||||
//
|
||||
//
|
||||
RoleID []uint64
|
||||
}
|
||||
|
||||
PermissionsRead struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
@@ -122,6 +139,75 @@ func (r *PermissionsEffective) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsEvaluate request
|
||||
func NewPermissionsEvaluate() *PermissionsEvaluate {
|
||||
return &PermissionsEvaluate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resource": r.Resource,
|
||||
"userID": r.UserID,
|
||||
"roleID": r.RoleID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetResource() []string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetRoleID() []uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsEvaluate) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource[]"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resource"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["roleID[]"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["roleID"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsRead request
|
||||
func NewPermissionsRead() *PermissionsRead {
|
||||
return &PermissionsRead{}
|
||||
|
||||
Generated
+41
-6
@@ -20,8 +20,9 @@ type (
|
||||
accessControl struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
roleFinder func(ctx context.Context, id uint64) ([]uint64, error)
|
||||
rbac interface {
|
||||
Evaluate(rbac.Session, string, rbac.Resource) rbac.Evaluated
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error
|
||||
@@ -29,15 +30,16 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func AccessControl() *accessControl {
|
||||
func AccessControl(rf func(ctx context.Context, id uint64) ([]uint64, error)) *accessControl {
|
||||
return &accessControl{
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
roleFinder: rf,
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
return svc.rbac.Evaluate(rbac.ContextToSession(ctx), op, res).Can
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
@@ -52,6 +54,39 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate returns a list of permissions evaluated for the given user/roles combo
|
||||
func (svc accessControl) Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error) {
|
||||
// Reusing the grant permission since this is who the feature is for
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
// Load roles for this user
|
||||
//
|
||||
// User's roles take priority over specified ones
|
||||
if user != 0 {
|
||||
rr, err := svc.roleFinder(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles = append(rr, roles...)
|
||||
}
|
||||
|
||||
session := rbac.ParamsToSession(ctx, user, roles...)
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
eval := svc.rbac.Evaluate(session, op, res)
|
||||
|
||||
ee = append(ee, eval)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/discovery"
|
||||
|
||||
automationService "github.com/cortezaproject/corteza-server/automation/service"
|
||||
systemService "github.com/cortezaproject/corteza-server/automation/service"
|
||||
"github.com/cortezaproject/corteza-server/compose/automation"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
@@ -126,7 +127,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config)
|
||||
}
|
||||
}
|
||||
|
||||
DefaultAccessControl = AccessControl()
|
||||
DefaultAccessControl = AccessControl(systemService.RolesForUser(s))
|
||||
DefaultResourceTranslation = ResourceTranslationsManager(locale.Global())
|
||||
|
||||
if DefaultObjectStore == nil {
|
||||
|
||||
@@ -519,6 +519,22 @@ endpoints:
|
||||
type: string
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: evaluate
|
||||
path: "/evaluate"
|
||||
method: GET
|
||||
title: Evaluate rules for given user/role combo
|
||||
parameters:
|
||||
get:
|
||||
- name: resource
|
||||
type: "[]string"
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: userID
|
||||
type: uint64
|
||||
required: false
|
||||
- name: roleID
|
||||
type: "[]uint64"
|
||||
required: false
|
||||
- name: read
|
||||
path: "/{roleID}/rules"
|
||||
method: GET
|
||||
|
||||
@@ -21,6 +21,7 @@ type (
|
||||
PermissionsAPI interface {
|
||||
List(context.Context, *request.PermissionsList) (interface{}, error)
|
||||
Effective(context.Context, *request.PermissionsEffective) (interface{}, error)
|
||||
Evaluate(context.Context, *request.PermissionsEvaluate) (interface{}, error)
|
||||
Read(context.Context, *request.PermissionsRead) (interface{}, error)
|
||||
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
|
||||
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
|
||||
@@ -30,6 +31,7 @@ type (
|
||||
Permissions struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Effective func(http.ResponseWriter, *http.Request)
|
||||
Evaluate func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
@@ -70,6 +72,22 @@ func NewPermissions(h PermissionsAPI) *Permissions {
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Evaluate: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsEvaluate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Evaluate(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsRead()
|
||||
@@ -126,6 +144,7 @@ func (h Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler)
|
||||
r.Use(middlewares...)
|
||||
r.Get("/permissions/", h.List)
|
||||
r.Get("/permissions/effective", h.Effective)
|
||||
r.Get("/permissions/evaluate", h.Evaluate)
|
||||
r.Get("/permissions/{roleID}/rules", h.Read)
|
||||
r.Delete("/permissions/{roleID}/rules", h.Delete)
|
||||
r.Patch("/permissions/{roleID}/rules", h.Update)
|
||||
|
||||
@@ -2,6 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/federation/rest/request"
|
||||
"github.com/cortezaproject/corteza-server/federation/service"
|
||||
"github.com/cortezaproject/corteza-server/federation/types"
|
||||
@@ -14,8 +15,13 @@ type (
|
||||
ac permissionsAccessController
|
||||
}
|
||||
|
||||
rbacResWrap struct {
|
||||
res string
|
||||
}
|
||||
|
||||
permissionsAccessController interface {
|
||||
Effective(context.Context, ...rbac.Resource) rbac.EffectiveSet
|
||||
Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error)
|
||||
List() []map[string]string
|
||||
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
|
||||
Grant(ctx context.Context, rr ...*rbac.Rule) error
|
||||
@@ -32,6 +38,15 @@ func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEff
|
||||
return ctrl.ac.Effective(ctx, types.Component{}), nil
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Evaluate(ctx context.Context, r *request.PermissionsEvaluate) (interface{}, error) {
|
||||
in := make([]rbac.Resource, 0, len(r.Resource))
|
||||
for _, res := range r.Resource {
|
||||
in = append(in, rbacResWrap{res: res})
|
||||
}
|
||||
|
||||
return ctrl.ac.Evaluate(ctx, r.UserID, r.RoleID, in...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
|
||||
return ctrl.ac.List(), nil
|
||||
}
|
||||
@@ -61,3 +76,7 @@ func (ctrl Permissions) Update(ctx context.Context, r *request.PermissionsUpdate
|
||||
|
||||
return api.OK(), ctrl.ac.Grant(ctx, r.Rules...)
|
||||
}
|
||||
|
||||
func (ar rbacResWrap) RbacResource() string {
|
||||
return ar.res
|
||||
}
|
||||
|
||||
@@ -44,6 +44,23 @@ type (
|
||||
Resource string
|
||||
}
|
||||
|
||||
PermissionsEvaluate struct {
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource []string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
//
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// RoleID GET parameter
|
||||
//
|
||||
//
|
||||
RoleID []uint64
|
||||
}
|
||||
|
||||
PermissionsRead struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
@@ -122,6 +139,75 @@ func (r *PermissionsEffective) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsEvaluate request
|
||||
func NewPermissionsEvaluate() *PermissionsEvaluate {
|
||||
return &PermissionsEvaluate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resource": r.Resource,
|
||||
"userID": r.UserID,
|
||||
"roleID": r.RoleID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetResource() []string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetRoleID() []uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsEvaluate) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource[]"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resource"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["roleID[]"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["roleID"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsRead request
|
||||
func NewPermissionsRead() *PermissionsRead {
|
||||
return &PermissionsRead{}
|
||||
|
||||
Generated
+41
-6
@@ -20,8 +20,9 @@ type (
|
||||
accessControl struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
roleFinder func(ctx context.Context, id uint64) ([]uint64, error)
|
||||
rbac interface {
|
||||
Evaluate(rbac.Session, string, rbac.Resource) rbac.Evaluated
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error
|
||||
@@ -29,15 +30,16 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func AccessControl() *accessControl {
|
||||
func AccessControl(rf func(ctx context.Context, id uint64) ([]uint64, error)) *accessControl {
|
||||
return &accessControl{
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
roleFinder: rf,
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
return svc.rbac.Evaluate(rbac.ContextToSession(ctx), op, res).Can
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
@@ -52,6 +54,39 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate returns a list of permissions evaluated for the given user/roles combo
|
||||
func (svc accessControl) Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error) {
|
||||
// Reusing the grant permission since this is who the feature is for
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
// Load roles for this user
|
||||
//
|
||||
// User's roles take priority over specified ones
|
||||
if user != 0 {
|
||||
rr, err := svc.roleFinder(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles = append(rr, roles...)
|
||||
}
|
||||
|
||||
session := rbac.ParamsToSession(ctx, user, roles...)
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
eval := svc.rbac.Evaluate(session, op, res)
|
||||
|
||||
ee = append(ee, eval)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ func Initialize(_ context.Context, log *zap.Logger, s store.Storer, c Config) (e
|
||||
DefaultActionlog = actionlog.NewService(DefaultStore, log, tee, policy)
|
||||
}
|
||||
|
||||
DefaultAccessControl = AccessControl()
|
||||
DefaultAccessControl = AccessControl(service.RolesForUser(s))
|
||||
|
||||
DefaultNode = Node(
|
||||
DefaultStore,
|
||||
|
||||
@@ -178,7 +178,7 @@ func (d *composeDecoder) decodeComposeRecord(ctx context.Context, s store.Storer
|
||||
}
|
||||
}
|
||||
|
||||
ac := service.AccessControl()
|
||||
ac := service.AccessControl(nil)
|
||||
|
||||
if len(d.namespaceID) > 0 {
|
||||
ffNs := make([]*composeRecordFilter, 0, len(ff)+len(d.namespaceID))
|
||||
|
||||
@@ -8,6 +8,29 @@ type (
|
||||
}
|
||||
|
||||
EffectiveSet []effective
|
||||
|
||||
Evaluated struct {
|
||||
Resource string `json:"resource"`
|
||||
Operation string `json:"operation"`
|
||||
Access Access `json:"-"`
|
||||
Can bool `json:"can"`
|
||||
Step explanation `json:"step"`
|
||||
|
||||
RoleID uint64 `json:"roleID,string,omitempty"`
|
||||
Rule *Rule `json:"rule,omitempty"`
|
||||
|
||||
Default *Evaluated `json:"default,omitempty"`
|
||||
}
|
||||
EvaluatedSet []Evaluated
|
||||
|
||||
explanation string
|
||||
)
|
||||
|
||||
const (
|
||||
stepIntegrity explanation = "integrity"
|
||||
stepBypass explanation = "bypass"
|
||||
stepRuleless explanation = "ruleless"
|
||||
stepEvaluated explanation = "evaluated"
|
||||
)
|
||||
|
||||
func (ee *EffectiveSet) Push(res, op string, allow bool) {
|
||||
|
||||
+26
-14
@@ -5,20 +5,26 @@ import (
|
||||
)
|
||||
|
||||
func check(indexedRules OptRuleSet, rolesByKind partRoles, op, res string) Access {
|
||||
a, _, _ := evaluate(indexedRules, rolesByKind, op, res, false)
|
||||
return a
|
||||
}
|
||||
|
||||
func evaluate(indexedRules OptRuleSet, rolesByKind partRoles, op, res string, parent bool) (Access, *Rule, explanation) {
|
||||
if member(rolesByKind, AnonymousRole) && len(rolesByKind) > 1 {
|
||||
// Integrity check; when user is member of anonymous role
|
||||
// should not be member of any other type of role
|
||||
return Deny
|
||||
|
||||
return Deny, nil, stepIntegrity
|
||||
}
|
||||
|
||||
if member(rolesByKind, BypassRole) {
|
||||
// if user has at least one bypass role, we allow access
|
||||
return Allow
|
||||
return Allow, nil, stepBypass
|
||||
}
|
||||
|
||||
if len(indexedRules) == 0 {
|
||||
// no rules no access
|
||||
return Inherit
|
||||
return Inherit, nil, stepRuleless
|
||||
}
|
||||
|
||||
var rules RuleSet
|
||||
@@ -34,11 +40,6 @@ func check(indexedRules OptRuleSet, rolesByKind partRoles, op, res string) Acces
|
||||
continue
|
||||
}
|
||||
|
||||
// user has at least one bypass role
|
||||
if kind == BypassRole {
|
||||
return Allow
|
||||
}
|
||||
|
||||
rules = nil
|
||||
for roleID, r := range indexedRules[op] {
|
||||
if !rolesByKind[kind][roleID] {
|
||||
@@ -47,17 +48,28 @@ func check(indexedRules OptRuleSet, rolesByKind partRoles, op, res string) Acces
|
||||
rules = append(rules, r...)
|
||||
}
|
||||
|
||||
access := checkRulesByResource(rules, op, res)
|
||||
// When evaluating access for parent, omit the exact tule
|
||||
if parent {
|
||||
nr := make(RuleSet, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
if r.Resource != res {
|
||||
nr = append(nr, r)
|
||||
}
|
||||
}
|
||||
rules = nr
|
||||
}
|
||||
|
||||
r, access := checkRulesByResource(rules, op, res)
|
||||
if access != Inherit {
|
||||
return access
|
||||
return access, r, stepEvaluated
|
||||
}
|
||||
}
|
||||
|
||||
return Inherit
|
||||
return Inherit, nil, stepRuleless
|
||||
}
|
||||
|
||||
// Check given resource match and operation on all given rules
|
||||
func checkRulesByResource(set RuleSet, op, res string) Access {
|
||||
func checkRulesByResource(set RuleSet, op, res string) (*Rule, Access) {
|
||||
// Make sure rules are always sorted (by level)
|
||||
// to avoid any kind of unstable behaviour
|
||||
sort.Sort(set)
|
||||
@@ -72,11 +84,11 @@ func checkRulesByResource(set RuleSet, op, res string) Access {
|
||||
}
|
||||
|
||||
if r.Access != Inherit {
|
||||
return r.Access
|
||||
return r, r.Access
|
||||
}
|
||||
}
|
||||
|
||||
return Inherit
|
||||
return nil, Inherit
|
||||
}
|
||||
|
||||
// at least one of the roles must be set to true
|
||||
|
||||
@@ -155,7 +155,8 @@ func Test_checkRulesByResource(t *testing.T) {
|
||||
|
||||
for _, c := range cc {
|
||||
t.Run(c.res, func(t *testing.T) {
|
||||
require.Equal(t, c.exp.String(), checkRulesByResource(c.set, c.op, c.res).String())
|
||||
_, a := checkRulesByResource(c.set, c.op, c.res)
|
||||
require.Equal(t, c.exp.String(), a.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+64
-5
@@ -93,12 +93,9 @@ func (svc *service) Can(ses Session, op string, res Resource) bool {
|
||||
//
|
||||
// See RuleSet's Check() func for details
|
||||
func (svc *service) Check(ses Session, op string, res Resource) (v Access) {
|
||||
svc.l.RLock()
|
||||
defer svc.l.RUnlock()
|
||||
|
||||
var (
|
||||
fRoles = getContextRoles(ses, res, svc.roles)
|
||||
access = check(svc.indexed, fRoles, op, res.RbacResource())
|
||||
eval, fRoles = svc.evaluate(ses, op, res, false)
|
||||
access = eval.Access
|
||||
)
|
||||
|
||||
svc.logger.Debug(access.String()+" "+op+" for "+res.RbacResource(),
|
||||
@@ -113,6 +110,68 @@ func (svc *service) Check(ses Session, op string, res Resource) (v Access) {
|
||||
return access
|
||||
}
|
||||
|
||||
// Eval evaluates access for the given parameters
|
||||
//
|
||||
// The evaluation outputs verbose details to assist the UI.
|
||||
func (svc *service) Evaluate(ses Session, op string, res Resource) Evaluated {
|
||||
var (
|
||||
eval, fRoles = svc.evaluate(ses, op, res, true)
|
||||
)
|
||||
|
||||
svc.logger.Debug(eval.Access.String()+" "+op+" for "+res.RbacResource(),
|
||||
append(
|
||||
fRoles.LogFields(),
|
||||
zap.Uint64("identity", ses.Identity()),
|
||||
zap.Any("indexed", len(svc.indexed)),
|
||||
zap.Any("rules", len(svc.rules)),
|
||||
)...,
|
||||
)
|
||||
|
||||
return eval
|
||||
}
|
||||
|
||||
func (svc *service) evaluate(ses Session, op string, res Resource, inclParent bool) (e Evaluated, _ partRoles) {
|
||||
svc.l.RLock()
|
||||
defer svc.l.RUnlock()
|
||||
|
||||
var (
|
||||
fRoles = getContextRoles(ses, res, svc.roles)
|
||||
access, rule, expl = evaluate(svc.indexed, fRoles, op, res.RbacResource(), false)
|
||||
)
|
||||
|
||||
// Check the requested resource
|
||||
e = Evaluated{
|
||||
Resource: res.RbacResource(),
|
||||
Operation: op,
|
||||
|
||||
Access: access,
|
||||
Can: access == Allow,
|
||||
Rule: rule,
|
||||
Step: expl,
|
||||
}
|
||||
if rule != nil {
|
||||
e.RoleID = rule.RoleID
|
||||
}
|
||||
|
||||
// Check the parent resource
|
||||
if inclParent {
|
||||
access, rule, expl = evaluate(svc.indexed, fRoles, op, res.RbacResource(), true)
|
||||
e.Default = &Evaluated{
|
||||
Resource: res.RbacResource(),
|
||||
Operation: op,
|
||||
Access: access,
|
||||
Can: access == Allow,
|
||||
Rule: rule,
|
||||
Step: expl,
|
||||
}
|
||||
if rule != nil {
|
||||
e.Default.RoleID = rule.RoleID
|
||||
}
|
||||
}
|
||||
|
||||
return e, fRoles
|
||||
}
|
||||
|
||||
// Grant appends and/or overwrites internal rules slice
|
||||
//
|
||||
// All rules with Inherit are removed
|
||||
|
||||
@@ -41,6 +41,14 @@ func ContextToSession(ctx context.Context) *session {
|
||||
return NewSession(ctx, auth.GetIdentityFromContext(ctx))
|
||||
}
|
||||
|
||||
func ParamsToSession(ctx context.Context, user uint64, roles ...uint64) *session {
|
||||
return &session{
|
||||
id: user,
|
||||
rr: roles,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func NewSession(ctx context.Context, i auth.Identifiable) *session {
|
||||
return &session{
|
||||
id: i.Identity(),
|
||||
|
||||
@@ -1221,6 +1221,22 @@ endpoints:
|
||||
type: string
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: evaluate
|
||||
path: "/evaluate"
|
||||
method: GET
|
||||
title: Evaluate rules for given user/role combo
|
||||
parameters:
|
||||
get:
|
||||
- name: resource
|
||||
type: "[]string"
|
||||
required: false
|
||||
title: Show only rules for a specific resource
|
||||
- name: userID
|
||||
type: uint64
|
||||
required: false
|
||||
- name: roleID
|
||||
type: "[]uint64"
|
||||
required: false
|
||||
- name: read
|
||||
path: "/{roleID}/rules"
|
||||
method: GET
|
||||
|
||||
@@ -21,6 +21,7 @@ type (
|
||||
PermissionsAPI interface {
|
||||
List(context.Context, *request.PermissionsList) (interface{}, error)
|
||||
Effective(context.Context, *request.PermissionsEffective) (interface{}, error)
|
||||
Evaluate(context.Context, *request.PermissionsEvaluate) (interface{}, error)
|
||||
Read(context.Context, *request.PermissionsRead) (interface{}, error)
|
||||
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
|
||||
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
|
||||
@@ -31,6 +32,7 @@ type (
|
||||
Permissions struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Effective func(http.ResponseWriter, *http.Request)
|
||||
Evaluate func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
@@ -72,6 +74,22 @@ func NewPermissions(h PermissionsAPI) *Permissions {
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Evaluate: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsEvaluate()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Evaluate(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPermissionsRead()
|
||||
@@ -144,6 +162,7 @@ func (h Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler)
|
||||
r.Use(middlewares...)
|
||||
r.Get("/permissions/", h.List)
|
||||
r.Get("/permissions/effective", h.Effective)
|
||||
r.Get("/permissions/evaluate", h.Evaluate)
|
||||
r.Get("/permissions/{roleID}/rules", h.Read)
|
||||
r.Delete("/permissions/{roleID}/rules", h.Delete)
|
||||
r.Patch("/permissions/{roleID}/rules", h.Update)
|
||||
|
||||
@@ -2,6 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/api"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
@@ -15,8 +16,13 @@ type (
|
||||
ac permissionsAccessController
|
||||
}
|
||||
|
||||
rbacResWrap struct {
|
||||
res string
|
||||
}
|
||||
|
||||
permissionsAccessController interface {
|
||||
Effective(context.Context, ...rbac.Resource) rbac.EffectiveSet
|
||||
Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error)
|
||||
List() []map[string]string
|
||||
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
|
||||
CloneRulesByRoleID(ctx context.Context, roleID uint64, toRoleID ...uint64) error
|
||||
@@ -34,6 +40,15 @@ func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEff
|
||||
return ctrl.ac.Effective(ctx, types.Component{}), nil
|
||||
}
|
||||
|
||||
func (ctrl Permissions) Evaluate(ctx context.Context, r *request.PermissionsEvaluate) (interface{}, error) {
|
||||
in := make([]rbac.Resource, 0, len(r.Resource))
|
||||
for _, res := range r.Resource {
|
||||
in = append(in, rbacResWrap{res: res})
|
||||
}
|
||||
|
||||
return ctrl.ac.Evaluate(ctx, r.UserID, r.RoleID, in...)
|
||||
}
|
||||
|
||||
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
|
||||
return ctrl.ac.List(), nil
|
||||
}
|
||||
@@ -68,3 +83,7 @@ func (ctrl Permissions) Clone(ctx context.Context, r *request.PermissionsClone)
|
||||
// Clone rules from role S to role T
|
||||
return api.OK(), ctrl.ac.CloneRulesByRoleID(ctx, r.RoleID, payload.ParseUint64s(r.CloneToRoleID)...)
|
||||
}
|
||||
|
||||
func (ar rbacResWrap) RbacResource() string {
|
||||
return ar.res
|
||||
}
|
||||
|
||||
@@ -44,6 +44,23 @@ type (
|
||||
Resource string
|
||||
}
|
||||
|
||||
PermissionsEvaluate struct {
|
||||
// Resource GET parameter
|
||||
//
|
||||
// Show only rules for a specific resource
|
||||
Resource []string
|
||||
|
||||
// UserID GET parameter
|
||||
//
|
||||
//
|
||||
UserID uint64 `json:",string"`
|
||||
|
||||
// RoleID GET parameter
|
||||
//
|
||||
//
|
||||
RoleID []uint64
|
||||
}
|
||||
|
||||
PermissionsRead struct {
|
||||
// RoleID PATH parameter
|
||||
//
|
||||
@@ -134,6 +151,75 @@ func (r *PermissionsEffective) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsEvaluate request
|
||||
func NewPermissionsEvaluate() *PermissionsEvaluate {
|
||||
return &PermissionsEvaluate{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"resource": r.Resource,
|
||||
"userID": r.UserID,
|
||||
"roleID": r.RoleID,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetResource() []string {
|
||||
return r.Resource
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetUserID() uint64 {
|
||||
return r.UserID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PermissionsEvaluate) GetRoleID() []uint64 {
|
||||
return r.RoleID
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PermissionsEvaluate) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["resource[]"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["resource"]; ok {
|
||||
r.Resource, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["userID"]; ok && len(val) > 0 {
|
||||
r.UserID, err = payload.ParseUint64(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["roleID[]"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := tmp["roleID"]; ok {
|
||||
r.RoleID, err = payload.ParseUint64s(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPermissionsRead request
|
||||
func NewPermissionsRead() *PermissionsRead {
|
||||
return &PermissionsRead{}
|
||||
|
||||
Generated
+41
-6
@@ -20,8 +20,9 @@ type (
|
||||
accessControl struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
roleFinder func(ctx context.Context, id uint64) ([]uint64, error)
|
||||
rbac interface {
|
||||
Evaluate(rbac.Session, string, rbac.Resource) rbac.Evaluated
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error
|
||||
@@ -29,15 +30,16 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func AccessControl() *accessControl {
|
||||
func AccessControl(rf func(ctx context.Context, id uint64) ([]uint64, error)) *accessControl {
|
||||
return &accessControl{
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
roleFinder: rf,
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
return svc.rbac.Evaluate(rbac.ContextToSession(ctx), op, res).Can
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
@@ -52,6 +54,39 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate returns a list of permissions evaluated for the given user/roles combo
|
||||
func (svc accessControl) Evaluate(ctx context.Context, user uint64, roles []uint64, rr ...rbac.Resource) (ee rbac.EvaluatedSet, err error) {
|
||||
// Reusing the grant permission since this is who the feature is for
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
// Load roles for this user
|
||||
//
|
||||
// User's roles take priority over specified ones
|
||||
if user != 0 {
|
||||
rr, err := svc.roleFinder(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles = append(rr, roles...)
|
||||
}
|
||||
|
||||
session := rbac.ParamsToSession(ctx, user, roles...)
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
eval := svc.rbac.Evaluate(session, op, res)
|
||||
|
||||
ee = append(ee, eval)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{
|
||||
|
||||
@@ -962,3 +962,18 @@ func UpdateRbacRoles(ctx context.Context, log *zap.Logger, ru rbacRoleUpdater, b
|
||||
ru.UpdateRoles(rr...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func RolesForUser(s store.Storer) func(ctx context.Context, userID uint64) ([]uint64, error) {
|
||||
return func(ctx context.Context, userID uint64) ([]uint64, error) {
|
||||
rr, _, err := store.SearchRoles(ctx, s, types.RoleFilter{MemberID: userID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]uint64, len(rr))
|
||||
for i, r := range rr {
|
||||
out[i] = r.ID
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, primaryCon
|
||||
}
|
||||
}
|
||||
|
||||
DefaultAccessControl = AccessControl()
|
||||
DefaultAccessControl = AccessControl(RolesForUser(s))
|
||||
|
||||
DefaultSettings = Settings(ctx, DefaultStore, DefaultLogger, DefaultAccessControl, CurrentSettings)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user