Add /permissions/effective endpoint, missing operations
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user