Support context roles support in rbac pkg
This commit is contained in:
13
automation/service/access_control.gen.go
generated
13
automation/service/access_control.gen.go
generated
@@ -15,7 +15,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/automation/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/spf13/cast"
|
||||
"strings"
|
||||
@@ -26,7 +25,7 @@ type (
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can([]uint64, string, rbac.Resource) bool
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
}
|
||||
@@ -41,15 +40,7 @@ func AccessControl() *accessControl {
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
var (
|
||||
identity = internalAuth.GetIdentityFromContext(ctx)
|
||||
)
|
||||
|
||||
if identity == nil {
|
||||
panic("expecting identity in context")
|
||||
}
|
||||
|
||||
return svc.rbac.Can(identity.Roles(), op, res)
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
|
||||
6
automation/types/rbac.gen.go
generated
6
automation/types/rbac.gen.go
generated
@@ -40,12 +40,12 @@ func (r Workflow) RbacResource() string {
|
||||
// RBAC resource is in the corteza+automation.workflow:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func WorkflowRbacResource(ID uint64) string {
|
||||
func WorkflowRbacResource(iD uint64) string {
|
||||
out := WorkflowRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
|
||||
13
compose/service/access_control.gen.go
generated
13
compose/service/access_control.gen.go
generated
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/spf13/cast"
|
||||
"strings"
|
||||
@@ -31,7 +30,7 @@ type (
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can([]uint64, string, rbac.Resource) bool
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
}
|
||||
@@ -46,15 +45,7 @@ func AccessControl() *accessControl {
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
var (
|
||||
identity = internalAuth.GetIdentityFromContext(ctx)
|
||||
)
|
||||
|
||||
if identity == nil {
|
||||
panic("expecting identity in context")
|
||||
}
|
||||
|
||||
return svc.rbac.Can(identity.Roles(), op, res)
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
|
||||
64
compose/types/rbac.gen.go
generated
64
compose/types/rbac.gen.go
generated
@@ -50,19 +50,19 @@ func (r Chart) RbacResource() string {
|
||||
// RBAC resource is in the corteza+compose.chart:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ChartRbacResource(NamespaceID uint64, ID uint64) string {
|
||||
func ChartRbacResource(namespaceID uint64, iD uint64) string {
|
||||
out := ChartRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NamespaceID != 0 {
|
||||
out += strconv.FormatUint(NamespaceID, 10)
|
||||
if namespaceID != 0 {
|
||||
out += strconv.FormatUint(namespaceID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -83,26 +83,26 @@ func (r ModuleField) RbacResource() string {
|
||||
// RBAC resource is in the corteza+compose.module-field:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ModuleFieldRbacResource(NamespaceID uint64, ModuleID uint64, ID uint64) string {
|
||||
func ModuleFieldRbacResource(namespaceID uint64, moduleID uint64, iD uint64) string {
|
||||
out := ModuleFieldRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NamespaceID != 0 {
|
||||
out += strconv.FormatUint(NamespaceID, 10)
|
||||
if namespaceID != 0 {
|
||||
out += strconv.FormatUint(namespaceID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ModuleID != 0 {
|
||||
out += strconv.FormatUint(ModuleID, 10)
|
||||
if moduleID != 0 {
|
||||
out += strconv.FormatUint(moduleID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -123,19 +123,19 @@ func (r Module) RbacResource() string {
|
||||
// RBAC resource is in the corteza+compose.module:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ModuleRbacResource(NamespaceID uint64, ID uint64) string {
|
||||
func ModuleRbacResource(namespaceID uint64, iD uint64) string {
|
||||
out := ModuleRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NamespaceID != 0 {
|
||||
out += strconv.FormatUint(NamespaceID, 10)
|
||||
if namespaceID != 0 {
|
||||
out += strconv.FormatUint(namespaceID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -156,12 +156,12 @@ func (r Namespace) RbacResource() string {
|
||||
// RBAC resource is in the corteza+compose.namespace:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func NamespaceRbacResource(ID uint64) string {
|
||||
func NamespaceRbacResource(iD uint64) string {
|
||||
out := NamespaceRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -182,19 +182,19 @@ func (r Page) RbacResource() string {
|
||||
// RBAC resource is in the corteza+compose.page:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func PageRbacResource(NamespaceID uint64, ID uint64) string {
|
||||
func PageRbacResource(namespaceID uint64, iD uint64) string {
|
||||
out := PageRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NamespaceID != 0 {
|
||||
out += strconv.FormatUint(NamespaceID, 10)
|
||||
if namespaceID != 0 {
|
||||
out += strconv.FormatUint(namespaceID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -215,26 +215,26 @@ func (r Record) RbacResource() string {
|
||||
// RBAC resource is in the corteza+compose.record:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func RecordRbacResource(NamespaceID uint64, ModuleID uint64, ID uint64) string {
|
||||
func RecordRbacResource(namespaceID uint64, moduleID uint64, iD uint64) string {
|
||||
out := RecordRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NamespaceID != 0 {
|
||||
out += strconv.FormatUint(NamespaceID, 10)
|
||||
if namespaceID != 0 {
|
||||
out += strconv.FormatUint(namespaceID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ModuleID != 0 {
|
||||
out += strconv.FormatUint(ModuleID, 10)
|
||||
if moduleID != 0 {
|
||||
out += strconv.FormatUint(moduleID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,21 @@
|
||||
"elements": {
|
||||
"type": "array",
|
||||
"title": "Resource elements",
|
||||
"description": "When not explicitly defined it fallsback to one item array with 'ID'"
|
||||
"description": "When not explicitly defined it fallbacks to one item array with 'ID'"
|
||||
},
|
||||
"attributes": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"enum": [ "true" ],
|
||||
"title": "Custom implementation of resource attributes",
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"title": "List of resource attributes",
|
||||
"description": "Attributes are used for generating list of contextual roles"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
13
federation/service/access_control.gen.go
generated
13
federation/service/access_control.gen.go
generated
@@ -17,7 +17,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/federation/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/spf13/cast"
|
||||
"strings"
|
||||
@@ -28,7 +27,7 @@ type (
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can([]uint64, string, rbac.Resource) bool
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
}
|
||||
@@ -43,15 +42,7 @@ func AccessControl() *accessControl {
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
var (
|
||||
identity = internalAuth.GetIdentityFromContext(ctx)
|
||||
)
|
||||
|
||||
if identity == nil {
|
||||
panic("expecting identity in context")
|
||||
}
|
||||
|
||||
return svc.rbac.Can(identity.Roles(), op, res)
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
|
||||
26
federation/types/rbac.gen.go
generated
26
federation/types/rbac.gen.go
generated
@@ -44,19 +44,19 @@ func (r ExposedModule) RbacResource() string {
|
||||
// RBAC resource is in the corteza+federation.exposed-module:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ExposedModuleRbacResource(NodeID uint64, ID uint64) string {
|
||||
func ExposedModuleRbacResource(nodeID uint64, iD uint64) string {
|
||||
out := ExposedModuleRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NodeID != 0 {
|
||||
out += strconv.FormatUint(NodeID, 10)
|
||||
if nodeID != 0 {
|
||||
out += strconv.FormatUint(nodeID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -77,12 +77,12 @@ func (r Node) RbacResource() string {
|
||||
// RBAC resource is in the corteza+federation.node:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func NodeRbacResource(ID uint64) string {
|
||||
func NodeRbacResource(iD uint64) string {
|
||||
out := NodeRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -103,19 +103,19 @@ func (r SharedModule) RbacResource() string {
|
||||
// RBAC resource is in the corteza+federation.shared-module:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func SharedModuleRbacResource(NodeID uint64, ID uint64) string {
|
||||
func SharedModuleRbacResource(nodeID uint64, iD uint64) string {
|
||||
out := SharedModuleRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if NodeID != 0 {
|
||||
out += strconv.FormatUint(NodeID, 10)
|
||||
if nodeID != 0 {
|
||||
out += strconv.FormatUint(nodeID, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
{{- range .Imports }}
|
||||
{{ normalizeImport . }}
|
||||
{{- end }}
|
||||
@@ -22,7 +21,7 @@ type (
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can([]uint64, string, rbac.Resource) bool
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
}
|
||||
@@ -39,15 +38,7 @@ func AccessControl() *accessControl {
|
||||
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
var (
|
||||
identity = internalAuth.GetIdentityFromContext(ctx)
|
||||
)
|
||||
|
||||
if identity == nil {
|
||||
panic("expecting identity in context")
|
||||
}
|
||||
|
||||
return svc.rbac.Can(identity.Roles(), op, res)
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
|
||||
@@ -32,7 +32,7 @@ const (
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (r {{ .Resource }}) RbacResource() string {
|
||||
return {{ .Resource }}RbacResource({{ if .RBAC.Resource }}{{ range .RBAC.Resource.Elements }}r.{{ unexport . }},{{ end }}{{ end }})
|
||||
return {{ .Resource }}RbacResource({{ if .RBAC.Resource }}{{ range .RBAC.Resource.Elements }}r.{{ export . }},{{ end }}{{ end }})
|
||||
}
|
||||
|
||||
// {{ .Resource }}RbacResource returns string representation of RBAC resource for {{ .Resource }}
|
||||
@@ -53,4 +53,31 @@ func {{ .Resource }}RbacResource({{ if .RBAC.Resource }}{{ range .RBAC.Resource.
|
||||
{{- end }}
|
||||
return out
|
||||
}
|
||||
|
||||
{{ if .RBAC.Resource.Attributes }}
|
||||
// RbacAttributes returns resource attributes used for generating list of contextual roles
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (r {{ .Resource }}) RbacAttributes() map[string]interface{} {
|
||||
return {{ unexport .Resource }}RbacAttributes(r)
|
||||
}
|
||||
|
||||
{{ if .RBAC.Resource.Attributes.Fields }}
|
||||
// {{ .Resource }}RbacResource returns string representation of RBAC resource for {{ .Resource }}
|
||||
//
|
||||
// RBAC resource is in the {{ .RBAC.Schema }}:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ unexport .Resource }}RbacAttributes(r {{ .Resource }}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
{{- range .RBAC.Resource.Attributes.Fields }}
|
||||
{{ printf "%q" . }}: r.{{ export . }},
|
||||
{{- end }}
|
||||
}
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
{{- end }}
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ type (
|
||||
}
|
||||
|
||||
rbacResource struct {
|
||||
Elements []string
|
||||
Elements []string
|
||||
Attributes *rbacAttributes
|
||||
}
|
||||
|
||||
rbacOperations []*rbacOperation
|
||||
@@ -37,6 +38,10 @@ type (
|
||||
CanFnName string `yaml:"canFnName"`
|
||||
Description string
|
||||
}
|
||||
|
||||
rbacAttributes struct {
|
||||
Fields []string `yaml:"-"`
|
||||
}
|
||||
)
|
||||
|
||||
func (set *rbacOperations) UnmarshalYAML(n *yaml.Node) error {
|
||||
@@ -64,6 +69,16 @@ func (op *rbacOperation) UnmarshalYAML(n *yaml.Node) error {
|
||||
return n.Decode(aux)
|
||||
}
|
||||
|
||||
func (a *rbacAttributes) UnmarshalYAML(n *yaml.Node) error {
|
||||
if y7s.IsKind(n, yaml.ScalarNode) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if not scalar, assume we will get list of fields
|
||||
a.Fields = make([]string, 0)
|
||||
return n.Decode(&a.Fields)
|
||||
}
|
||||
|
||||
func RbacOperationCanFnName(res, op string) string {
|
||||
// when check function name is not explicitly defined we try
|
||||
// to use resource and operation name and generate easy-to-read name
|
||||
|
||||
@@ -44,7 +44,7 @@ func Export(pp ...string) (out string) {
|
||||
|
||||
func Unexport(pp ...string) (out string) {
|
||||
out = Export(pp...)
|
||||
if len(out) > 0 {
|
||||
if len(out) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ type (
|
||||
Resource interface {
|
||||
RbacResource() string
|
||||
}
|
||||
|
||||
resourceDicter interface {
|
||||
Dict() map[string]interface{}
|
||||
}
|
||||
)
|
||||
|
||||
func ResourceSchema(r string) string {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/slice"
|
||||
)
|
||||
|
||||
type (
|
||||
ctxRoleCheckFn func(map[string]interface{}) bool
|
||||
|
||||
// role information, adapted for the needs of RBAC package
|
||||
role struct {
|
||||
// all RBAC rules refer to role ID
|
||||
@@ -11,17 +17,17 @@ type (
|
||||
|
||||
// role type that will allow us
|
||||
kind roleKind
|
||||
|
||||
check ctxRoleCheckFn
|
||||
}
|
||||
|
||||
roleKind int
|
||||
|
||||
roles []*role
|
||||
|
||||
partRoles []map[uint64]bool
|
||||
)
|
||||
|
||||
const (
|
||||
CommonRole = iota
|
||||
CommonRole roleKind = iota
|
||||
AnonymousRole
|
||||
AuthenticatedRole
|
||||
ContextRole
|
||||
@@ -42,8 +48,15 @@ func partitionRoles(rr ...*role) partRoles {
|
||||
return out
|
||||
}
|
||||
|
||||
func roleKindsByPriority() []int {
|
||||
return []int{
|
||||
// Returns slice of role types by priority
|
||||
//
|
||||
// Priority is important here. We want to have
|
||||
// stable RBAC check behaviour and ability
|
||||
// to override allow/deny depending on how niche the role (type) is:
|
||||
// - bypass always stake precedence
|
||||
// - context (eg owners) are more niche than common
|
||||
func roleKindsByPriority() []roleKind {
|
||||
return []roleKind{
|
||||
BypassRole,
|
||||
ContextRole,
|
||||
CommonRole,
|
||||
@@ -51,3 +64,44 @@ func roleKindsByPriority() []int {
|
||||
AnonymousRole,
|
||||
}
|
||||
}
|
||||
|
||||
// compare list of session roles (ids) with preloaded roles and calculate the final list
|
||||
func getContextRoles(sRoles []uint64, res Resource, preloadedRoles []*role) (out partRoles) {
|
||||
var (
|
||||
mm = slice.ToUint64BoolMap(sRoles)
|
||||
attr = make(map[string]interface{})
|
||||
)
|
||||
|
||||
if ar, ok := res.(resourceDicter); ok {
|
||||
// if resource implements Dict() fn, we can use it to
|
||||
// collect attributes, used for expr. evaluation and contextual role gathering
|
||||
attr = ar.Dict()
|
||||
|
||||
}
|
||||
|
||||
out = make([]map[uint64]bool, len(roleKindsByPriority()))
|
||||
for _, r := range preloadedRoles {
|
||||
if r.kind == ContextRole {
|
||||
if r.check == nil {
|
||||
// expression not defined, skip contextual role
|
||||
continue
|
||||
}
|
||||
|
||||
if !r.check(attr) {
|
||||
// add role to the list ONLY of expression evaluated true
|
||||
continue
|
||||
}
|
||||
} else if !mm[r.id] {
|
||||
// skip all other types of roles that user from session is not member of
|
||||
continue
|
||||
}
|
||||
|
||||
if out[r.kind] == nil {
|
||||
out[r.kind] = make(map[uint64]bool)
|
||||
}
|
||||
|
||||
out[r.kind][r.id] = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
87
pkg/rbac/roles_test.go
Normal file
87
pkg/rbac/roles_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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},
|
||||
)
|
||||
)
|
||||
|
||||
req.Nil(pr[AuthenticatedRole])
|
||||
req.Nil(pr[AnonymousRole])
|
||||
req.NotNil(pr[BypassRole])
|
||||
req.NotNil(pr[ContextRole])
|
||||
req.NotNil(pr[CommonRole])
|
||||
req.Len(pr[BypassRole], 3)
|
||||
req.True(pr[BypassRole][1])
|
||||
req.True(pr[BypassRole][2])
|
||||
req.True(pr[BypassRole][3])
|
||||
req.Len(pr[ContextRole], 1)
|
||||
req.True(pr[ContextRole][4])
|
||||
req.Len(pr[CommonRole], 1)
|
||||
req.True(pr[CommonRole][5])
|
||||
}
|
||||
|
||||
func Test_getContextRoles(t *testing.T) {
|
||||
var (
|
||||
dyCheck = func(r bool) ctxRoleCheckFn {
|
||||
return func(map[string]interface{}) bool {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
tcc = []struct {
|
||||
name string
|
||||
sessionRoles []uint64
|
||||
res Resource
|
||||
preloadRoles []*role
|
||||
output []*role
|
||||
}{
|
||||
{
|
||||
"existing role",
|
||||
[]uint64{1},
|
||||
nil,
|
||||
[]*role{{id: 1, kind: BypassRole}},
|
||||
[]*role{{id: 1, kind: BypassRole}},
|
||||
},
|
||||
{
|
||||
"missing role",
|
||||
[]uint64{2},
|
||||
nil,
|
||||
[]*role{{id: 1, kind: BypassRole}},
|
||||
[]*role{},
|
||||
},
|
||||
{
|
||||
"dynamic role",
|
||||
[]uint64{1, 2},
|
||||
nil,
|
||||
[]*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}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
req.Equal(partitionRoles(tc.output...), getContextRoles(tc.sessionRoles, tc.res, tc.preloadRoles))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func checkOptimised(indexedRules OptRuleSet, rolesByKind partRoles, res, op stri
|
||||
|
||||
// Check given resource match and operation on all given rules
|
||||
//
|
||||
// Function expects sorted rules!
|
||||
// Function expects rules, sorted by level!
|
||||
func checkRulesByResource(set []*Rule, res, op string) Access {
|
||||
for _, r := range set {
|
||||
if !matchResource(res, r.Resource) {
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
resourceValidator func(string, ...string) error
|
||||
|
||||
service struct {
|
||||
l *sync.Mutex
|
||||
logger *zap.Logger
|
||||
@@ -21,6 +19,8 @@ type (
|
||||
rules RuleSet
|
||||
indexed OptRuleSet
|
||||
|
||||
roles []*role
|
||||
|
||||
store rbacRulesStore
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ type (
|
||||
RuleFilter struct{}
|
||||
|
||||
ControllerV2 interface {
|
||||
Can(roles []uint64, op string, res Resource) bool
|
||||
Check(roles []uint64, op string, res Resource) (v Access)
|
||||
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)
|
||||
Watch(ctx context.Context)
|
||||
FindRulesByRoleID(roleID uint64) (rr RuleSet)
|
||||
@@ -94,21 +94,23 @@ func NewService(logger *zap.Logger, s rbacRulesStore) (svc *service) {
|
||||
// System user is always allowed to do everything
|
||||
//
|
||||
// When not explicitly allowed through rules or fallbacks, function will return FALSE.
|
||||
func (svc service) Can(roles []uint64, op string, res Resource) bool {
|
||||
return svc.Check(roles, op, res) == Allow
|
||||
func (svc service) Can(ses Session, op string, res Resource) bool {
|
||||
return svc.Check(ses, op, res) == Allow
|
||||
}
|
||||
|
||||
// Check verifies if role has access to perform an operation on a resource
|
||||
//
|
||||
// See RuleSet's Check() func for details
|
||||
func (svc service) Check(roles []uint64, op string, res Resource) (v Access) {
|
||||
func (svc service) Check(ses Session, op string, res Resource) (v Access) {
|
||||
svc.l.Lock()
|
||||
defer svc.l.Unlock()
|
||||
|
||||
// @todo roles => securityContext
|
||||
// @todo get context roles!
|
||||
|
||||
return checkOptimised(svc.indexed, nil, op, res.RbacResource())
|
||||
return checkOptimised(
|
||||
svc.indexed,
|
||||
getContextRoles(ses.Roles(), res, svc.roles),
|
||||
op,
|
||||
res.RbacResource(),
|
||||
)
|
||||
}
|
||||
|
||||
// Grant appends and/or overwrites internal rules slice
|
||||
|
||||
47
pkg/rbac/session.go
Normal file
47
pkg/rbac/session.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
)
|
||||
|
||||
type (
|
||||
Session interface {
|
||||
// Identity of the subject
|
||||
Identity() uint64
|
||||
|
||||
// Roles returns all subject's roles for the session
|
||||
Roles() []uint64
|
||||
|
||||
// Context used for expr evaluation
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
session struct {
|
||||
// identity
|
||||
id uint64
|
||||
|
||||
// roles
|
||||
rr []uint64
|
||||
|
||||
// context
|
||||
ctx context.Context
|
||||
}
|
||||
)
|
||||
|
||||
func (s session) Identity() uint64 { return s.id }
|
||||
func (s session) Roles() []uint64 { return s.rr }
|
||||
func (s session) Context() context.Context { return s.ctx }
|
||||
|
||||
var _ Session = &session{}
|
||||
|
||||
func ContextToSession(ctx context.Context) *session {
|
||||
i := auth.GetIdentityFromContext(ctx)
|
||||
s := &session{
|
||||
id: i.Identity(),
|
||||
rr: i.Roles(),
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
13
system/service/access_control.gen.go
generated
13
system/service/access_control.gen.go
generated
@@ -18,7 +18,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"github.com/spf13/cast"
|
||||
@@ -30,7 +29,7 @@ type (
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can([]uint64, string, rbac.Resource) bool
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
}
|
||||
@@ -45,15 +44,7 @@ func AccessControl() *accessControl {
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
var (
|
||||
identity = internalAuth.GetIdentityFromContext(ctx)
|
||||
)
|
||||
|
||||
if identity == nil {
|
||||
panic("expecting identity in context")
|
||||
}
|
||||
|
||||
return svc.rbac.Can(identity.Roles(), op, res)
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
|
||||
30
system/types/rbac.gen.go
generated
30
system/types/rbac.gen.go
generated
@@ -48,12 +48,12 @@ func (r Application) RbacResource() string {
|
||||
// RBAC resource is in the corteza+system.application:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ApplicationRbacResource(ID uint64) string {
|
||||
func ApplicationRbacResource(iD uint64) string {
|
||||
out := ApplicationRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -74,12 +74,12 @@ func (r AuthClient) RbacResource() string {
|
||||
// RBAC resource is in the corteza+system.auth-client:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func AuthClientRbacResource(ID uint64) string {
|
||||
func AuthClientRbacResource(iD uint64) string {
|
||||
out := AuthClientRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -100,12 +100,12 @@ func (r Role) RbacResource() string {
|
||||
// RBAC resource is in the corteza+system.role:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func RoleRbacResource(ID uint64) string {
|
||||
func RoleRbacResource(iD uint64) string {
|
||||
out := RoleRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -126,12 +126,12 @@ func (r Template) RbacResource() string {
|
||||
// RBAC resource is in the corteza+system.template:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func TemplateRbacResource(ID uint64) string {
|
||||
func TemplateRbacResource(iD uint64) string {
|
||||
out := TemplateRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
@@ -152,12 +152,12 @@ func (r User) RbacResource() string {
|
||||
// RBAC resource is in the corteza+system.user:/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func UserRbacResource(ID uint64) string {
|
||||
func UserRbacResource(iD uint64) string {
|
||||
out := UserRbacResourceSchema + ":"
|
||||
out += "/"
|
||||
|
||||
if ID != 0 {
|
||||
out += strconv.FormatUint(ID, 10)
|
||||
if iD != 0 {
|
||||
out += strconv.FormatUint(iD, 10)
|
||||
} else {
|
||||
out += "*"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user