diff --git a/codegen.sh b/codegen.sh index 762a9907a..40eac5edc 100755 --- a/codegen.sh +++ b/codegen.sh @@ -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" } diff --git a/codegen/v2/type-set.go b/codegen/v2/type-set.go index e3f3e28c0..dca3190a5 100644 --- a/codegen/v2/type-set.go +++ b/codegen/v2/type-set.go @@ -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 }} ` diff --git a/crm/internal/repository/record.go b/crm/internal/repository/record.go index a7a7f9c76..8664b332f 100644 --- a/crm/internal/repository/record.go +++ b/crm/internal/repository/record.go @@ -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 } diff --git a/crm/internal/service/chart.go b/crm/internal/service/chart.go index 0d86fc1d9..2335a9bd0 100644 --- a/crm/internal/service/chart.go +++ b/crm/internal/service/chart.go @@ -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") } diff --git a/crm/internal/service/module.go b/crm/internal/service/module.go index 35d8dd432..791556b03 100644 --- a/crm/internal/service/module.go +++ b/crm/internal/service/module.go @@ -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") } diff --git a/crm/internal/service/page.go b/crm/internal/service/page.go index 15a6ae407..81d0f9510 100644 --- a/crm/internal/service/page.go +++ b/crm/internal/service/page.go @@ -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") } diff --git a/crm/internal/service/permissions.go b/crm/internal/service/permissions.go index e7530fe3f..e4bf725a9 100644 --- a/crm/internal/service/permissions.go +++ b/crm/internal/service/permissions.go @@ -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 } diff --git a/crm/internal/service/trigger.go b/crm/internal/service/trigger.go index 6ba67e1d8..ecd2cc54d 100644 --- a/crm/internal/service/trigger.go +++ b/crm/internal/service/trigger.go @@ -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") } diff --git a/crm/types/chart.go b/crm/types/chart.go index 65182dfb9..d4bdcd3f3 100644 --- a/crm/types/chart.go +++ b/crm/types/chart.go @@ -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) } diff --git a/crm/types/module.go b/crm/types/module.go index 01e561ac4..638be5d49 100644 --- a/crm/types/module.go +++ b/crm/types/module.go @@ -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) } diff --git a/crm/types/namespace.go b/crm/types/namespace.go index 3231c958b..3a60ecd3f 100644 --- a/crm/types/namespace.go +++ b/crm/types/namespace.go @@ -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) } diff --git a/crm/types/page.go b/crm/types/page.go index 2d8fab143..1a20ac8e7 100644 --- a/crm/types/page.go +++ b/crm/types/page.go @@ -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) } diff --git a/crm/types/permission_resources.go b/crm/types/permission_resources.go new file mode 100644 index 000000000..dfba8fdad --- /dev/null +++ b/crm/types/permission_resources.go @@ -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:") diff --git a/crm/types/record.go b/crm/types/record.go index 10aa1b076..e542bc3c3 100644 --- a/crm/types/record.go +++ b/crm/types/record.go @@ -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) } diff --git a/crm/types/trigger.go b/crm/types/trigger.go index 14b4c4018..3baf2916c 100644 --- a/crm/types/trigger.go +++ b/crm/types/trigger.go @@ -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) } diff --git a/internal/rules/interfaces.go b/internal/rules/interfaces.go index 1ef4add14..e52a45b34 100644 --- a/internal/rules/interfaces.go +++ b/internal/rules/interfaces.go @@ -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) diff --git a/internal/rules/resource.go b/internal/rules/resource.go index b58686ffd..f703a13b6 100644 --- a/internal/rules/resource.go +++ b/internal/rules/resource.go @@ -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{} diff --git a/internal/rules/resource_test.go b/internal/rules/resource_test.go index 262ebf2dd..07f817bcc 100644 --- a/internal/rules/resource_test.go +++ b/internal/rules/resource_test.go @@ -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) } diff --git a/internal/rules/resources.go b/internal/rules/resources.go index 6f5a91dde..e153edc7c 100644 --- a/internal/rules/resources.go +++ b/internal/rules/resources.go @@ -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 diff --git a/internal/rules/rule.go b/internal/rules/rule.go index ac39f839f..3229cd306 100644 --- a/internal/rules/rule.go +++ b/internal/rules/rule.go @@ -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 { diff --git a/messaging/internal/service/permissions.go b/messaging/internal/service/permissions.go index d95ebf34d..191116271 100644 --- a/messaging/internal/service/permissions.go +++ b/messaging/internal/service/permissions.go @@ -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 } diff --git a/messaging/types/channel.gen.go b/messaging/types/channel.gen.go index 714dc22d5..66f623fb3 100644 --- a/messaging/types/channel.gen.go +++ b/messaging/types/channel.gen.go @@ -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 -} diff --git a/messaging/types/channel.go b/messaging/types/channel.go index 4da87c211..e2fb1636d 100644 --- a/messaging/types/channel.go +++ b/messaging/types/channel.go @@ -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 { diff --git a/messaging/types/message.go b/messaging/types/message.go index 7c220b4a3..89e71e4fc 100644 --- a/messaging/types/message.go +++ b/messaging/types/message.go @@ -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) +} diff --git a/messaging/types/permission_resources.go b/messaging/types/permission_resources.go new file mode 100644 index 000000000..45bb0199b --- /dev/null +++ b/messaging/types/permission_resources.go @@ -0,0 +1,8 @@ +package types + +import ( + "github.com/crusttech/crust/internal/rules" +) + +const PermissionResource = rules.Resource("messaging") +const ChannelPermissionResource = rules.Resource("messaging:channel:") diff --git a/messaging/types/permissions.go b/messaging/types/permissions.go deleted file mode 100644 index f088bac92..000000000 --- a/messaging/types/permissions.go +++ /dev/null @@ -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{} diff --git a/messaging/types/role.go b/messaging/types/role.go deleted file mode 100644 index 42b4cf4e4..000000000 --- a/messaging/types/role.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -import ( - "github.com/crusttech/crust/system/types" -) - -type ( - Role struct { - types.Role - } -) diff --git a/system/db/mysql/statik.go b/system/db/mysql/statik.go index 3120ef3f1..512451cbd 100644 --- a/system/db/mysql/statik.go +++ b/system/db/mysql/statik.go @@ -8,7 +8,7 @@ import ( ) func Data() string { - return "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE settings (\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value TEXT COMMENT 'Setting value',\n\n PRIMARY KEY (name)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE users (\n id BIGINT UNSIGNED NOT NULL,\n email TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT NOT NULL,\n name TEXT NOT NULL,\n handle TEXT NOT NULL,\n meta JSON NOT NULL,\n satosa_id CHAR(36) NULL,\n\n rel_organisation BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n suspended_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE UNIQUE INDEX uid_satosa ON users (satosa_id);\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xedzU\x8am \x00\x00m \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE teams RENAME TO sys_team;\nALTER TABLE organisations RENAME TO sys_organisation;\nALTER TABLE team_members RENAME TO sys_team_member;\nALTER TABLE users RENAME TO sys_user;PK\x07\x08\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8# add field to manage user type (bot support)\nALTER TABLE `sys_user` ADD `kind` VARCHAR(8) NOT NULL DEFAULT '' AFTER `handle`;\n\n# add field to manage \"ownership\" (get all bots created by user)\nALTER TABLE `sys_user` ADD `rel_user_id` BIGINT UNSIGNED NOT NULL AFTER `rel_organisation`, ADD INDEX (`rel_user_id`);\nPK\x07\x089\xa0\xdat8\x01\x00\x008\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP INDEX `uid_satosa`, ADD INDEX `uid_satosa` (`satosa_id`) USING BTREE;PK\x07\x08\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE sys_credentials (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n label TEXT NOT NULL COMMENT 'something we can differentiate credentials by',\n kind VARCHAR(128) NOT NULL COMMENT 'hash, facebook, gplus, github, linkedin ...',\n credentials TEXT NOT NULL COMMENT 'crypted/hashed passwords, secrets, social profile ID',\n meta JSON NOT NULL,\n expires_at DATETIME NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX idx_owner ON sys_credentials (rel_owner);\nPK\x07\x08f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` MODIFY `password` TEXT NULL;\nPK\x07\x080V\x13\x0f4\x00\x00\x004\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00 \x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `sys_rules` (\n `rel_team` BIGINT UNSIGNED NOT NULL,\n `resource` VARCHAR(128) NOT NULL,\n `operation` VARCHAR(128) NOT NULL,\n `value` TINYINT(1) NOT NULL,\n\n PRIMARY KEY (`rel_team`, `resource`, `operation`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE sys_team RENAME TO sys_role;\nALTER TABLE sys_team_member RENAME TO sys_role_member;\n\nALTER TABLE `sys_role_member` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nALTER TABLE `sys_rules` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nPK\x07\x08s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,\x00 \x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8REPLACE INTO `sys_role` (`id`, `name`, `handle`) VALUES\n (1, 'Everyone', 'everyone'),\n (2, 'Administrators', 'admins');\n\n-- Value: Allow (2), Deny (1), Inherit(0),\nREPLACE INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n-- Everyone\n (1, 'compose:*', 'access', 2),\n (1, 'messaging:*', 'access', 2),\n-- Admins\n (2, 'compose', 'namespace.create', 2),\n (2, 'compose', 'access', 2),\n (2, 'compose', 'grant', 2),\n (2, 'compose:namespace:*', 'page.create', 2),\n (2, 'compose:namespace:*', 'read', 2),\n (2, 'compose:namespace:*', 'update', 2),\n (2, 'compose:namespace:*', 'delete', 2),\n (2, 'compose:namespace:*', 'module.create', 2),\n (2, 'compose:namespace:*', 'chart.create', 2),\n (2, 'compose:namespace:*', 'trigger.create', 2),\n (2, 'compose:chart:*', 'read', 2),\n (2, 'compose:chart:*', 'update', 2),\n (2, 'compose:chart:*', 'delete', 2),\n (2, 'compose:trigger:*', 'read', 2),\n (2, 'compose:trigger:*', 'update', 2),\n (2, 'compose:trigger:*', 'delete', 2),\n (2, 'compose:page:*', 'read', 2),\n (2, 'compose:page:*', 'update', 2),\n (2, 'compose:page:*', 'delete', 2),\n (2, 'system:*', 'access', 2),\n (2, 'system:*', 'grant', 2),\n (2, 'system:*', 'organisation.create', 2),\n (2, 'system:*', 'role.create', 2),\n (2, 'system:organisation:*', 'access', 2),\n (2, 'system:role:*', 'read', 2),\n (2, 'system:role:*', 'update', 2),\n (2, 'system:role:*', 'delete', 2),\n (2, 'system:role:*', 'members.manage', 2),\n (2, 'messaging:*', 'access', 2),\n (2, 'messaging:*', 'grant', 2),\n (2, 'messaging:*', 'channel.public.create', 2),\n (2, 'messaging:*', 'channel.private.create', 2),\n (2, 'messaging:*', 'channel.group.create', 2),\n (2, 'messaging:channel:*', 'update', 2),\n (2, 'messaging:channel:*', 'leave', 2),\n (2, 'messaging:channel:*', 'read', 2),\n (2, 'messaging:channel:*', 'join', 2),\n (2, 'messaging:channel:*', 'delete', 2),\n (2, 'messaging:channel:*', 'undelete', 2),\n (2, 'messaging:channel:*', 'archive', 2),\n (2, 'messaging:channel:*', 'unarchive', 2),\n (2, 'messaging:channel:*', 'members.manage', 2),\n (2, 'messaging:channel:*', 'webhooks.manage', 2),\n (2, 'messaging:channel:*', 'attachments.manage', 2),\n (2, 'messaging:channel:*', 'message.attach', 2),\n (2, 'messaging:channel:*', 'message.update.all', 2),\n (2, 'messaging:channel:*', 'message.update.own', 2),\n (2, 'messaging:channel:*', 'message.delete.all', 2),\n (2, 'messaging:channel:*', 'message.delete.own', 2),\n (2, 'messaging:channel:*', 'message.embed', 2),\n (2, 'messaging:channel:*', 'message.send', 2),\n (2, 'messaging:channel:*', 'message.reply', 2),\n (2, 'messaging:channel:*', 'message.react', 2),\n (2, 'compose:module:*', 'read', 2),\n (2, 'compose:module:*', 'update', 2),\n (2, 'compose:module:*', 'delete', 2),\n (2, 'compose:module:*', 'record.create', 2),\n (2, 'compose:module:*', 'record.read', 2),\n (2, 'compose:module:*', 'record.update', 2),\n (2, 'compose:module:*', 'record.delete', 2);\nPK\x07\x08\xe95\xd5#\x86\x0b\x00\x00\x86\x0b\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00 \x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE sys_application (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n name TEXT NOT NULL COMMENT 'something we can differentiate application by',\n enabled BOOL NOT NULL,\n\n unify JSON NULL COMMENT 'unify specific settings',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n\nREPLACE INTO `sys_application` (`id`, `name`, `enabled`, `rel_owner`, `unify`) VALUES\n( 1, 'Crust Messaging', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/messaging/\", \"listed\": true}'\n),\n( 2, 'Crust CRM', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/crm/\", \"listed\": true}'\n),\n( 3, 'Crust Admin Area', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/admin/\", \"listed\": true}'\n),\n( 4, 'Corteza Jitsi Bridge', true, 0,\n '{\"logo\": \"/applications/jitsi.png\", \"icon\": \"/applications/jitsi_icon.png\", \"url\": \"/bridge/jitsi/\", \"listed\": true}'\n),\n( 5, 'Google Maps', true, 0,\n '{\"logo\": \"/applications/google_maps.png\", \"icon\": \"/applications/google_maps_icon.png\", \"url\": \"/bridge/google-maps/\", \"listed\": true}'\n);\n\n-- Allow admin access to applications\nINSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n (1, 'system:application:*', 'read', 2),\n (2, 'system', 'application.create', 2),\n (2, 'system:application:*', 'read', 2),\n (2, 'system:application:*', 'update', 2),\n (2, 'system:application:*', 'delete', 2)\n;\nPK\x07\x08\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xedzU\x8am \x00\x00m \x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe \x00\x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9\xa0\xdat8\x01\x00\x008\x01\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd8\n\x00\x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81t\x0c\x00\x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x0d\x00\x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(0V\x13\x0f4\x00\x00\x004\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81+\x11\x00\x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00\x1b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbf\x11\x00\x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x16\x13\x00\x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xe95\xd5#\x86\x0b\x00\x00\x86\x0b\x00\x00,\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x89\x14\x00\x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00\"\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r \x00\x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xdc'\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\x99)\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x0c\x00\x0c\x00&\x04\x00\x00\x04*\x00\x00\x00\x00" + return "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE settings (\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value TEXT COMMENT 'Setting value',\n\n PRIMARY KEY (name)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE users (\n id BIGINT UNSIGNED NOT NULL,\n email TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT NOT NULL,\n name TEXT NOT NULL,\n handle TEXT NOT NULL,\n meta JSON NOT NULL,\n satosa_id CHAR(36) NULL,\n\n rel_organisation BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n suspended_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE UNIQUE INDEX uid_satosa ON users (satosa_id);\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xedzU\x8am \x00\x00m \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE teams RENAME TO sys_team;\nALTER TABLE organisations RENAME TO sys_organisation;\nALTER TABLE team_members RENAME TO sys_team_member;\nALTER TABLE users RENAME TO sys_user;PK\x07\x08\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8# add field to manage user type (bot support)\nALTER TABLE `sys_user` ADD `kind` VARCHAR(8) NOT NULL DEFAULT '' AFTER `handle`;\n\n# add field to manage \"ownership\" (get all bots created by user)\nALTER TABLE `sys_user` ADD `rel_user_id` BIGINT UNSIGNED NOT NULL AFTER `rel_organisation`, ADD INDEX (`rel_user_id`);\nPK\x07\x089\xa0\xdat8\x01\x00\x008\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP INDEX `uid_satosa`, ADD INDEX `uid_satosa` (`satosa_id`) USING BTREE;PK\x07\x08\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE sys_credentials (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n label TEXT NOT NULL COMMENT 'something we can differentiate credentials by',\n kind VARCHAR(128) NOT NULL COMMENT 'hash, facebook, gplus, github, linkedin ...',\n credentials TEXT NOT NULL COMMENT 'crypted/hashed passwords, secrets, social profile ID',\n meta JSON NOT NULL,\n expires_at DATETIME NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX idx_owner ON sys_credentials (rel_owner);\nPK\x07\x08f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` MODIFY `password` TEXT NULL;\nPK\x07\x080V\x13\x0f4\x00\x00\x004\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00 \x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `sys_rules` (\n `rel_team` BIGINT UNSIGNED NOT NULL,\n `resource` VARCHAR(128) NOT NULL,\n `operation` VARCHAR(128) NOT NULL,\n `value` TINYINT(1) NOT NULL,\n\n PRIMARY KEY (`rel_team`, `resource`, `operation`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE sys_team RENAME TO sys_role;\nALTER TABLE sys_team_member RENAME TO sys_role_member;\n\nALTER TABLE `sys_role_member` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nALTER TABLE `sys_rules` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nPK\x07\x08s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,\x00 \x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8REPLACE INTO `sys_role` (`id`, `name`, `handle`) VALUES\n (1, 'Everyone', 'everyone'),\n (2, 'Administrators', 'admins');\n\n-- Value: Allow (2), Deny (1), Inherit(0),\nREPLACE INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n-- Everyone\n (1, 'compose:*', 'access', 2),\n (1, 'messaging:*', 'access', 2),\n-- Admins\n (2, 'compose', 'namespace.create', 2),\n (2, 'compose', 'access', 2),\n (2, 'compose', 'grant', 2),\n (2, 'compose:namespace:*', 'page.create', 2),\n (2, 'compose:namespace:*', 'read', 2),\n (2, 'compose:namespace:*', 'update', 2),\n (2, 'compose:namespace:*', 'delete', 2),\n (2, 'compose:namespace:*', 'module.create', 2),\n (2, 'compose:namespace:*', 'chart.create', 2),\n (2, 'compose:namespace:*', 'trigger.create', 2),\n (2, 'compose:chart:*', 'read', 2),\n (2, 'compose:chart:*', 'update', 2),\n (2, 'compose:chart:*', 'delete', 2),\n (2, 'compose:trigger:*', 'read', 2),\n (2, 'compose:trigger:*', 'update', 2),\n (2, 'compose:trigger:*', 'delete', 2),\n (2, 'compose:page:*', 'read', 2),\n (2, 'compose:page:*', 'update', 2),\n (2, 'compose:page:*', 'delete', 2),\n (2, 'system', 'access', 2),\n (2, 'system', 'grant', 2),\n (2, 'system', 'organisation.create', 2),\n (2, 'system', 'role.create', 2),\n (2, 'system:organisation:*', 'access', 2),\n (2, 'system:role:*', 'read', 2),\n (2, 'system:role:*', 'update', 2),\n (2, 'system:role:*', 'delete', 2),\n (2, 'system:role:*', 'members.manage', 2),\n (2, 'messaging', 'access', 2),\n (2, 'messaging', 'grant', 2),\n (2, 'messaging', 'channel.public.create', 2),\n (2, 'messaging', 'channel.private.create', 2),\n (2, 'messaging', 'channel.group.create', 2),\n (2, 'messaging:channel:*', 'update', 2),\n (2, 'messaging:channel:*', 'leave', 2),\n (2, 'messaging:channel:*', 'read', 2),\n (2, 'messaging:channel:*', 'join', 2),\n (2, 'messaging:channel:*', 'delete', 2),\n (2, 'messaging:channel:*', 'undelete', 2),\n (2, 'messaging:channel:*', 'archive', 2),\n (2, 'messaging:channel:*', 'unarchive', 2),\n (2, 'messaging:channel:*', 'members.manage', 2),\n (2, 'messaging:channel:*', 'webhooks.manage', 2),\n (2, 'messaging:channel:*', 'attachments.manage', 2),\n (2, 'messaging:channel:*', 'message.attach', 2),\n (2, 'messaging:channel:*', 'message.update.all', 2),\n (2, 'messaging:channel:*', 'message.update.own', 2),\n (2, 'messaging:channel:*', 'message.delete.all', 2),\n (2, 'messaging:channel:*', 'message.delete.own', 2),\n (2, 'messaging:channel:*', 'message.embed', 2),\n (2, 'messaging:channel:*', 'message.send', 2),\n (2, 'messaging:channel:*', 'message.reply', 2),\n (2, 'messaging:channel:*', 'message.react', 2),\n (2, 'compose:module:*', 'read', 2),\n (2, 'compose:module:*', 'update', 2),\n (2, 'compose:module:*', 'delete', 2),\n (2, 'compose:module:*', 'record.create', 2),\n (2, 'compose:module:*', 'record.read', 2),\n (2, 'compose:module:*', 'record.update', 2),\n (2, 'compose:module:*', 'record.delete', 2);\nPK\x07\x08z\",\xe8t\x0b\x00\x00t\x0b\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00 \x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE sys_application (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n name TEXT NOT NULL COMMENT 'something we can differentiate application by',\n enabled BOOL NOT NULL,\n\n unify JSON NULL COMMENT 'unify specific settings',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n\nREPLACE INTO `sys_application` (`id`, `name`, `enabled`, `rel_owner`, `unify`) VALUES\n( 1, 'Crust Messaging', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/messaging/\", \"listed\": true}'\n),\n( 2, 'Crust CRM', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/crm/\", \"listed\": true}'\n),\n( 3, 'Crust Admin Area', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/admin/\", \"listed\": true}'\n),\n( 4, 'Corteza Jitsi Bridge', true, 0,\n '{\"logo\": \"/applications/jitsi.png\", \"icon\": \"/applications/jitsi_icon.png\", \"url\": \"/bridge/jitsi/\", \"listed\": true}'\n),\n( 5, 'Google Maps', true, 0,\n '{\"logo\": \"/applications/google_maps.png\", \"icon\": \"/applications/google_maps_icon.png\", \"url\": \"/bridge/google-maps/\", \"listed\": true}'\n);\n\n-- Allow admin access to applications\nINSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n (1, 'system:application:*', 'read', 2),\n (2, 'system', 'application.create', 2),\n (2, 'system:application:*', 'read', 2),\n (2, 'system:application:*', 'update', 2),\n (2, 'system:application:*', 'delete', 2)\n;\nPK\x07\x08\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xedzU\x8am \x00\x00m \x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe \x00\x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9\xa0\xdat8\x01\x00\x008\x01\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd8\n\x00\x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81t\x0c\x00\x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x0d\x00\x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(0V\x13\x0f4\x00\x00\x004\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81+\x11\x00\x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00\x1b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbf\x11\x00\x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x16\x13\x00\x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\",\xe8t\x0b\x00\x00t\x0b\x00\x00,\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x89\x14\x00\x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00\"\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81` \x00\x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xca'\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\x87)\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x0c\x00\x0c\x00&\x04\x00\x00\xf2)\x00\x00\x00\x00" } func init() { diff --git a/system/internal/service/permissions.go b/system/internal/service/permissions.go index 7f043ec56..8edbfe9a2 100644 --- a/system/internal/service/permissions.go +++ b/system/internal/service/permissions.go @@ -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 { diff --git a/system/internal/service/rules.go b/system/internal/service/rules.go index a82738845..b1738d3a0 100644 --- a/system/internal/service/rules.go +++ b/system/internal/service/rules.go @@ -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) } diff --git a/system/internal/service/validation.go b/system/internal/service/validation.go index 421d7516c..9c426472b 100644 --- a/system/internal/service/validation.go +++ b/system/internal/service/validation.go @@ -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) } diff --git a/system/service/rules.go b/system/service/rules.go index d6c488045..fd4d070a6 100644 --- a/system/service/rules.go +++ b/system/service/rules.go @@ -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) } diff --git a/system/types/applications.go b/system/types/applications.go index ea609d7e4..f6c98d4a7 100644 --- a/system/types/applications.go +++ b/system/types/applications.go @@ -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 { diff --git a/system/types/organisation.gen.go b/system/types/organisation.gen.go index 6b47abe1d..c107b3239 100644 --- a/system/types/organisation.gen.go +++ b/system/types/organisation.gen.go @@ -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 -} diff --git a/system/types/organisation.go b/system/types/organisation.go index a3b0b07db..a970d23d7 100644 --- a/system/types/organisation.go +++ b/system/types/organisation.go @@ -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) } diff --git a/system/types/permission_resources.go b/system/types/permission_resources.go new file mode 100644 index 000000000..eda214b38 --- /dev/null +++ b/system/types/permission_resources.go @@ -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:") diff --git a/system/types/role.gen.go b/system/types/role.gen.go index 4e8246712..49fddb17a 100644 --- a/system/types/role.gen.go +++ b/system/types/role.gen.go @@ -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 -} diff --git a/system/types/role.go b/system/types/role.go index aa0fd5f04..1600005da 100644 --- a/system/types/role.go +++ b/system/types/role.go @@ -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) }