Refactor permission resources
This commit is contained in:
parent
8a10839070
commit
78763c715d
@ -33,7 +33,7 @@ function types {
|
||||
./build/gen-type-set --with-primary-key=false --types RecordValue --output crm/types/record_value.gen.go
|
||||
|
||||
./build/gen-type-set --types MessageAttachment --output messaging/types/attachment.gen.go
|
||||
./build/gen-type-set --with-resources=true --types Channel --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --output messaging/types/channel.gen.go
|
||||
./build/gen-type-set --types Channel --output messaging/types/channel.gen.go
|
||||
./build/gen-type-set --with-primary-key=false --types ChannelMember --output messaging/types/channel_member.gen.go
|
||||
./build/gen-type-set --with-primary-key=false --types Command,CommandParam --output messaging/types/command.gen.go
|
||||
./build/gen-type-set --types Mention --output messaging/types/mention.gen.go
|
||||
@ -43,8 +43,8 @@ function types {
|
||||
|
||||
./build/gen-type-set --types User --output system/types/user.gen.go
|
||||
./build/gen-type-set --types Application --output system/types/application.gen.go
|
||||
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Role --output system/types/role.gen.go
|
||||
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Organisation --output system/types/organisation.gen.go
|
||||
./build/gen-type-set --types Role --output system/types/role.gen.go
|
||||
./build/gen-type-set --types Organisation --output system/types/organisation.gen.go
|
||||
./build/gen-type-set --types Credentials --output system/types/credentials.gen.go
|
||||
green "OK"
|
||||
}
|
||||
|
||||
@ -91,24 +91,9 @@ func (set {{ $set }}Set) IDs() (IDs []uint64) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{ if $.WithResources }}
|
||||
// Resources returns a slice of types.Resource from all items in the set
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set {{ $set }}Set) Resources() (Resources []{{ $.ResourceType }}) {
|
||||
Resources = make([]{{ $.ResourceType }}, len(set))
|
||||
|
||||
for i := range set {
|
||||
Resources[i] = set[i].Resource()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@ func (r *record) Find(module *types.Module, filter string, sort string, page int
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Append order args to select args and execute actual query.
|
||||
// append order args to select args and execute actual query.
|
||||
if err := r.db().Select(&response.Records, sqlSelect, argsSelect...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ func (svc *chart) Find() (cc types.ChartSet, err error) {
|
||||
}
|
||||
|
||||
func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) {
|
||||
if !svc.prmSvc.CanCreateChart() {
|
||||
if !svc.prmSvc.CanCreateChart(crmNamespace()) {
|
||||
return nil, errors.New("not allowed to create this chart")
|
||||
}
|
||||
|
||||
@ -112,7 +112,9 @@ func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) {
|
||||
}
|
||||
|
||||
func (svc *chart) DeleteByID(ID uint64) error {
|
||||
if !svc.prmSvc.CanDeleteChartByID(ID) {
|
||||
if c, err := svc.chartRepo.FindByID(ID); err != nil {
|
||||
return errors.Wrap(err, "could not delete chart")
|
||||
} else if !svc.prmSvc.CanDeleteChart(c) {
|
||||
return errors.New("not allowed to delete this chart")
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ func (svc *module) Find() (mm types.ModuleSet, err error) {
|
||||
}
|
||||
|
||||
func (svc *module) Create(mod *types.Module) (*types.Module, error) {
|
||||
if !svc.prmSvc.CanCreateModule() {
|
||||
if !svc.prmSvc.CanCreateModule(crmNamespace()) {
|
||||
return nil, errors.New("not allowed to create this module")
|
||||
}
|
||||
|
||||
@ -116,7 +116,9 @@ func (svc *module) Update(module *types.Module) (m *types.Module, err error) {
|
||||
}
|
||||
|
||||
func (svc *module) DeleteByID(ID uint64) error {
|
||||
if !svc.prmSvc.CanDeleteModuleByID(ID) {
|
||||
if m, err := svc.moduleRepo.FindByID(ID); err != nil {
|
||||
return errors.Wrap(err, "could not delete module")
|
||||
} else if !svc.prmSvc.CanDeleteModule(m) {
|
||||
return errors.New("not allowed to delete this module")
|
||||
}
|
||||
|
||||
|
||||
@ -136,7 +136,7 @@ func (svc *page) Reorder(selfID uint64, pageIDs []uint64) error {
|
||||
|
||||
func (svc *page) Create(page *types.Page) (p *types.Page, err error) {
|
||||
validate := func() error {
|
||||
if !svc.prmSvc.CanCreatePage() {
|
||||
if !svc.prmSvc.CanCreatePage(crmNamespace()) {
|
||||
return errors.New("not allowed to create this module")
|
||||
}
|
||||
|
||||
@ -189,7 +189,9 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) {
|
||||
}
|
||||
|
||||
func (svc *page) DeleteByID(ID uint64) error {
|
||||
if !svc.prmSvc.CanDeletePageByID(ID) {
|
||||
if p, err := svc.pageRepo.FindByID(ID); err != nil {
|
||||
return errors.Wrap(err, "could not delete page")
|
||||
} else if !svc.prmSvc.CanDeletePage(p) {
|
||||
return errors.New("not allowed to delete this page")
|
||||
}
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ type (
|
||||
rules systemService.RulesService
|
||||
}
|
||||
|
||||
resource interface {
|
||||
Resource() internalRules.Resource
|
||||
permissionResource interface {
|
||||
PermissionResource() internalRules.Resource
|
||||
}
|
||||
|
||||
PermissionsService interface {
|
||||
@ -28,31 +28,26 @@ type (
|
||||
|
||||
CanAccess() bool
|
||||
CanCreateNamspace() bool
|
||||
CanCreateModule() bool
|
||||
CanReadModule(r resource) bool
|
||||
CanUpdateModule(r resource) bool
|
||||
CanDeleteModule(r resource) bool
|
||||
CanDeleteModuleByID(ID uint64) bool
|
||||
CanCreateRecord(r resource) bool
|
||||
CanReadRecord(r resource) bool
|
||||
CanUpdateRecord(r resource) bool
|
||||
CanDeleteRecord(r resource) bool
|
||||
CanDeleteRecordByModuleID(ID uint64) bool
|
||||
CanCreateChart() bool
|
||||
CanReadChart(r resource) bool
|
||||
CanUpdateChart(r resource) bool
|
||||
CanDeleteChart(r resource) bool
|
||||
CanDeleteChartByID(ID uint64) bool
|
||||
CanCreateTrigger() bool
|
||||
CanReadTrigger(r resource) bool
|
||||
CanUpdateTrigger(r resource) bool
|
||||
CanDeleteTrigger(r resource) bool
|
||||
CanDeleteTriggerByID(ID uint64) bool
|
||||
CanCreatePage() bool
|
||||
CanReadPage(r resource) bool
|
||||
CanUpdatePage(r resource) bool
|
||||
CanDeletePage(r resource) bool
|
||||
CanDeletePageByID(ID uint64) bool
|
||||
CanCreateModule(r permissionResource) bool
|
||||
CanReadModule(r permissionResource) bool
|
||||
CanUpdateModule(r permissionResource) bool
|
||||
CanDeleteModule(r permissionResource) bool
|
||||
CanCreateRecord(r permissionResource) bool
|
||||
CanReadRecord(r permissionResource) bool
|
||||
CanUpdateRecord(r permissionResource) bool
|
||||
CanDeleteRecord(r permissionResource) bool
|
||||
CanCreateChart(r permissionResource) bool
|
||||
CanReadChart(r permissionResource) bool
|
||||
CanUpdateChart(r permissionResource) bool
|
||||
CanDeleteChart(r permissionResource) bool
|
||||
CanCreateTrigger(r permissionResource) bool
|
||||
CanReadTrigger(r permissionResource) bool
|
||||
CanUpdateTrigger(r permissionResource) bool
|
||||
CanDeleteTrigger(r permissionResource) bool
|
||||
CanCreatePage(r permissionResource) bool
|
||||
CanReadPage(r permissionResource) bool
|
||||
CanUpdatePage(r permissionResource) bool
|
||||
CanDeletePage(r permissionResource) bool
|
||||
}
|
||||
|
||||
effectivePermission struct {
|
||||
@ -60,12 +55,16 @@ type (
|
||||
Operation string `json:"operation"`
|
||||
Allow bool `json:"allow"`
|
||||
}
|
||||
|
||||
Compose struct{}
|
||||
)
|
||||
|
||||
func (Compose) Resource() internalRules.Resource {
|
||||
return internalRules.Resource{Service: "compose"}
|
||||
// Creates a virtual namespace for CRM
|
||||
//
|
||||
// We need this until we're through with complete migration
|
||||
// to Crust Compose
|
||||
func crmNamespace() *types.Namespace {
|
||||
return &types.Namespace{
|
||||
ID: types.NamespaceCRM,
|
||||
}
|
||||
}
|
||||
|
||||
func Permissions() PermissionsService {
|
||||
@ -84,10 +83,6 @@ func (p *permissions) With(ctx context.Context) PermissionsService {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *permissions) baseResource() resource {
|
||||
return &Compose{}
|
||||
}
|
||||
|
||||
// Return permissions
|
||||
func (p *permissions) Effective() (ee []effectivePermission, err error) {
|
||||
ep := func(res, op string, allow bool) effectivePermission {
|
||||
@ -102,136 +97,110 @@ func (p *permissions) Effective() (ee []effectivePermission, err error) {
|
||||
ee = append(ee, ep("compose", "grant", p.CanGrant()))
|
||||
ee = append(ee, ep("compose", "namespace.create", p.CanCreateNamspace()))
|
||||
|
||||
ee = append(ee, ep("compose:namespace:crm", "module.create", p.CanCreateModule()))
|
||||
ee = append(ee, ep("compose:namespace:crm", "chart.create", p.CanCreateChart()))
|
||||
ee = append(ee, ep("compose:namespace:crm", "trigger.create", p.CanCreateTrigger()))
|
||||
ee = append(ee, ep("compose:namespace:crm", "page.create", p.CanCreatePage()))
|
||||
ee = append(ee, ep("compose:namespace:crm", "module.create", p.CanCreateModule(crmNamespace())))
|
||||
ee = append(ee, ep("compose:namespace:crm", "chart.create", p.CanCreateChart(crmNamespace())))
|
||||
ee = append(ee, ep("compose:namespace:crm", "trigger.create", p.CanCreateTrigger(crmNamespace())))
|
||||
ee = append(ee, ep("compose:namespace:crm", "page.create", p.CanCreatePage(crmNamespace())))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *permissions) CanAccess() bool {
|
||||
return p.checkAccess(p.baseResource(), "access")
|
||||
return p.checkAccess(types.PermissionResource, "access")
|
||||
}
|
||||
|
||||
func (p *permissions) CanGrant() bool {
|
||||
return p.checkAccess(p.baseResource(), "grant")
|
||||
return p.checkAccess(types.PermissionResource, "grant")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateNamspace() bool {
|
||||
return p.checkAccess(p.baseResource(), "namespace.create")
|
||||
return p.checkAccess(types.PermissionResource, "namespace.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateModule() bool {
|
||||
func (p *permissions) CanCreateModule(ns permissionResource) bool {
|
||||
// @todo move to func args when namespaces are implemented
|
||||
ns := &types.Namespace{ID: types.NamespaceCRM}
|
||||
return p.checkAccess(ns, "module.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadModule(r resource) bool {
|
||||
func (p *permissions) CanReadModule(r permissionResource) bool {
|
||||
return p.checkAccess(r, "read")
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateModule(r resource) bool {
|
||||
func (p *permissions) CanUpdateModule(r permissionResource) bool {
|
||||
return p.checkAccess(r, "update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteModule(r resource) bool {
|
||||
func (p *permissions) CanDeleteModule(r permissionResource) bool {
|
||||
return p.checkAccess(r, "delete")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteModuleByID(ID uint64) bool {
|
||||
return p.CanDeleteModule(&types.Module{ID: ID})
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateRecord(r resource) bool {
|
||||
func (p *permissions) CanCreateRecord(r permissionResource) bool {
|
||||
return p.checkAccess(r, "record.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadRecord(r resource) bool {
|
||||
func (p *permissions) CanReadRecord(r permissionResource) bool {
|
||||
return p.checkAccess(r, "record.read")
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateRecord(r resource) bool {
|
||||
func (p *permissions) CanUpdateRecord(r permissionResource) bool {
|
||||
return p.checkAccess(r, "record.update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteRecord(r resource) bool {
|
||||
func (p *permissions) CanDeleteRecord(r permissionResource) bool {
|
||||
return p.checkAccess(r, "record.delete")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteRecordByModuleID(moduleID uint64) bool {
|
||||
return p.CanDeleteRecord(&types.Record{ModuleID: moduleID})
|
||||
func (p *permissions) CanCreateChart(r permissionResource) bool {
|
||||
return p.checkAccess(r, "chart.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateChart() bool {
|
||||
// @todo move to func args when namespaces are implemented
|
||||
ns := &types.Namespace{ID: types.NamespaceCRM}
|
||||
return p.checkAccess(ns, "chart.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadChart(r resource) bool {
|
||||
func (p *permissions) CanReadChart(r permissionResource) bool {
|
||||
return p.checkAccess(r, "read")
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateChart(r resource) bool {
|
||||
func (p *permissions) CanUpdateChart(r permissionResource) bool {
|
||||
return p.checkAccess(r, "update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteChart(r resource) bool {
|
||||
func (p *permissions) CanDeleteChart(r permissionResource) bool {
|
||||
return p.checkAccess(r, "delete")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteChartByID(ID uint64) bool {
|
||||
return p.CanDeleteChart(&types.Chart{ID: ID})
|
||||
func (p *permissions) CanCreateTrigger(r permissionResource) bool {
|
||||
return p.checkAccess(r, "trigger.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateTrigger() bool {
|
||||
// @todo move to func args when namespaces are implemented
|
||||
ns := &types.Namespace{ID: types.NamespaceCRM}
|
||||
return p.checkAccess(ns, "trigger.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadTrigger(r resource) bool {
|
||||
func (p *permissions) CanReadTrigger(r permissionResource) bool {
|
||||
return p.checkAccess(r, "read")
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateTrigger(r resource) bool {
|
||||
func (p *permissions) CanUpdateTrigger(r permissionResource) bool {
|
||||
return p.checkAccess(r, "update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteTrigger(r resource) bool {
|
||||
func (p *permissions) CanDeleteTrigger(r permissionResource) bool {
|
||||
return p.checkAccess(r, "delete")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteTriggerByID(ID uint64) bool {
|
||||
return p.CanDeleteTrigger(&types.Trigger{ID: ID})
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreatePage() bool {
|
||||
func (p *permissions) CanCreatePage(r permissionResource) bool {
|
||||
// @todo move to func args when namespaces are implemented
|
||||
ns := &types.Namespace{ID: types.NamespaceCRM}
|
||||
return p.checkAccess(ns, "page.create")
|
||||
return p.checkAccess(r, "page.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadPage(r resource) bool {
|
||||
func (p *permissions) CanReadPage(r permissionResource) bool {
|
||||
return p.checkAccess(r, "read")
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdatePage(r resource) bool {
|
||||
func (p *permissions) CanUpdatePage(r permissionResource) bool {
|
||||
return p.checkAccess(r, "update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeletePage(r resource) bool {
|
||||
func (p *permissions) CanDeletePage(r permissionResource) bool {
|
||||
return p.checkAccess(r, "delete")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeletePageByID(ID uint64) bool {
|
||||
return p.CanDeletePage(&types.Page{ID: ID})
|
||||
}
|
||||
|
||||
func (p *permissions) checkAccess(resource resource, operation string, fallbacks ...internalRules.CheckAccessFunc) bool {
|
||||
access := p.rules.Check(resource.Resource().String(), operation, fallbacks...)
|
||||
func (p *permissions) checkAccess(resource permissionResource, operation string, fallbacks ...internalRules.CheckAccessFunc) bool {
|
||||
access := p.rules.Check(resource.PermissionResource(), operation, fallbacks...)
|
||||
if access == internalRules.Allow {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ func (svc *trigger) Find(filter types.TriggerFilter) (tt types.TriggerSet, err e
|
||||
}
|
||||
|
||||
func (svc *trigger) Create(trigger *types.Trigger) (p *types.Trigger, err error) {
|
||||
if !svc.prmSvc.CanCreateTrigger() {
|
||||
if !svc.prmSvc.CanCreateTrigger(crmNamespace()) {
|
||||
return nil, errors.New("not allowed to create this trigger")
|
||||
}
|
||||
|
||||
@ -110,7 +110,9 @@ func (svc *trigger) Update(trigger *types.Trigger) (t *types.Trigger, err error)
|
||||
}
|
||||
|
||||
func (svc *trigger) DeleteByID(ID uint64) error {
|
||||
if !svc.prmSvc.CanDeleteTriggerByID(ID) {
|
||||
if t, err := svc.triggerRepo.FindByID(ID); err != nil {
|
||||
return errors.Wrap(err, "could not delete trigger")
|
||||
} else if !svc.prmSvc.CanDeleteTrigger(t) {
|
||||
return errors.New("not allowed to delete this trigger")
|
||||
}
|
||||
|
||||
|
||||
@ -22,12 +22,6 @@ type (
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Chart) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "compose",
|
||||
Scope: "page",
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
return resource
|
||||
func (c Chart) PermissionResource() rules.Resource {
|
||||
return ChartPermissionResource.AppendID(c.ID)
|
||||
}
|
||||
|
||||
@ -24,12 +24,6 @@ type (
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Module) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "compose",
|
||||
Scope: "module",
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
return resource
|
||||
func (m Module) PermissionResource() rules.Resource {
|
||||
return ModulePermissionResource.AppendID(m.ID)
|
||||
}
|
||||
|
||||
@ -15,12 +15,6 @@ const (
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Namespace) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "compose",
|
||||
Scope: "namespace",
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
return resource
|
||||
func (n Namespace) PermissionResource() rules.Resource {
|
||||
return NamespacePermissionResource.AppendID(n.ID)
|
||||
}
|
||||
|
||||
@ -40,12 +40,6 @@ type (
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Page) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "compose",
|
||||
Scope: "page",
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
return resource
|
||||
func (p Page) PermissionResource() rules.Resource {
|
||||
return PagePermissionResource.AppendID(p.ID)
|
||||
}
|
||||
|
||||
12
crm/types/permission_resources.go
Normal file
12
crm/types/permission_resources.go
Normal file
@ -0,0 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
const PermissionResource = rules.Resource("compose")
|
||||
const NamespacePermissionResource = rules.Resource("compose:namespace:")
|
||||
const ChartPermissionResource = rules.Resource("compose:chart:")
|
||||
const ModulePermissionResource = rules.Resource("compose:module:")
|
||||
const PagePermissionResource = rules.Resource("compose:page:")
|
||||
const TriggerPermissionResource = rules.Resource("compose:trigger:")
|
||||
@ -43,12 +43,6 @@ loop:
|
||||
}
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Record) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "compose",
|
||||
Scope: "module", // intentionally using module here so we can use Record's resource
|
||||
ID: r.ModuleID,
|
||||
}
|
||||
|
||||
return resource
|
||||
func (r Record) PermissionResource() rules.Resource {
|
||||
return ModulePermissionResource.AppendID(r.ModuleID)
|
||||
}
|
||||
|
||||
@ -47,12 +47,6 @@ func (set ActionSet) Value() (driver.Value, error) {
|
||||
}
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Trigger) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "compose",
|
||||
Scope: "trigger",
|
||||
ID: r.ID,
|
||||
}
|
||||
|
||||
return resource
|
||||
func (t Trigger) PermissionResource() rules.Resource {
|
||||
return TriggerPermissionResource.AppendID(t.ID)
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
type ResourcesInterface interface {
|
||||
With(ctx context.Context, db *factory.DB) ResourcesInterface
|
||||
|
||||
Check(resource string, operation string, fallbacks ...CheckAccessFunc) Access
|
||||
Check(resource Resource, operation string, fallbacks ...CheckAccessFunc) Access
|
||||
|
||||
Grant(roleID uint64, rules []Rule) error
|
||||
Read(roleID uint64) ([]Rule, error)
|
||||
|
||||
@ -1,48 +1,77 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
Name string `json:"name"`
|
||||
Scope string `json:"scope"`
|
||||
Service string `json:"service"`
|
||||
type (
|
||||
Resource string
|
||||
)
|
||||
|
||||
const (
|
||||
resourceDelimiter = ':'
|
||||
resourceWildcard = '*'
|
||||
)
|
||||
|
||||
func (r Resource) append(suffix string) Resource {
|
||||
if !r.IsAppendable() {
|
||||
panic("can not append to non appendable resource '" + r.String() + "'")
|
||||
}
|
||||
|
||||
return Resource(r.String() + suffix)
|
||||
}
|
||||
|
||||
type ResourceJSON struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
Name string `json:"name"`
|
||||
Scope string `json:"scope"`
|
||||
Service string `json:"service"`
|
||||
ResourceID string `json:"resource"`
|
||||
// Resource to satisfty interfaces and ease development
|
||||
func (r Resource) PermissionResource() Resource {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r Resource) AppendID(ID uint64) Resource {
|
||||
return r.append(strconv.FormatUint(ID, 10))
|
||||
}
|
||||
|
||||
func (r Resource) AppendWildcard() Resource {
|
||||
return r.TrimID().append(string(resourceWildcard))
|
||||
}
|
||||
|
||||
// Trims off wildcard/id from resource
|
||||
func (r Resource) TrimID() Resource {
|
||||
s := r.String()
|
||||
p := strings.LastIndexByte(s, resourceDelimiter)
|
||||
if p > 0 {
|
||||
return Resource(s[0 : p+1])
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// IsAppendable checks if Resource has trailing resource delimiter
|
||||
func (r Resource) IsAppendable() bool {
|
||||
return strings.IndexByte(r.String(), resourceDelimiter) > -1
|
||||
}
|
||||
|
||||
// IsValid does basic resource validation
|
||||
func (r Resource) IsValid() bool {
|
||||
return len(r) > 0 && r[len(r)-1] != resourceDelimiter
|
||||
}
|
||||
|
||||
// IsServiceLevel checks for resource delimiters - service level resources do not have it
|
||||
func (r Resource) GetService() Resource {
|
||||
s := r.String()
|
||||
p := strings.IndexByte(s, resourceDelimiter)
|
||||
if p > 0 {
|
||||
return Resource(s[0:p])
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// HasWildcard checks if resource has wildcard char at the end
|
||||
func (r Resource) HasWildcard() bool {
|
||||
return len(r) > 0 && r[len(r)-1] == resourceWildcard
|
||||
}
|
||||
|
||||
func (r Resource) String() string {
|
||||
if r.ID > 0 {
|
||||
return fmt.Sprintf("%s:%s:%d", r.Service, r.Scope, r.ID)
|
||||
} else if r.Scope == "" {
|
||||
return r.Service
|
||||
}
|
||||
|
||||
return ""
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (r Resource) All() string {
|
||||
return fmt.Sprintf("%s:%s:*", r.Service, r.Scope)
|
||||
}
|
||||
|
||||
func (r Resource) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(ResourceJSON{
|
||||
r.ID,
|
||||
r.Name,
|
||||
r.Scope,
|
||||
r.Service,
|
||||
r.String(),
|
||||
})
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = Resource{}
|
||||
|
||||
@ -3,33 +3,61 @@ package rules
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/crusttech/crust/internal/test"
|
||||
)
|
||||
|
||||
func TestResource(t *testing.T) {
|
||||
var (
|
||||
assert = test.Assert
|
||||
|
||||
sCases = []struct {
|
||||
r Resource
|
||||
s string
|
||||
}{
|
||||
{
|
||||
Resource("a:b:c"),
|
||||
"a:b:c"},
|
||||
{
|
||||
Resource("a:b:c").PermissionResource(),
|
||||
"a:b:c"},
|
||||
{
|
||||
Resource("a:b:").AppendID(1),
|
||||
"a:b:1"},
|
||||
{
|
||||
Resource("a:b:").AppendWildcard(),
|
||||
"a:b:*"},
|
||||
{
|
||||
Resource("a:b:1").TrimID(),
|
||||
"a:b:"},
|
||||
{
|
||||
Resource("a:b:1").GetService(),
|
||||
"a"},
|
||||
}
|
||||
)
|
||||
r := Resource{123, "Test name", "channel", "messaging"}
|
||||
assert(t, r.String() == "messaging:channel:123", "Resource ID doesn't match, messaging:channel:123 != '%s'", r.String())
|
||||
|
||||
b, _ := json.Marshal(r)
|
||||
{
|
||||
r := ResourceJSON{}
|
||||
json.Unmarshal(b, &r)
|
||||
assert(t, r.ResourceID == "messaging:channel:123", "Decoded full-json resource ID doesn't match, messaging:channel:123 != '%s'", r.ResourceID)
|
||||
for _, sc := range sCases {
|
||||
assert(t, sc.r.String() == sc.s, "Resource check failed (%s != %s)", sc.r, sc.s)
|
||||
}
|
||||
|
||||
{
|
||||
r := Resource{}
|
||||
json.Unmarshal(b, &r)
|
||||
assert(t, r.String() == "messaging:channel:123", "Decoded full-json resource ID doesn't match, messaging:channel:123 != '%s'", r.String())
|
||||
}
|
||||
var r string
|
||||
r = "a:"
|
||||
assert(t, Resource(r).IsAppendable(), "Expecting resource %q to be appendable", r)
|
||||
r = "a:1"
|
||||
assert(t, Resource(r).IsAppendable(), "Expecting resource %q to be appendable", r)
|
||||
r = "a:*"
|
||||
assert(t, Resource(r).IsAppendable(), "Expecting resource %q to be appendable", r)
|
||||
|
||||
{
|
||||
r.ID = 0
|
||||
assert(t, r.String() == "", "Empty resource should return empty string, got '%s'", r.String())
|
||||
}
|
||||
r = "a"
|
||||
assert(t, Resource(r).IsValid(), "Expecting resource %q to be valid", r)
|
||||
r = "a:"
|
||||
assert(t, !Resource(r).IsValid(), "Expecting resource %q not to be valid", r)
|
||||
r = "a:1"
|
||||
assert(t, Resource(r).IsValid(), "Expecting resource %q to be valid", r)
|
||||
r = "a:*"
|
||||
assert(t, Resource(r).IsValid(), "Expecting resource %q to be valid", r)
|
||||
|
||||
r = "a:1"
|
||||
assert(t, !Resource(r).HasWildcard(), "Expecting resource %q to not have wildcard", r)
|
||||
r = "a:*"
|
||||
assert(t, Resource(r).HasWildcard(), "Expecting resource %q to have wildcard", r)
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
@ -10,7 +9,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
delimiter = ":"
|
||||
everyoneRoleId uint64 = 1
|
||||
defaultAccess Access = Deny
|
||||
)
|
||||
@ -29,48 +27,43 @@ func NewResources(ctx context.Context, db *factory.DB) ResourcesInterface {
|
||||
return (&resources{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r *resources) With(ctx context.Context, db *factory.DB) ResourcesInterface {
|
||||
func (rr *resources) With(ctx context.Context, db *factory.DB) ResourcesInterface {
|
||||
return &resources{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resources) identity() uint64 {
|
||||
return auth.GetIdentityFromContext(r.ctx).Identity()
|
||||
func (rr *resources) identity() uint64 {
|
||||
return auth.GetIdentityFromContext(rr.ctx).Identity()
|
||||
}
|
||||
|
||||
// IsAllowed function checks granted permission for specific resource and operation. Permission checks on
|
||||
// global level are not allowed and will always return Deny.
|
||||
func (r *resources) Check(resource string, operation string, fallbacks ...CheckAccessFunc) Access {
|
||||
func (rr *resources) Check(r Resource, operation string, fallbacks ...CheckAccessFunc) Access {
|
||||
// Number one check, do we have a valid identity?
|
||||
if !auth.GetIdentityFromContext(r.ctx).Valid() {
|
||||
if !auth.GetIdentityFromContext(rr.ctx).Valid() {
|
||||
return Deny
|
||||
}
|
||||
|
||||
parts := strings.Split(resource, delimiter)
|
||||
|
||||
// Permission check on global level is not allowed.
|
||||
if parts[len(parts)-1] == "*" || parts[len(parts)-1] == "" {
|
||||
if !r.IsValid() {
|
||||
// Make sure we do not let through wildcard or undefined resources
|
||||
return Deny
|
||||
}
|
||||
|
||||
// Resource-specific check
|
||||
checks := []CheckAccessFunc{
|
||||
func() Access { return r.checkAccess(resource, operation) },
|
||||
func() Access { return r.checkAccessEveryone(resource, operation) },
|
||||
func() Access { return rr.checkAccess(r, operation) },
|
||||
func() Access { return rr.checkAccessEveryone(r, operation) },
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
// If this is a non-service resource (so, not system, messaging),
|
||||
// add checks for any-resource (ending with `*`)
|
||||
parts[len(parts)-1] = "*"
|
||||
anyResource := strings.Join(parts, delimiter)
|
||||
if r.IsAppendable() {
|
||||
wc := r.AppendWildcard()
|
||||
|
||||
checks = append(
|
||||
checks,
|
||||
func() Access { return r.checkAccess(anyResource, operation) },
|
||||
func() Access { return r.checkAccessEveryone(anyResource, operation) },
|
||||
func() Access { return rr.checkAccess(wc, operation) },
|
||||
func() Access { return rr.checkAccessEveryone(wc, operation) },
|
||||
)
|
||||
}
|
||||
|
||||
@ -84,19 +77,19 @@ func (r *resources) Check(resource string, operation string, fallbacks ...CheckA
|
||||
return defaultAccess
|
||||
}
|
||||
|
||||
func (r *resources) checkAccess(resource string, operation string) Access {
|
||||
user := r.identity()
|
||||
result := []Access{}
|
||||
query := []string{
|
||||
// select rules
|
||||
"select r.value from sys_rules r",
|
||||
// join members
|
||||
"inner join sys_role_member m on (m.rel_role = r.rel_role and m.rel_user=?)",
|
||||
// add conditions
|
||||
"where r.resource=? and r.operation=?",
|
||||
}
|
||||
queryString := strings.Join(query, " ")
|
||||
if err := r.db.Select(&result, queryString, user, resource, operation); err != nil {
|
||||
//
|
||||
func (rr *resources) checkAccess(resource Resource, operation string) Access {
|
||||
var result = make([]Access, 0)
|
||||
|
||||
user := rr.identity()
|
||||
|
||||
query := "" +
|
||||
"SELECT r.value " +
|
||||
" FROM sys_rules r" +
|
||||
" INNER JOIN sys_role_member m ON (m.rel_role = r.rel_role AND m.rel_user = ?)" +
|
||||
" WHERE r.resource = ? AND r.operation = ?"
|
||||
|
||||
if err := rr.db.Select(&result, query, user, resource, operation); err != nil {
|
||||
// @todo: log error
|
||||
return Deny
|
||||
}
|
||||
@ -115,16 +108,15 @@ func (r *resources) checkAccess(resource string, operation string) Access {
|
||||
return Inherit
|
||||
}
|
||||
|
||||
func (r *resources) checkAccessEveryone(resource string, operation string) Access {
|
||||
result := []Access{}
|
||||
query := []string{
|
||||
// select rules
|
||||
"select r.value from sys_rules r",
|
||||
// add conditions
|
||||
"where r.rel_role = ? and r.resource=? and r.operation=?",
|
||||
}
|
||||
queryString := strings.Join(query, " ")
|
||||
if err := r.db.Select(&result, queryString, everyoneRoleId, resource, operation); err != nil {
|
||||
func (rr *resources) checkAccessEveryone(resource Resource, operation string) Access {
|
||||
var result = make([]Access, 0)
|
||||
|
||||
query := "" +
|
||||
"SELECT r.value " +
|
||||
" FROM sys_rules r" +
|
||||
" WHERE r.rel_role = ? AND r.resource = ? AND r.operation = ?"
|
||||
|
||||
if err := rr.db.Select(&result, query, everyoneRoleId, resource, operation); err != nil {
|
||||
// @todo: log error
|
||||
return Deny
|
||||
}
|
||||
@ -136,17 +128,17 @@ func (r *resources) checkAccessEveryone(resource string, operation string) Acces
|
||||
return Inherit
|
||||
}
|
||||
|
||||
func (r *resources) Grant(roleID uint64, rules []Rule) error {
|
||||
return r.db.Transaction(func() error {
|
||||
func (rr *resources) Grant(roleID uint64, rules []Rule) error {
|
||||
return rr.db.Transaction(func() error {
|
||||
var err error
|
||||
for _, rule := range rules {
|
||||
rule.RoleID = roleID
|
||||
|
||||
switch rule.Value {
|
||||
case Inherit:
|
||||
_, err = r.db.NamedExec("delete from sys_rules where rel_role=:rel_role and resource=:resource and operation=:operation", rule)
|
||||
_, err = rr.db.NamedExec("delete from sys_rules where rel_role=:rel_role and resource=:resource and operation=:operation", rule)
|
||||
default:
|
||||
err = r.db.Replace("sys_rules", rule)
|
||||
err = rr.db.Replace("sys_rules", rule)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@ -156,19 +148,19 @@ func (r *resources) Grant(roleID uint64, rules []Rule) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (r *resources) Read(roleID uint64) ([]Rule, error) {
|
||||
func (rr *resources) Read(roleID uint64) ([]Rule, error) {
|
||||
result := []Rule{}
|
||||
|
||||
query := "select * from sys_rules where rel_role = ?"
|
||||
if err := r.db.Select(&result, query, roleID); err != nil {
|
||||
if err := rr.db.Select(&result, query, roleID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *resources) Delete(roleID uint64) error {
|
||||
func (rr *resources) Delete(roleID uint64) error {
|
||||
query := "delete from sys_rules where rel_role = ?"
|
||||
if _, err := r.db.Exec(query, roleID); err != nil {
|
||||
if _, err := rr.db.Exec(query, roleID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -9,10 +9,10 @@ const (
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
RoleID uint64 `json:"roleID,string" db:"rel_role"`
|
||||
Resource string `json:"resource" db:"resource"`
|
||||
Operation string `json:"operation" db:"operation"`
|
||||
Value Access `json:"value,string" db:"value"`
|
||||
RoleID uint64 `json:"roleID,string" db:"rel_role"`
|
||||
Resource Resource `json:"resource" db:"resource"`
|
||||
Operation string `json:"operation" db:"operation"`
|
||||
Value Access `json:"value,string" db:"value"`
|
||||
}
|
||||
|
||||
func (a *Access) UnmarshalJSON(data []byte) error {
|
||||
|
||||
@ -18,6 +18,10 @@ type (
|
||||
rules systemService.RulesService
|
||||
}
|
||||
|
||||
resource interface {
|
||||
PermissionResource() internalRules.Resource
|
||||
}
|
||||
|
||||
PermissionsService interface {
|
||||
With(context.Context) PermissionsService
|
||||
|
||||
@ -55,9 +59,9 @@ type (
|
||||
}
|
||||
|
||||
effectivePermission struct {
|
||||
Resource string `json:"resource"`
|
||||
Operation string `json:"operation"`
|
||||
Allow bool `json:"allow"`
|
||||
Resource internalRules.Resource `json:"resource"`
|
||||
Operation string `json:"operation"`
|
||||
Allow bool `json:"allow"`
|
||||
}
|
||||
)
|
||||
|
||||
@ -78,7 +82,7 @@ func (p *permissions) With(ctx context.Context) PermissionsService {
|
||||
}
|
||||
|
||||
func (p *permissions) Effective() (ee []effectivePermission, err error) {
|
||||
ep := func(res, op string, allow bool) effectivePermission {
|
||||
ep := func(res internalRules.Resource, op string, allow bool) effectivePermission {
|
||||
return effectivePermission{
|
||||
Resource: res,
|
||||
Operation: op,
|
||||
@ -86,41 +90,41 @@ func (p *permissions) Effective() (ee []effectivePermission, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
ee = append(ee, ep("messaging", "access", p.CanAccess()))
|
||||
ee = append(ee, ep("messaging", "grant", p.CanGrant()))
|
||||
ee = append(ee, ep("messaging", "channel.public.create", p.CanCreatePublicChannel()))
|
||||
ee = append(ee, ep("messaging", "channel.private.create", p.CanCreatePrivateChannel()))
|
||||
ee = append(ee, ep("messaging", "channel.group.create", p.CanCreateGroupChannel()))
|
||||
ee = append(ee, ep(types.PermissionResource, "access", p.CanAccess()))
|
||||
ee = append(ee, ep(types.PermissionResource, "grant", p.CanGrant()))
|
||||
ee = append(ee, ep(types.PermissionResource, "channel.public.create", p.CanCreatePublicChannel()))
|
||||
ee = append(ee, ep(types.PermissionResource, "channel.private.create", p.CanCreatePrivateChannel()))
|
||||
ee = append(ee, ep(types.PermissionResource, "channel.group.create", p.CanCreateGroupChannel()))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *permissions) CanAccess() bool {
|
||||
return p.checkAccess("messaging", "access")
|
||||
return p.checkAccess(types.PermissionResource, "access")
|
||||
}
|
||||
|
||||
func (p *permissions) CanGrant() bool {
|
||||
return p.checkAccess("messaging", "grant")
|
||||
return p.checkAccess(types.PermissionResource, "grant")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreatePublicChannel() bool {
|
||||
return p.checkAccess("messaging", "channel.public.create", p.allow())
|
||||
return p.checkAccess(types.PermissionResource, "channel.public.create", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreatePrivateChannel() bool {
|
||||
return p.checkAccess("messaging", "channel.private.create", p.allow())
|
||||
return p.checkAccess(types.PermissionResource, "channel.private.create", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateGroupChannel() bool {
|
||||
return p.checkAccess("messaging", "channel.group.create", p.allow())
|
||||
return p.checkAccess(types.PermissionResource, "channel.group.create", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "update", p.isChannelOwnerFallback(ch))
|
||||
return p.checkAccess(ch, "update", p.isChannelOwnerFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "read", p.canReadFallback(ch))
|
||||
return p.checkAccess(ch, "read", p.canReadFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadChannelByID(id uint64) bool {
|
||||
@ -128,76 +132,76 @@ func (p *permissions) CanReadChannelByID(id uint64) bool {
|
||||
}
|
||||
|
||||
func (p *permissions) CanJoinChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "join", p.canJoinFallback(ch))
|
||||
return p.checkAccess(ch, "join", p.canJoinFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanLeaveChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "leave", p.allow())
|
||||
return p.checkAccess(ch, "leave", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanArchiveChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "archive", p.isChannelOwnerFallback(ch))
|
||||
return p.checkAccess(ch, "archive", p.isChannelOwnerFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanUnarchiveChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "unarchive", p.isChannelOwnerFallback(ch))
|
||||
return p.checkAccess(ch, "unarchive", p.isChannelOwnerFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "delete", p.isChannelOwnerFallback(ch))
|
||||
return p.checkAccess(ch, "delete", p.isChannelOwnerFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanUndeleteChannel(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "undelete", p.isChannelOwnerFallback(ch))
|
||||
return p.checkAccess(ch, "undelete", p.isChannelOwnerFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanManageChannelMembers(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "members.manage", p.isChannelOwnerFallback(ch))
|
||||
return p.checkAccess(ch, "members.manage", p.isChannelOwnerFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanManageChannelWebhooks(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "webhooks.manage")
|
||||
return p.checkAccess(ch, "webhooks.manage")
|
||||
}
|
||||
|
||||
func (p *permissions) CanManageChannelAttachments(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "attachments.manage")
|
||||
return p.checkAccess(ch, "attachments.manage")
|
||||
}
|
||||
|
||||
func (p *permissions) CanSendMessage(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.send", p.canSendMessagesFallback(ch))
|
||||
return p.checkAccess(ch, "message.send", p.canSendMessagesFallback(ch))
|
||||
}
|
||||
|
||||
func (p *permissions) CanReplyMessage(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.reply", p.allow())
|
||||
return p.checkAccess(ch, "message.reply", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanEmbedMessage(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.embed", p.allow())
|
||||
return p.checkAccess(ch, "message.embed", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanAttachMessage(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.attach", p.allow())
|
||||
return p.checkAccess(ch, "message.attach", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateOwnMessages(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.update.own", p.allow())
|
||||
return p.checkAccess(ch, "message.update.own", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateMessages(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.update.all")
|
||||
return p.checkAccess(ch, "message.update.all")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteOwnMessages(ch *types.Channel) bool {
|
||||
// @todo implement
|
||||
return p.checkAccess(ch.Resource().String(), "message.delete.own", p.allow())
|
||||
return p.checkAccess(ch, "message.delete.own", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteMessages(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.delete.all")
|
||||
return p.checkAccess(ch, "message.delete.all")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReactMessage(ch *types.Channel) bool {
|
||||
return p.checkAccess(ch.Resource().String(), "message.react", p.allow())
|
||||
return p.checkAccess(ch, "message.react", p.allow())
|
||||
}
|
||||
|
||||
func (p permissions) canJoinFallback(ch *types.Channel) func() internalRules.Access {
|
||||
@ -255,8 +259,8 @@ func (p permissions) isChannelOwnerFallback(ch *types.Channel) func() internalRu
|
||||
}
|
||||
}
|
||||
|
||||
func (p *permissions) checkAccess(resource string, operation string, fallbacks ...internalRules.CheckAccessFunc) bool {
|
||||
access := p.rules.Check(resource, operation, fallbacks...)
|
||||
func (p *permissions) checkAccess(res resource, operation string, fallbacks ...internalRules.CheckAccessFunc) bool {
|
||||
access := p.rules.Check(res.PermissionResource(), operation, fallbacks...)
|
||||
if access == internalRules.Allow {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
// Hello! This file is auto-generated.
|
||||
|
||||
type (
|
||||
@ -69,16 +65,3 @@ func (set ChannelSet) IDs() (IDs []uint64) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Resources returns a slice of types.Resource from all items in the set
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set ChannelSet) Resources() (Resources []rules.Resource) {
|
||||
Resources = make([]rules.Resource, len(set))
|
||||
|
||||
for i := range set {
|
||||
Resources[i] = set[i].Resource()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -61,16 +61,8 @@ type (
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Channel) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "messaging",
|
||||
Scope: "channel",
|
||||
}
|
||||
if r != nil {
|
||||
resource.ID = r.ID
|
||||
resource.Name = r.Name
|
||||
}
|
||||
return resource
|
||||
func (c Channel) PermissionResource() rules.Resource {
|
||||
return ChannelPermissionResource.AppendID(c.ID)
|
||||
}
|
||||
|
||||
func (c *Channel) IsValid() bool {
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
systemTypes "github.com/crusttech/crust/system/types"
|
||||
)
|
||||
|
||||
@ -126,3 +127,7 @@ func (mtype MessageType) Value() (driver.Value, error) {
|
||||
func (m *Message) IsValid() bool {
|
||||
return m.DeletedAt == nil
|
||||
}
|
||||
|
||||
func (m Message) PermissionResource() rules.Resource {
|
||||
return ChannelPermissionResource.AppendID(m.ChannelID)
|
||||
}
|
||||
|
||||
8
messaging/types/permission_resources.go
Normal file
8
messaging/types/permission_resources.go
Normal file
@ -0,0 +1,8 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
const PermissionResource = rules.Resource("messaging")
|
||||
const ChannelPermissionResource = rules.Resource("messaging:channel:")
|
||||
@ -1,16 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
type (
|
||||
ResourceProvider interface {
|
||||
Resource() rules.Resource
|
||||
}
|
||||
)
|
||||
|
||||
// These entities create resources in RBAC
|
||||
var _ ResourceProvider = &Organisation{}
|
||||
var _ ResourceProvider = &Role{}
|
||||
var _ ResourceProvider = &Channel{}
|
||||
@ -1,11 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
Role struct {
|
||||
types.Role
|
||||
}
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
@ -16,6 +16,10 @@ type (
|
||||
rules RulesService
|
||||
}
|
||||
|
||||
resource interface {
|
||||
PermissionResource() internalRules.Resource
|
||||
}
|
||||
|
||||
PermissionsService interface {
|
||||
With(context.Context) PermissionsService
|
||||
|
||||
@ -77,55 +81,51 @@ func (p *permissions) Effective() (ee []effectivePermission, err error) {
|
||||
}
|
||||
|
||||
func (p *permissions) CanAccess() bool {
|
||||
return p.checkAccess("system", "access")
|
||||
return p.checkAccess(types.PermissionResource, "access")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateOrganisation() bool {
|
||||
return p.checkAccess("system", "organisation.create")
|
||||
return p.checkAccess(types.PermissionResource, "organisation.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateRole() bool {
|
||||
return p.checkAccess("system", "role.create")
|
||||
return p.checkAccess(types.PermissionResource, "role.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateApplication() bool {
|
||||
return p.checkAccess("system", "application.create")
|
||||
return p.checkAccess(types.PermissionResource, "application.create")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadRole(rl *types.Role) bool {
|
||||
return p.checkAccess(rl.Resource().String(), "read", p.allow())
|
||||
return p.checkAccess(rl, "read", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateRole(rl *types.Role) bool {
|
||||
return p.checkAccess(rl.Resource().String(), "update")
|
||||
return p.checkAccess(rl, "update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteRole(rl *types.Role) bool {
|
||||
return p.checkAccess(rl.Resource().String(), "delete")
|
||||
return p.checkAccess(rl, "delete")
|
||||
}
|
||||
|
||||
func (p *permissions) CanManageRoleMembers(rl *types.Role) bool {
|
||||
return p.checkAccess(rl.Resource().String(), "members.manage")
|
||||
return p.checkAccess(rl, "members.manage")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadApplication(app *types.Application) bool {
|
||||
return p.checkAccess(app.Resource().String(), "read", p.allow())
|
||||
return p.checkAccess(app, "read", p.allow())
|
||||
}
|
||||
|
||||
func (p *permissions) CanUpdateApplication(app *types.Application) bool {
|
||||
return p.checkAccess(app.Resource().String(), "update")
|
||||
return p.checkAccess(app, "update")
|
||||
}
|
||||
|
||||
func (p *permissions) CanDeleteApplication(app *types.Application) bool {
|
||||
return p.checkAccess(app.Resource().String(), "delete")
|
||||
return p.checkAccess(app, "delete")
|
||||
}
|
||||
|
||||
func (p *permissions) checkAccess(resource string, operation string, fallbacks ...internalRules.CheckAccessFunc) bool {
|
||||
access := p.rules.Check(resource, operation, fallbacks...)
|
||||
if access == internalRules.Allow {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
func (p *permissions) checkAccess(r resource, operation string, fallbacks ...internalRules.CheckAccessFunc) bool {
|
||||
return p.rules.Check(r.PermissionResource(), operation, fallbacks...) == internalRules.Allow
|
||||
}
|
||||
|
||||
func (p permissions) allow() func() internalRules.Access {
|
||||
|
||||
@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@ -35,7 +34,7 @@ type (
|
||||
List() (interface{}, error)
|
||||
Effective(filter string) ([]EffectiveRules, error)
|
||||
|
||||
Check(resource string, operation string, fallbacks ...internalRules.CheckAccessFunc) internalRules.Access
|
||||
Check(resource internalRules.Resource, operation string, fallbacks ...internalRules.CheckAccessFunc) internalRules.Access
|
||||
|
||||
Read(roleID uint64) (interface{}, error)
|
||||
Update(roleID uint64, rules []internalRules.Rule) (interface{}, error)
|
||||
@ -60,11 +59,8 @@ func (p *rules) With(ctx context.Context) RulesService {
|
||||
func (p *rules) List() (interface{}, error) {
|
||||
perms := []types.Permission{}
|
||||
for resource, operations := range permissionList {
|
||||
err := p.checkServiceAccess(resource)
|
||||
if err == nil {
|
||||
for ops := range operations {
|
||||
perms = append(perms, types.Permission{Resource: resource, Operation: ops})
|
||||
}
|
||||
for ops := range operations {
|
||||
perms = append(perms, types.Permission{Resource: resource, Operation: ops})
|
||||
}
|
||||
}
|
||||
return perms, nil
|
||||
@ -87,7 +83,7 @@ func (p *rules) Effective(filter string) (eff []EffectiveRules, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *rules) Check(resource string, operation string, fallbacks ...internalRules.CheckAccessFunc) internalRules.Access {
|
||||
func (p *rules) Check(resource internalRules.Resource, operation string, fallbacks ...internalRules.CheckAccessFunc) internalRules.Access {
|
||||
return p.resources.Check(resource, operation, fallbacks...)
|
||||
}
|
||||
|
||||
@ -130,12 +126,11 @@ func (p *rules) Delete(roleID uint64) (interface{}, error) {
|
||||
return nil, p.resources.Delete(roleID)
|
||||
}
|
||||
|
||||
func (p *rules) checkServiceAccess(resource string) error {
|
||||
service := strings.Split(resource, delimiter)[0]
|
||||
|
||||
grant := p.resources.Check(service, "grant")
|
||||
func (p *rules) checkServiceAccess(resource internalRules.Resource) error {
|
||||
// First, we trim off resource to get service - only service resource have grant operation
|
||||
grant := p.resources.Check(resource.GetService(), "grant")
|
||||
if grant == internalRules.Allow {
|
||||
return nil
|
||||
}
|
||||
return errors.Errorf("No grant permissions for: %v", service)
|
||||
return errors.Errorf("No grant permissions for: %q", resource)
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
internalRules "github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -15,16 +15,16 @@ var (
|
||||
"role.create": true,
|
||||
"application.create": true,
|
||||
},
|
||||
"system:organisation": map[string]bool{
|
||||
"system:organisation:": map[string]bool{
|
||||
"access": true,
|
||||
},
|
||||
"system:role": map[string]bool{
|
||||
"system:role:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
"members.manage": true,
|
||||
},
|
||||
"system:application": map[string]bool{
|
||||
"system:application:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
@ -36,7 +36,7 @@ var (
|
||||
"channel.private.create": true,
|
||||
"channel.group.create": true,
|
||||
},
|
||||
"messaging:channel": map[string]bool{
|
||||
"messaging:channel:": map[string]bool{
|
||||
"update": true,
|
||||
"read": true,
|
||||
"join": true,
|
||||
@ -63,7 +63,7 @@ var (
|
||||
"grant": true,
|
||||
"namespace.create": true,
|
||||
},
|
||||
"compose:namespace": map[string]bool{
|
||||
"compose:namespace:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
@ -72,7 +72,7 @@ var (
|
||||
"trigger.create": true,
|
||||
"page.create": true,
|
||||
},
|
||||
"compose:module": map[string]bool{
|
||||
"compose:module:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
@ -81,17 +81,17 @@ var (
|
||||
"record.update": true,
|
||||
"record.delete": true,
|
||||
},
|
||||
"compose:chart": map[string]bool{
|
||||
"compose:chart:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
},
|
||||
"compose:trigger": map[string]bool{
|
||||
"compose:trigger:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
},
|
||||
"compose:page": map[string]bool{
|
||||
"compose:page:": map[string]bool{
|
||||
"read": true,
|
||||
"update": true,
|
||||
"delete": true,
|
||||
@ -99,28 +99,18 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func validatePermission(resource string, operation string) error {
|
||||
resourceParts := strings.Split(resource, delimiter)
|
||||
if len(resourceParts) < 1 {
|
||||
return errors.Errorf("Invalid resource format, expected >= 1, got %d", len(resourceParts))
|
||||
func validatePermission(resource internalRules.Resource, operation string) error {
|
||||
if !resource.IsValid() {
|
||||
return errors.Errorf("invalid resource format: %q", resource)
|
||||
}
|
||||
|
||||
resourceName := resourceParts[0]
|
||||
if len(resourceParts) > 1 {
|
||||
resourceName = resourceParts[0] + delimiter + resourceParts[1]
|
||||
}
|
||||
res := resource.TrimID().String()
|
||||
|
||||
if service, ok := permissionList[resourceName]; ok {
|
||||
if service, ok := permissionList[res]; ok {
|
||||
if op := service[operation]; op {
|
||||
if len(resourceParts) == 3 {
|
||||
if val := resourceParts[2]; val != "" {
|
||||
return nil
|
||||
}
|
||||
return errors.Errorf("Invalid resource format, missing resource ID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.Errorf("Unknown operation: '%s'", operation)
|
||||
}
|
||||
return errors.Errorf("Unknown resource name: '%s'", resourceName)
|
||||
return errors.Errorf("Unknown resource name: '%s'", resource)
|
||||
}
|
||||
|
||||
@ -11,13 +11,13 @@ type (
|
||||
RulesService interface {
|
||||
List() (interface{}, error)
|
||||
Effective(filter string) ([]service.EffectiveRules, error)
|
||||
Check(resource string, operation string, fallbacks ...rules.CheckAccessFunc) rules.Access
|
||||
Check(resource rules.Resource, operation string, fallbacks ...rules.CheckAccessFunc) rules.Access
|
||||
Read(roleID uint64) (interface{}, error)
|
||||
}
|
||||
)
|
||||
|
||||
var DefaultRules = service.DefaultRules
|
||||
|
||||
func Rules(ctx context.Context) RulesService {
|
||||
func Rules(ctx context.Context) service.RulesService {
|
||||
return DefaultRules.With(ctx)
|
||||
}
|
||||
|
||||
@ -43,17 +43,9 @@ func (u *Application) Identity() uint64 {
|
||||
return u.ID
|
||||
}
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (u *Application) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "system",
|
||||
Scope: "application",
|
||||
}
|
||||
if u != nil {
|
||||
resource.ID = u.ID
|
||||
resource.Name = u.Name
|
||||
}
|
||||
return resource
|
||||
// Resource returns a resource ID for this type
|
||||
func (u Application) PermissionResource() rules.Resource {
|
||||
return ApplicationPermissionResource.AppendID(u.ID)
|
||||
}
|
||||
|
||||
func (au *ApplicationUnify) Scan(value interface{}) error {
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
// Hello! This file is auto-generated.
|
||||
|
||||
type (
|
||||
@ -69,16 +65,3 @@ func (set OrganisationSet) IDs() (IDs []uint64) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Resources returns a slice of types.Resource from all items in the set
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set OrganisationSet) Resources() (Resources []rules.Resource) {
|
||||
Resources = make([]rules.Resource, len(set))
|
||||
|
||||
for i := range set {
|
||||
Resources[i] = set[i].Resource()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -23,15 +23,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Organisation) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "system",
|
||||
Scope: "organisation",
|
||||
}
|
||||
if r != nil {
|
||||
resource.ID = r.ID
|
||||
resource.Name = r.Name
|
||||
}
|
||||
return resource
|
||||
// Resource returns a resource ID for this type
|
||||
func (o *Organisation) PermissionResource() rules.Resource {
|
||||
return OrganisationPermissionResource.AppendID(o.ID)
|
||||
}
|
||||
|
||||
10
system/types/permission_resources.go
Normal file
10
system/types/permission_resources.go
Normal file
@ -0,0 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
const PermissionResource = rules.Resource("system")
|
||||
const ApplicationPermissionResource = rules.Resource("system:application:")
|
||||
const OrganisationPermissionResource = rules.Resource("system:organisation:")
|
||||
const RolePermissionResource = rules.Resource("system:role:")
|
||||
@ -1,9 +1,5 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/crusttech/crust/internal/rules"
|
||||
)
|
||||
|
||||
// Hello! This file is auto-generated.
|
||||
|
||||
type (
|
||||
@ -69,16 +65,3 @@ func (set RoleSet) IDs() (IDs []uint64) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Resources returns a slice of types.Resource from all items in the set
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set RoleSet) Resources() (Resources []rules.Resource) {
|
||||
Resources = make([]rules.Resource, len(set))
|
||||
|
||||
for i := range set {
|
||||
Resources[i] = set[i].Resource()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -23,15 +23,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// Resource returns a system resource ID for this type
|
||||
func (r *Role) Resource() rules.Resource {
|
||||
resource := rules.Resource{
|
||||
Service: "system",
|
||||
Scope: "role",
|
||||
}
|
||||
if r != nil {
|
||||
resource.ID = r.ID
|
||||
resource.Name = r.Name
|
||||
}
|
||||
return resource
|
||||
// Resource returns a resource ID for this type
|
||||
func (r *Role) PermissionResource() rules.Resource {
|
||||
return RolePermissionResource.AppendID(r.ID)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user