3
0

Add /permissions/effective endpoint, missing operations

This commit is contained in:
Denis Arh
2019-03-12 17:51:18 +01:00
parent 702a3a3372
commit e352f065c3
34 changed files with 779 additions and 47 deletions
+56
View File
@@ -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",
+27
View File
@@ -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"
}
]
}
}
]
}
+24
View File
@@ -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.",
+27
View File
@@ -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"
}
]
}
}
]
}
+16
View File
@@ -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",
+16
View File
@@ -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",
+57
View File
@@ -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)
})
})
}
+1 -1
View File
@@ -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
}
+28
View File
@@ -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()
}
+76
View File
@@ -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()
+2
View File
@@ -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)
+40 -9
View File
@@ -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")
}
+4 -4
View File
@@ -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)
}
+7 -3
View File
@@ -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
+21
View File
@@ -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
+21
View File
@@ -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
+14
View File
@@ -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
+2 -2
View File
@@ -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)
+57
View File
@@ -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)
})
})
}
+1 -1
View File
@@ -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
}
+27
View File
@@ -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()
}
+76
View File
@@ -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()
+1
View File
@@ -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)
+30 -4
View File
@@ -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")
}
+4 -4
View File
@@ -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.
+1 -1
View File
@@ -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
}
File diff suppressed because one or more lines are too long
@@ -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),
+14 -4
View File
@@ -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)
+6
View File
@@ -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)
}
+46
View File
@@ -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"`
+31 -1
View File
@@ -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 {
+24
View File
@@ -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...)
}
+1
View File
@@ -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,