diff --git a/api/crm/spec.json b/api/crm/spec.json index c1ea80e71..8feb758b1 100644 --- a/api/crm/spec.json +++ b/api/crm/spec.json @@ -1,4 +1,60 @@ [ + { + "title": "Permissions", + "parameters": {}, + "entrypoint": "permissions", + "path": "/permissions", + "apis": [ + { + "name": "effective", + "path": "/effective", + "method": "GET", + "title": "Effective rules for current user", + "parameters": { + "get": [ + { + "name": "resource", + "type": "string", + "required": false, + "title": "Show only rules for a specific resource" + } + ] + } + } + ] + }, + { + "title": "Permissions", + "description": "Permssions", + "entrypoint": "page", + "path": "/permissions", + "authentication": [], + "struct": [ + { + "imports": [ + "sqlxTypes github.com/jmoiron/sqlx/types" + ] + } + ], + "apis": [ + { + "name": "list", + "method": "GET", + "path": "/", + "title": "List available pages", + "parameters": { + "get": [ + { + "type": "uint64", + "name": "selfID", + "required": false, + "title": "Parent page ID" + } + ] + } + } + ] + }, { "title": "Pages", "description": "CRM module pages", diff --git a/api/crm/spec/permissions.json b/api/crm/spec/permissions.json new file mode 100644 index 000000000..befdf4e14 --- /dev/null +++ b/api/crm/spec/permissions.json @@ -0,0 +1,27 @@ +{ + "Title": "Permissions", + "Interface": "Permissions", + "Struct": null, + "Parameters": {}, + "Protocol": "", + "Authentication": null, + "Path": "/permissions", + "APIs": [ + { + "Name": "effective", + "Method": "GET", + "Title": "Effective rules for current user", + "Path": "/effective", + "Parameters": { + "get": [ + { + "name": "resource", + "required": false, + "title": "Show only rules for a specific resource", + "type": "string" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/api/messaging/spec.json b/api/messaging/spec.json index 68e46c469..afaee6f84 100644 --- a/api/messaging/spec.json +++ b/api/messaging/spec.json @@ -1,4 +1,28 @@ [ + { + "title": "Permissions", + "parameters": {}, + "entrypoint": "permissions", + "path": "/permissions", + "apis": [ + { + "name": "effective", + "path": "/effective", + "method": "GET", + "title": "Effective rules for current user", + "parameters": { + "get": [ + { + "name": "resource", + "type": "string", + "required": false, + "title": "Show only rules for a specific resource" + } + ] + } + } + ] + }, { "title": "Channels", "description": "A channel is a representation of a sequence of messages. It has meta data like channel subject. Channels may be public, private or group.", diff --git a/api/messaging/spec/permissions.json b/api/messaging/spec/permissions.json new file mode 100644 index 000000000..befdf4e14 --- /dev/null +++ b/api/messaging/spec/permissions.json @@ -0,0 +1,27 @@ +{ + "Title": "Permissions", + "Interface": "Permissions", + "Struct": null, + "Parameters": {}, + "Protocol": "", + "Authentication": null, + "Path": "/permissions", + "APIs": [ + { + "Name": "effective", + "Method": "GET", + "Title": "Effective rules for current user", + "Path": "/effective", + "Parameters": { + "get": [ + { + "name": "resource", + "required": false, + "title": "Show only rules for a specific resource", + "type": "string" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/api/system/spec.json b/api/system/spec.json index 0505e46eb..5b541f09d 100644 --- a/api/system/spec.json +++ b/api/system/spec.json @@ -687,6 +687,22 @@ "title": "Retrieve defined permissions", "parameters": {} }, + { + "name": "effective", + "path": "/effective", + "method": "GET", + "title": "Effective rules for current user", + "parameters": { + "get": [ + { + "name": "resource", + "type": "string", + "required": false, + "title": "Show only rules for a specific resource" + } + ] + } + }, { "name": "read", "path": "/{roleID}/rules", diff --git a/api/system/spec/permissions.json b/api/system/spec/permissions.json index c9cb8a0f0..33516f3dc 100644 --- a/api/system/spec/permissions.json +++ b/api/system/spec/permissions.json @@ -23,6 +23,22 @@ "Path": "/", "Parameters": {} }, + { + "Name": "effective", + "Method": "GET", + "Title": "Effective rules for current user", + "Path": "/effective", + "Parameters": { + "get": [ + { + "name": "resource", + "required": false, + "title": "Show only rules for a specific resource", + "type": "string" + } + ] + } + }, { "Name": "read", "Method": "GET", diff --git a/crm/rest/handlers/permissions.go b/crm/rest/handlers/permissions.go new file mode 100644 index 000000000..7338cab52 --- /dev/null +++ b/crm/rest/handlers/permissions.go @@ -0,0 +1,57 @@ +package handlers + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `permissions.go`, `permissions.util.go` or `permissions_test.go` to + implement your API calls, helper functions and tests. The file `permissions.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "context" + "github.com/go-chi/chi" + "net/http" + + "github.com/titpetric/factory/resputil" + + "github.com/crusttech/crust/crm/rest/request" +) + +// Internal API interface +type PermissionsAPI interface { + Effective(context.Context, *request.PermissionsEffective) (interface{}, error) +} + +// HTTP API interface +type Permissions struct { + Effective func(http.ResponseWriter, *http.Request) +} + +func NewPermissions(ph PermissionsAPI) *Permissions { + return &Permissions{ + Effective: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPermissionsEffective() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.Effective(r.Context(), params) + }) + }, + } +} + +func (ph *Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Route("/permissions", func(r chi.Router) { + r.Get("/effective", ph.Effective) + }) + }) +} diff --git a/crm/rest/middleware.go b/crm/rest/middleware.go index 6f3b1c6fb..204af7f2c 100644 --- a/crm/rest/middleware.go +++ b/crm/rest/middleware.go @@ -8,7 +8,7 @@ import ( func middlewareAllowedAccess(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !service.DefaultPermissions.With(r.Context()).CanAccessCompose() { + if !service.DefaultPermissions.With(r.Context()).CanAccess() { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } diff --git a/crm/rest/permissions.go b/crm/rest/permissions.go new file mode 100644 index 000000000..bcfdbd63e --- /dev/null +++ b/crm/rest/permissions.go @@ -0,0 +1,28 @@ +package rest + +import ( + "context" + + "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/crm/service" + + "github.com/pkg/errors" +) + +var _ = errors.Wrap + +type Permissions struct { + svc struct { + perm service.PermissionsService + } +} + +func (Permissions) New() *Permissions { + ctrl := &Permissions{} + ctrl.svc.perm = service.DefaultPermissions + return ctrl +} + +func (ctrl *Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) { + return ctrl.svc.perm.With(ctx).Effective() +} diff --git a/crm/rest/request/permissions.go b/crm/rest/request/permissions.go new file mode 100644 index 000000000..1d2c9889b --- /dev/null +++ b/crm/rest/request/permissions.go @@ -0,0 +1,76 @@ +package request + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `permissions.go`, `permissions.util.go` or `permissions_test.go` to + implement your API calls, helper functions and tests. The file `permissions.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "encoding/json" + "io" + "mime/multipart" + "net/http" + "strings" + + "github.com/go-chi/chi" + "github.com/pkg/errors" +) + +var _ = chi.URLParam +var _ = multipart.FileHeader{} + +// Permissions effective request parameters +type PermissionsEffective struct { + Resource string +} + +func NewPermissionsEffective() *PermissionsEffective { + return &PermissionsEffective{} +} + +func (pReq *PermissionsEffective) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(pReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := get["resource"]; ok { + + pReq.Resource = val + } + + return err +} + +var _ RequestFiller = NewPermissionsEffective() diff --git a/crm/rest/router.go b/crm/rest/router.go index 0aab2893c..c89ff8c62 100644 --- a/crm/rest/router.go +++ b/crm/rest/router.go @@ -9,6 +9,7 @@ import ( func MountRoutes() func(chi.Router) { var ( + permissions = Permissions{}.New() module = Module{}.New() record = Record{}.New() page = Page{}.New() @@ -27,6 +28,7 @@ func MountRoutes() func(chi.Router) { r.Use(auth.MiddlewareValidOnly) r.Use(middlewareAllowedAccess) + handlers.NewPermissions(permissions).MountRoutes(r) handlers.NewPage(page).MountRoutes(r) handlers.NewModule(module).MountRoutes(r) handlers.NewRecord(record).MountRoutes(r) diff --git a/crm/service/permissions.go b/crm/service/permissions.go index 447899656..ba25c52b9 100644 --- a/crm/service/permissions.go +++ b/crm/service/permissions.go @@ -3,8 +3,6 @@ package service import ( "context" - "github.com/davecgh/go-spew/spew" - "github.com/crusttech/crust/crm/repository" "github.com/crusttech/crust/crm/types" internalRules "github.com/crusttech/crust/internal/rules" @@ -26,7 +24,9 @@ type ( PermissionsService interface { With(context.Context) PermissionsService - CanAccessCompose() bool + Effective() (ee []effectivePermission, err error) + + CanAccess() bool CanCreateNamspace() bool CanCreateModule() bool CanReadModule(r resource) bool @@ -55,6 +55,12 @@ type ( CanDeletePageByID(ID uint64) bool } + effectivePermission struct { + Resource string `json:"resource"` + Operation string `json:"operation"` + Allow bool `json:"allow"` + } + Compose struct{} ) @@ -82,18 +88,43 @@ func (p *permissions) baseResource() resource { return &Compose{} } -func (p *permissions) CanAccessCompose() bool { - spew.Dump(p.baseResource().Resource().String()) +// Return permissions +func (p *permissions) Effective() (ee []effectivePermission, err error) { + ep := func(res, op string, allow bool) effectivePermission { + return effectivePermission{ + Resource: res, + Operation: op, + Allow: allow, + } + } + + ee = append(ee, ep("compose", "access", p.CanAccess())) + 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())) + + return +} + +func (p *permissions) CanAccess() bool { return p.checkAccess(p.baseResource(), "access") } +func (p *permissions) CanGrant() bool { + return p.checkAccess(p.baseResource(), "grant") +} + func (p *permissions) CanCreateNamspace() bool { return p.checkAccess(p.baseResource(), "namespace.create") } func (p *permissions) CanCreateModule() bool { // @todo move to func args when namespaces are implemented - ns := &types.Namespace{ID: "crm"} + ns := &types.Namespace{ID: types.NamespaceCRM} return p.checkAccess(ns, "module.create") } @@ -135,7 +166,7 @@ func (p *permissions) CanDeleteRecordByModuleID(moduleID uint64) bool { func (p *permissions) CanCreateChart() bool { // @todo move to func args when namespaces are implemented - ns := &types.Namespace{ID: "crm"} + ns := &types.Namespace{ID: types.NamespaceCRM} return p.checkAccess(ns, "chart.create") } @@ -157,7 +188,7 @@ func (p *permissions) CanDeleteChartByID(ID uint64) bool { func (p *permissions) CanCreateTrigger() bool { // @todo move to func args when namespaces are implemented - ns := &types.Namespace{ID: "crm"} + ns := &types.Namespace{ID: types.NamespaceCRM} return p.checkAccess(ns, "trigger.create") } @@ -179,7 +210,7 @@ func (p *permissions) CanDeleteTriggerByID(ID uint64) bool { func (p *permissions) CanCreatePage() bool { // @todo move to func args when namespaces are implemented - ns := &types.Namespace{ID: "crm"} + ns := &types.Namespace{ID: types.NamespaceCRM} return p.checkAccess(ns, "page.create") } diff --git a/crm/service/permissions_test.go b/crm/service/permissions_test.go index a439972bf..5a3c1723a 100644 --- a/crm/service/permissions_test.go +++ b/crm/service/permissions_test.go @@ -60,8 +60,8 @@ func TestPermissions(t *testing.T) { systemRulesSvc := systemService.Rules().With(ctx) // Test `access` to compose service. - ret := permissionsSvc.CanAccessCompose() - Assert(t, ret == true, "expected CanAccessCompose == true, got %v", ret) + ret := permissionsSvc.CanAccess() + Assert(t, ret == true, "expected CanAccess == true, got %v", ret) // Add `access` to compose service. list := []rules.Rule{ @@ -71,6 +71,6 @@ func TestPermissions(t *testing.T) { NoError(t, err, "expected no error, got %v", err) // Test `access` to compose service. - ret = permissionsSvc.CanAccessCompose() - Assert(t, ret == true, "expected CanAccessCompose == true, got %v", ret) + ret = permissionsSvc.CanAccess() + Assert(t, ret == true, "expected CanAccess == true, got %v", ret) } diff --git a/crm/types/namespace.go b/crm/types/namespace.go index a9b7f674c..3231c958b 100644 --- a/crm/types/namespace.go +++ b/crm/types/namespace.go @@ -6,16 +6,20 @@ import ( type ( Namespace struct { - ID string `json:"id,string" db:"id"` + ID uint64 `json:"id,string" db:"id"` } ) +const ( + NamespaceCRM uint64 = 10000000 +) + // Resource returns a system resource ID for this type func (r *Namespace) Resource() rules.Resource { resource := rules.Resource{ Service: "compose", - // Hardcoded single namespace (CRM) for now - Scope: "namespace:crm", + Scope: "namespace", + ID: r.ID, } return resource diff --git a/docs/crm/README.md b/docs/crm/README.md index 9e05db124..a7a5db908 100644 --- a/docs/crm/README.md +++ b/docs/crm/README.md @@ -386,6 +386,27 @@ CRM module pages +# Permissions + +## Effective rules for current user + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/effective` | HTTP/S | GET | +Warning: implode(): Invalid arguments passed in /private/tmp/Users/darh/Work/go/src/github.com/crusttech/crust/codegen/templates/README.tpl on line 22 + | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| resource | string | GET | Show only rules for a specific resource | N/A | NO | + + + + # Records CRM records diff --git a/docs/messaging/README.md b/docs/messaging/README.md index 521347407..c94bcbc0f 100644 --- a/docs/messaging/README.md +++ b/docs/messaging/README.md @@ -448,6 +448,27 @@ The following event types may be sent with a message event: +# Permissions + +## Effective rules for current user + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/effective` | HTTP/S | GET | +Warning: implode(): Invalid arguments passed in /private/tmp/Users/darh/Work/go/src/github.com/crusttech/crust/codegen/templates/README.tpl on line 22 + | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| resource | string | GET | Show only rules for a specific resource | N/A | NO | + + + + # Search entry point ## Search for messages diff --git a/docs/system/README.md b/docs/system/README.md index 15a0492df..a37815531 100644 --- a/docs/system/README.md +++ b/docs/system/README.md @@ -232,6 +232,20 @@ Organisations represent a top-level grouping entity. There may be many organisat | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | +## Effective rules for current user + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/effective` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| resource | string | GET | Show only rules for a specific resource | N/A | NO | + ## Retrieve role permissions #### Method diff --git a/internal/subscription/check.go b/internal/subscription/check.go index bee03b5c3..290e19e13 100644 --- a/internal/subscription/check.go +++ b/internal/subscription/check.go @@ -68,11 +68,11 @@ func Check(ctx context.Context) (err error) { } if err != nil { - log.Printf("unable to check for licence: %v, try %d/%d", err, try, checkMaxTries) + log.Printf("unable to check for subscription: %v, try %d/%d", err, try, checkMaxTries) } if try >= checkMaxTries { - return errors.Wrap(err, "unable to check for licence") + return errors.Wrap(err, "unable to check for subscription") } time.Sleep(time.Second * checkTryDelay * checkTryDelay) diff --git a/messaging/rest/handlers/permissions.go b/messaging/rest/handlers/permissions.go new file mode 100644 index 000000000..2f258e641 --- /dev/null +++ b/messaging/rest/handlers/permissions.go @@ -0,0 +1,57 @@ +package handlers + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `permissions.go`, `permissions.util.go` or `permissions_test.go` to + implement your API calls, helper functions and tests. The file `permissions.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "context" + "github.com/go-chi/chi" + "net/http" + + "github.com/titpetric/factory/resputil" + + "github.com/crusttech/crust/messaging/rest/request" +) + +// Internal API interface +type PermissionsAPI interface { + Effective(context.Context, *request.PermissionsEffective) (interface{}, error) +} + +// HTTP API interface +type Permissions struct { + Effective func(http.ResponseWriter, *http.Request) +} + +func NewPermissions(ph PermissionsAPI) *Permissions { + return &Permissions{ + Effective: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPermissionsEffective() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.Effective(r.Context(), params) + }) + }, + } +} + +func (ph *Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Route("/permissions", func(r chi.Router) { + r.Get("/effective", ph.Effective) + }) + }) +} diff --git a/messaging/rest/middleware.go b/messaging/rest/middleware.go index fab9c08cc..db5ec9576 100644 --- a/messaging/rest/middleware.go +++ b/messaging/rest/middleware.go @@ -8,7 +8,7 @@ import ( func middlewareAllowedAccess(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !service.DefaultPermissions.With(r.Context()).CanAccessMessaging() { + if !service.DefaultPermissions.With(r.Context()).CanAccess() { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } diff --git a/messaging/rest/permissions.go b/messaging/rest/permissions.go new file mode 100644 index 000000000..d2c04b822 --- /dev/null +++ b/messaging/rest/permissions.go @@ -0,0 +1,27 @@ +package rest + +import ( + "context" + + "github.com/crusttech/crust/messaging/rest/request" + "github.com/crusttech/crust/messaging/service" + "github.com/pkg/errors" +) + +var _ = errors.Wrap + +type Permissions struct { + svc struct { + perm service.PermissionsService + } +} + +func (Permissions) New() *Permissions { + ctrl := &Permissions{} + ctrl.svc.perm = service.DefaultPermissions + return ctrl +} + +func (ctrl *Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) { + return ctrl.svc.perm.With(ctx).Effective() +} diff --git a/messaging/rest/request/permissions.go b/messaging/rest/request/permissions.go new file mode 100644 index 000000000..1d2c9889b --- /dev/null +++ b/messaging/rest/request/permissions.go @@ -0,0 +1,76 @@ +package request + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `permissions.go`, `permissions.util.go` or `permissions_test.go` to + implement your API calls, helper functions and tests. The file `permissions.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "encoding/json" + "io" + "mime/multipart" + "net/http" + "strings" + + "github.com/go-chi/chi" + "github.com/pkg/errors" +) + +var _ = chi.URLParam +var _ = multipart.FileHeader{} + +// Permissions effective request parameters +type PermissionsEffective struct { + Resource string +} + +func NewPermissionsEffective() *PermissionsEffective { + return &PermissionsEffective{} +} + +func (pReq *PermissionsEffective) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(pReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := get["resource"]; ok { + + pReq.Resource = val + } + + return err +} + +var _ RequestFiller = NewPermissionsEffective() diff --git a/messaging/rest/router.go b/messaging/rest/router.go index 1cea96d27..664dfb88d 100644 --- a/messaging/rest/router.go +++ b/messaging/rest/router.go @@ -20,6 +20,7 @@ func MountRoutes() func(chi.Router) { r.Use(auth.MiddlewareValidOnly) r.Use(middlewareAllowedAccess) + handlers.NewPermissions(Permissions{}.New()).MountRoutes(r) handlers.NewChannel(Channel{}.New()).MountRoutes(r) handlers.NewMessage(Message{}.New()).MountRoutes(r) handlers.NewSearch(Search{}.New()).MountRoutes(r) diff --git a/messaging/service/permissions.go b/messaging/service/permissions.go index 85008c19d..2849f1b29 100644 --- a/messaging/service/permissions.go +++ b/messaging/service/permissions.go @@ -21,8 +21,10 @@ type ( PermissionsService interface { With(context.Context) PermissionsService - CanAccessMessaging() bool - CanGrantMessaging() bool + Effective() (ee []effectivePermission, err error) + + CanAccess() bool + CanGrant() bool CanCreatePublicChannel() bool CanCreatePrivateChannel() bool CanCreateGroupChannel() bool @@ -51,6 +53,12 @@ type ( CanDeleteMessages(ch *types.Channel) bool CanReactMessage(ch *types.Channel) bool } + + effectivePermission struct { + Resource string `json:"resource"` + Operation string `json:"operation"` + Allow bool `json:"allow"` + } ) func Permissions() PermissionsService { @@ -69,11 +77,29 @@ func (p *permissions) With(ctx context.Context) PermissionsService { } } -func (p *permissions) CanAccessMessaging() bool { +func (p *permissions) Effective() (ee []effectivePermission, err error) { + ep := func(res, op string, allow bool) effectivePermission { + return effectivePermission{ + Resource: res, + Operation: op, + Allow: allow, + } + } + + 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())) + + return +} + +func (p *permissions) CanAccess() bool { return p.checkAccess("messaging", "access") } -func (p *permissions) CanGrantMessaging() bool { +func (p *permissions) CanGrant() bool { return p.checkAccess("messaging", "grant") } diff --git a/messaging/service/permissions_test.go b/messaging/service/permissions_test.go index 1f51d65f1..0bab81d34 100644 --- a/messaging/service/permissions_test.go +++ b/messaging/service/permissions_test.go @@ -67,8 +67,8 @@ func TestPermissions(t *testing.T) { NoError(t, err, "expected no error, got %v", err) // Test `access` to messaging service. - ret := permissionsSvc.CanAccessMessaging() - Assert(t, ret == false, "expected CanAccessMessaging == false, got %v", ret) + ret := permissionsSvc.CanAccess() + Assert(t, ret == false, "expected CanAccess == false, got %v", ret) } // Add `access` to messaging service. @@ -80,8 +80,8 @@ func TestPermissions(t *testing.T) { NoError(t, err, "expected no error, got %v", err) // Test `access` to messaging service. - ret := permissionsSvc.CanAccessMessaging() - Assert(t, ret == true, "expected CanAccessMessaging == true, got %v", ret) + ret := permissionsSvc.CanAccess() + Assert(t, ret == true, "expected CanAccess == true, got %v", ret) } // Create test channel. diff --git a/messaging/websocket/router.go b/messaging/websocket/router.go index 0295324c8..c96c68248 100644 --- a/messaging/websocket/router.go +++ b/messaging/websocket/router.go @@ -36,7 +36,7 @@ func MountRoutes(ctx context.Context, config *repository.Flags) func(chi.Router) func middlewareAllowedAccess(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !service.DefaultPermissions.With(r.Context()).CanAccessMessaging() { + if !service.DefaultPermissions.With(r.Context()).CanAccess() { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return } diff --git a/system/db/mysql/statik.go b/system/db/mysql/statik.go index 859385a67..3120ef3f1 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\x80Cm8INSERT INTO `sys_role` (`id`, `name`, `handle`) VALUES (1, 'Everyone', 'everyone');\nINSERT INTO `sys_role` (`id`, `name`, `handle`) VALUES (2, 'Administrators', 'admins');\n\n-- Value: Allow (2), Deny (1), Inherit(0),\nINSERT 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:*', '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.direct.create', 2),\n (2, 'messaging:channel:*', 'update', 2),\n (2, 'messaging:channel:*', 'message.attach', 2),\n (2, 'messaging:channel:*', 'message.update.all', 2),\n (2, 'messaging:channel:*', 'leave', 2),\n (2, 'messaging:channel:*', 'webhooks.manage', 2),\n (2, 'messaging:channel:*', 'message.embed', 2),\n (2, 'messaging:channel:*', 'members.manage', 2),\n (2, 'messaging:channel:*', 'attachments.manage', 2),\n (2, 'messaging:channel:*', 'message.send', 2),\n (2, 'messaging:channel:*', 'message.reply', 2),\n (2, 'messaging:channel:*', 'read', 2),\n (2, 'messaging:channel:*', 'join', 2),\n (2, 'messaging:channel:*', 'message.update.own', 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\xe3U\xd4\x88x\n\x00\x00x\n\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!(\xe3U\xd4\x88x\n\x00\x00x\n\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\x81d\x1f\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\xce&\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\x8b(\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x0c\x00\x0c\x00&\x04\x00\x00\xf6(\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\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" } func init() { diff --git a/system/db/schema/mysql/20190226160000.system_roles_and_rules.up.sql b/system/db/schema/mysql/20190226160000.system_roles_and_rules.up.sql index 1bff8b499..b3a90db46 100644 --- a/system/db/schema/mysql/20190226160000.system_roles_and_rules.up.sql +++ b/system/db/schema/mysql/20190226160000.system_roles_and_rules.up.sql @@ -1,8 +1,9 @@ -INSERT INTO `sys_role` (`id`, `name`, `handle`) VALUES (1, 'Everyone', 'everyone'); -INSERT INTO `sys_role` (`id`, `name`, `handle`) VALUES (2, 'Administrators', 'admins'); +REPLACE INTO `sys_role` (`id`, `name`, `handle`) VALUES + (1, 'Everyone', 'everyone'), + (2, 'Administrators', 'admins'); -- Value: Allow (2), Deny (1), Inherit(0), -INSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES +REPLACE INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES -- Everyone (1, 'compose:*', 'access', 2), (1, 'messaging:*', 'access', 2), @@ -26,6 +27,7 @@ INSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES (2, 'compose:page:*', 'read', 2), (2, 'compose:page:*', 'update', 2), (2, 'compose:page:*', 'delete', 2), + (2, 'system:*', 'access', 2), (2, 'system:*', 'grant', 2), (2, 'system:*', 'organisation.create', 2), (2, 'system:*', 'role.create', 2), @@ -38,20 +40,26 @@ INSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES (2, 'messaging:*', 'grant', 2), (2, 'messaging:*', 'channel.public.create', 2), (2, 'messaging:*', 'channel.private.create', 2), - (2, 'messaging:*', 'channel.direct.create', 2), + (2, 'messaging:*', 'channel.group.create', 2), (2, 'messaging:channel:*', 'update', 2), - (2, 'messaging:channel:*', 'message.attach', 2), - (2, 'messaging:channel:*', 'message.update.all', 2), (2, 'messaging:channel:*', 'leave', 2), - (2, 'messaging:channel:*', 'webhooks.manage', 2), - (2, 'messaging:channel:*', 'message.embed', 2), - (2, 'messaging:channel:*', 'members.manage', 2), - (2, 'messaging:channel:*', 'attachments.manage', 2), - (2, 'messaging:channel:*', 'message.send', 2), - (2, 'messaging:channel:*', 'message.reply', 2), (2, 'messaging:channel:*', 'read', 2), (2, 'messaging:channel:*', 'join', 2), + (2, 'messaging:channel:*', 'delete', 2), + (2, 'messaging:channel:*', 'undelete', 2), + (2, 'messaging:channel:*', 'archive', 2), + (2, 'messaging:channel:*', 'unarchive', 2), + (2, 'messaging:channel:*', 'members.manage', 2), + (2, 'messaging:channel:*', 'webhooks.manage', 2), + (2, 'messaging:channel:*', 'attachments.manage', 2), + (2, 'messaging:channel:*', 'message.attach', 2), + (2, 'messaging:channel:*', 'message.update.all', 2), (2, 'messaging:channel:*', 'message.update.own', 2), + (2, 'messaging:channel:*', 'message.delete.all', 2), + (2, 'messaging:channel:*', 'message.delete.own', 2), + (2, 'messaging:channel:*', 'message.embed', 2), + (2, 'messaging:channel:*', 'message.send', 2), + (2, 'messaging:channel:*', 'message.reply', 2), (2, 'messaging:channel:*', 'message.react', 2), (2, 'compose:module:*', 'read', 2), (2, 'compose:module:*', 'update', 2), diff --git a/system/rest/handlers/permissions.go b/system/rest/handlers/permissions.go index 3f6415c39..7996e53d2 100644 --- a/system/rest/handlers/permissions.go +++ b/system/rest/handlers/permissions.go @@ -28,6 +28,7 @@ import ( // Internal API interface type PermissionsAPI interface { List(context.Context, *request.PermissionsList) (interface{}, error) + Effective(context.Context, *request.PermissionsEffective) (interface{}, error) Read(context.Context, *request.PermissionsRead) (interface{}, error) Delete(context.Context, *request.PermissionsDelete) (interface{}, error) Update(context.Context, *request.PermissionsUpdate) (interface{}, error) @@ -35,10 +36,11 @@ type PermissionsAPI interface { // HTTP API interface type Permissions struct { - List func(http.ResponseWriter, *http.Request) - Read func(http.ResponseWriter, *http.Request) - Delete func(http.ResponseWriter, *http.Request) - Update func(http.ResponseWriter, *http.Request) + List func(http.ResponseWriter, *http.Request) + Effective func(http.ResponseWriter, *http.Request) + Read func(http.ResponseWriter, *http.Request) + Delete func(http.ResponseWriter, *http.Request) + Update func(http.ResponseWriter, *http.Request) } func NewPermissions(ph PermissionsAPI) *Permissions { @@ -50,6 +52,13 @@ func NewPermissions(ph PermissionsAPI) *Permissions { return ph.List(r.Context(), params) }) }, + Effective: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPermissionsEffective() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.Effective(r.Context(), params) + }) + }, Read: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() params := request.NewPermissionsRead() @@ -79,6 +88,7 @@ func (ph *Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handle r.Use(middlewares...) r.Route("/permissions", func(r chi.Router) { r.Get("/", ph.List) + r.Get("/effective", ph.Effective) r.Get("/{roleID}/rules", ph.Read) r.Delete("/{roleID}/rules", ph.Delete) r.Patch("/{roleID}/rules", ph.Update) diff --git a/system/rest/permissions.go b/system/rest/permissions.go index 28b0963e1..ccbe35f62 100644 --- a/system/rest/permissions.go +++ b/system/rest/permissions.go @@ -15,6 +15,7 @@ type ( Permissions struct { svc struct { rules service.RulesService + perm service.PermissionsService } } ) @@ -22,6 +23,7 @@ type ( func (Permissions) New() *Permissions { ctrl := &Permissions{} ctrl.svc.rules = service.DefaultRules + ctrl.svc.perm = service.DefaultPermissions return ctrl } @@ -29,6 +31,10 @@ func (ctrl *Permissions) List(ctx context.Context, r *request.PermissionsList) ( return ctrl.svc.rules.With(ctx).List() } +func (ctrl *Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) { + return ctrl.svc.perm.With(ctx).Effective() +} + func (ctrl *Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) { return ctrl.svc.rules.With(ctx).Read(r.RoleID) } diff --git a/system/rest/request/permissions.go b/system/rest/request/permissions.go index 5fcc25685..7016deb9a 100644 --- a/system/rest/request/permissions.go +++ b/system/rest/request/permissions.go @@ -71,6 +71,52 @@ func (peReq *PermissionsList) Fill(r *http.Request) (err error) { var _ RequestFiller = NewPermissionsList() +// Permissions effective request parameters +type PermissionsEffective struct { + Resource string +} + +func NewPermissionsEffective() *PermissionsEffective { + return &PermissionsEffective{} +} + +func (peReq *PermissionsEffective) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(peReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := get["resource"]; ok { + + peReq.Resource = val + } + + return err +} + +var _ RequestFiller = NewPermissionsEffective() + // Permissions read request parameters type PermissionsRead struct { RoleID uint64 `json:",string"` diff --git a/system/service/permissions.go b/system/service/permissions.go index 9df842642..fee470e1f 100644 --- a/system/service/permissions.go +++ b/system/service/permissions.go @@ -19,6 +19,8 @@ type ( PermissionsService interface { With(context.Context) PermissionsService + Effective() (ee []effectivePermission, err error) + CanCreateOrganisation() bool CanCreateRole() bool CanCreateApplication() bool @@ -32,6 +34,12 @@ type ( CanUpdateApplication(app *types.Application) bool CanDeleteApplication(app *types.Application) bool } + + effectivePermission struct { + Resource string `json:"resource"` + Operation string `json:"operation"` + Allow bool `json:"allow"` + } ) func Permissions() PermissionsService { @@ -50,8 +58,30 @@ func (p *permissions) With(ctx context.Context) PermissionsService { } } +func (p *permissions) Effective() (ee []effectivePermission, err error) { + ep := func(res, op string, allow bool) effectivePermission { + return effectivePermission{ + Resource: res, + Operation: op, + Allow: allow, + } + } + + ee = append(ee, ep("system", "access", p.CanAccess())) + ee = append(ee, ep("system", "application.create", p.CanCreateApplication())) + ee = append(ee, ep("system", "role.create", p.CanCreateRole())) + ee = append(ee, ep("system", "organisation.create", p.CanCreateOrganisation())) + ee = append(ee, ep("system", "grant", p.CanCreateRole())) + + return +} + +func (p *permissions) CanAccess() bool { + return p.checkAccess("system", "access") +} + func (p *permissions) CanCreateOrganisation() bool { - return p.checkAccess("system", "application.create") + return p.checkAccess("system", "organisation.create") } func (p *permissions) CanCreateRole() bool { diff --git a/system/service/rules.go b/system/service/rules.go index 6697633ae..e457ea541 100644 --- a/system/service/rules.go +++ b/system/service/rules.go @@ -23,10 +23,17 @@ type ( resources internalRules.ResourcesInterface } + effectiveRules struct { + Resource string `json:"resource"` + Operation string `json:"operation"` + Allow bool `json:"allow"` + } + RulesService interface { With(ctx context.Context) RulesService List() (interface{}, error) + Effective(filter string) ([]effectiveRules, error) Check(resource string, operation string, fallbacks ...internalRules.CheckAccessFunc) internalRules.Access @@ -63,6 +70,23 @@ func (p *rules) List() (interface{}, error) { return perms, nil } +func (p *rules) Effective(filter string) (eff []effectiveRules, err error) { + eff = []effectiveRules{} + for resource, operations := range permissionList { + // err := p.checkServiceAccess(resource) + if err == nil { + for ops := range operations { + eff = append(eff, effectiveRules{ + Resource: resource, + Operation: ops, + Allow: false, + }) + } + } + } + return +} + func (p *rules) Check(resource string, operation string, fallbacks ...internalRules.CheckAccessFunc) internalRules.Access { return p.resources.Check(resource, operation, fallbacks...) } diff --git a/system/service/validation.go b/system/service/validation.go index e0731174d..421d7516c 100644 --- a/system/service/validation.go +++ b/system/service/validation.go @@ -9,6 +9,7 @@ import ( var ( permissionList = map[string]map[string]bool{ "system": map[string]bool{ + "access": true, "grant": true, "organisation.create": true, "role.create": true,