diff --git a/api/sam/spec.json b/api/sam/spec.json index 2951c51d6..8cd604b4c 100644 --- a/api/sam/spec.json +++ b/api/sam/spec.json @@ -634,5 +634,78 @@ } } ] + }, + { + "title": "Permissions", + "package": "sam", + "parameters": {}, + "entrypoint": "permissions", + "authentication": [ + "Client ID", + "Session ID" + ], + "apis": [ + { + "name": "list", + "path": "/permissions", + "method": "GET", + "title": "List default permissions", + "parameters": {} + }, + { + "name": "getTeam", + "path": "/permissions/{team}", + "method": "GET", + "title": "Retrieve current permission settings", + "parameters": { + "path": [ + { + "name": "team", + "type": "string", + "required": true, + "title": "Team ID" + } + ], + "get": [ + { + "name": "scope", + "type": "string", + "required": true, + "title": "Permissions scope" + }, + { + "name": "resource", + "type": "string", + "required": true, + "title": "Permissions resource" + } + ] + } + }, + { + "name": "setTeam", + "path": "/permissions/{team}", + "method": "POST", + "title": "Update permission settings", + "parameters": { + "path": [ + { + "name": "team", + "type": "string", + "required": true, + "title": "Team ID" + } + ], + "post": [ + { + "name": "permissions", + "type": "[]rbac.Permission", + "required": true, + "title": "List of permissions to set" + } + ] + } + } + ] } ] diff --git a/api/sam/spec/permissions.json b/api/sam/spec/permissions.json new file mode 100644 index 000000000..6f3a42cca --- /dev/null +++ b/api/sam/spec/permissions.json @@ -0,0 +1,76 @@ +{ + "Title": "Permissions", + "Package": "sam", + "Interface": "Permissions", + "Struct": null, + "Parameters": {}, + "Protocol": "", + "Authentication": [ + "Client ID", + "Session ID" + ], + "Path": "/permissions", + "APIs": [ + { + "Name": "list", + "Method": "GET", + "Title": "List default permissions", + "Path": "/permissions", + "Parameters": {} + }, + { + "Name": "getTeam", + "Method": "GET", + "Title": "Retrieve current permission settings", + "Path": "/permissions/{team}", + "Parameters": { + "get": [ + { + "name": "scope", + "required": true, + "title": "Permissions scope", + "type": "string" + }, + { + "name": "resource", + "required": true, + "title": "Permissions resource", + "type": "string" + } + ], + "path": [ + { + "name": "team", + "required": true, + "title": "Team ID", + "type": "string" + } + ] + } + }, + { + "Name": "setTeam", + "Method": "POST", + "Title": "Update permission settings", + "Path": "/permissions/{team}", + "Parameters": { + "path": [ + { + "name": "team", + "required": true, + "title": "Team ID", + "type": "string" + } + ], + "post": [ + { + "name": "permissions", + "required": true, + "title": "List of permissions to set", + "type": "[]rbac.Permission" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/sam/README.md b/docs/sam/README.md index 5338691b4..0536a89d9 100644 --- a/docs/sam/README.md +++ b/docs/sam/README.md @@ -418,6 +418,55 @@ The following event types may be sent with a message event: +# Permissions + +## List default permissions + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/permissions` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + +## Retrieve current permission settings + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/permissions/{team}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| scope | string | GET | Permissions scope | N/A | YES | +| resource | string | GET | Permissions resource | N/A | YES | +| team | string | PATH | Team ID | N/A | YES | + +## Update permission settings + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/permissions/{team}` | HTTP/S | POST | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| team | string | PATH | Team ID | N/A | YES | +| permissions | []rbac.Permission | POST | List of permissions to set | N/A | YES | + + + + # Search entry point ## Search for messages diff --git a/sam/rest/handlers/permissions.go b/sam/rest/handlers/permissions.go new file mode 100644 index 000000000..f714bb8fd --- /dev/null +++ b/sam/rest/handlers/permissions.go @@ -0,0 +1,77 @@ +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/sam/rest/request" +) + +// Internal API interface +type PermissionsAPI interface { + List(context.Context, *request.PermissionsList) (interface{}, error) + GetTeam(context.Context, *request.PermissionsGetTeam) (interface{}, error) + SetTeam(context.Context, *request.PermissionsSetTeam) (interface{}, error) +} + +// HTTP API interface +type Permissions struct { + List func(http.ResponseWriter, *http.Request) + GetTeam func(http.ResponseWriter, *http.Request) + SetTeam func(http.ResponseWriter, *http.Request) +} + +func NewPermissions(ph PermissionsAPI) *Permissions { + return &Permissions{ + List: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPermissionsList() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.List(r.Context(), params) + }) + }, + GetTeam: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPermissionsGetTeam() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.GetTeam(r.Context(), params) + }) + }, + SetTeam: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPermissionsSetTeam() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return ph.SetTeam(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("/permissions", ph.List) + r.Get("/permissions/{team}", ph.GetTeam) + r.Post("/permissions/{team}", ph.SetTeam) + }) + }) +} diff --git a/sam/rest/permissions.go b/sam/rest/permissions.go new file mode 100644 index 000000000..ad2b007b4 --- /dev/null +++ b/sam/rest/permissions.go @@ -0,0 +1,33 @@ +package rest + +import ( + "context" + + "github.com/crusttech/crust/sam/rest/request" + "github.com/crusttech/crust/sam/service" + _ "github.com/crusttech/crust/sam/types" +) + +type Permissions struct { + svc struct { + perms service.PermissionsService + } +} + +func (Permissions) New() *Permissions { + ctrl := &Permissions{} + ctrl.svc.perms = service.DefaultPermissions + return ctrl +} + +func (ctrl *Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) { + return ctrl.svc.perms.List() +} + +func (ctrl *Permissions) GetTeam(ctx context.Context, r *request.PermissionsGetTeam) (interface{}, error) { + return ctrl.svc.perms.Get(r.Team, r.Scope, r.Resource) +} + +func (ctrl *Permissions) SetTeam(ctx context.Context, r *request.PermissionsSetTeam) (interface{}, error) { + return ctrl.svc.perms.Set(r.Team, r.Permissions) +} diff --git a/sam/rest/request/attachment.go b/sam/rest/request/attachment.go index 337f815d0..cf4acb686 100644 --- a/sam/rest/request/attachment.go +++ b/sam/rest/request/attachment.go @@ -17,6 +17,7 @@ package request import ( "encoding/json" + "github.com/crusttech/crust/internal/rbac" "github.com/go-chi/chi" "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" @@ -29,6 +30,7 @@ import ( var _ = chi.URLParam var _ = types.JSONText{} var _ = multipart.FileHeader{} +var _ = rbac.Operation{} // Attachment original request parameters type AttachmentOriginal struct { diff --git a/sam/rest/request/channel.go b/sam/rest/request/channel.go index b068043da..4825225e6 100644 --- a/sam/rest/request/channel.go +++ b/sam/rest/request/channel.go @@ -17,6 +17,7 @@ package request import ( "encoding/json" + "github.com/crusttech/crust/internal/rbac" "github.com/go-chi/chi" "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" @@ -29,6 +30,7 @@ import ( var _ = chi.URLParam var _ = types.JSONText{} var _ = multipart.FileHeader{} +var _ = rbac.Operation{} // Channel list request parameters type ChannelList struct { diff --git a/sam/rest/request/message.go b/sam/rest/request/message.go index 65716221e..9ca4c4398 100644 --- a/sam/rest/request/message.go +++ b/sam/rest/request/message.go @@ -17,6 +17,7 @@ package request import ( "encoding/json" + "github.com/crusttech/crust/internal/rbac" "github.com/go-chi/chi" "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" @@ -29,6 +30,7 @@ import ( var _ = chi.URLParam var _ = types.JSONText{} var _ = multipart.FileHeader{} +var _ = rbac.Operation{} // Message create request parameters type MessageCreate struct { diff --git a/sam/rest/request/permissions.go b/sam/rest/request/permissions.go new file mode 100644 index 000000000..bd5f7386f --- /dev/null +++ b/sam/rest/request/permissions.go @@ -0,0 +1,170 @@ +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" + "github.com/crusttech/crust/internal/rbac" + "github.com/go-chi/chi" + "github.com/jmoiron/sqlx/types" + "github.com/pkg/errors" + "io" + "mime/multipart" + "net/http" + "strings" +) + +var _ = chi.URLParam +var _ = types.JSONText{} +var _ = multipart.FileHeader{} +var _ = rbac.Operation{} + +// Permissions list request parameters +type PermissionsList struct { +} + +func NewPermissionsList() *PermissionsList { + return &PermissionsList{} +} + +func (p *PermissionsList) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(p) + + 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]) + } + + return err +} + +var _ RequestFiller = NewPermissionsList() + +// Permissions getTeam request parameters +type PermissionsGetTeam struct { + Scope string + Resource string + Team string +} + +func NewPermissionsGetTeam() *PermissionsGetTeam { + return &PermissionsGetTeam{} +} + +func (p *PermissionsGetTeam) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(p) + + 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["scope"]; ok { + + p.Scope = val + } + if val, ok := get["resource"]; ok { + + p.Resource = val + } + p.Team = chi.URLParam(r, "team") + + return err +} + +var _ RequestFiller = NewPermissionsGetTeam() + +// Permissions setTeam request parameters +type PermissionsSetTeam struct { + Team string + Permissions []rbac.Permission +} + +func NewPermissionsSetTeam() *PermissionsSetTeam { + return &PermissionsSetTeam{} +} + +func (p *PermissionsSetTeam) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(p) + + 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]) + } + + p.Team = chi.URLParam(r, "team") + + return err +} + +var _ RequestFiller = NewPermissionsSetTeam() diff --git a/sam/rest/request/search.go b/sam/rest/request/search.go index cedd8f159..536a5a221 100644 --- a/sam/rest/request/search.go +++ b/sam/rest/request/search.go @@ -17,6 +17,7 @@ package request import ( "encoding/json" + "github.com/crusttech/crust/internal/rbac" "github.com/go-chi/chi" "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" @@ -29,6 +30,7 @@ import ( var _ = chi.URLParam var _ = types.JSONText{} var _ = multipart.FileHeader{} +var _ = rbac.Operation{} // Search messages request parameters type SearchMessages struct { diff --git a/sam/service/permissions.go b/sam/service/permissions.go new file mode 100644 index 000000000..d8910c4d5 --- /dev/null +++ b/sam/service/permissions.go @@ -0,0 +1,49 @@ +package service + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/crusttech/crust/internal/rbac" + "github.com/crusttech/crust/sam/repository" +) + +type ( + permissions struct { + db db + ctx context.Context + } + + PermissionsService interface { + With(ctx context.Context) PermissionsService + + List() (interface{}, error) + Get(team string, scope string, resource string) (interface{}, error) + Set(team string, permissions []rbac.Permission) (interface{}, error) + } +) + +func Permissions() PermissionsService { + return (&permissions{}).With(context.Background()) +} + +func (svc *permissions) With(ctx context.Context) PermissionsService { + db := repository.DB(ctx) + return &permissions{ + db: db, + ctx: ctx, + } +} + +func (p *permissions) List() (interface{}, error) { + return nil, errors.New("service.permissions.list: not implemented") +} + +func (p *permissions) Get(team string, scope string, resource string) (interface{}, error) { + return nil, errors.New("service.permissions.get: not implemented") +} + +func (p *permissions) Set(team string, permissions []rbac.Permission) (interface{}, error) { + return nil, errors.New("service.permissions.set: not implemented") +} diff --git a/sam/service/service.go b/sam/service/service.go index ba0539d0b..98b0140fc 100644 --- a/sam/service/service.go +++ b/sam/service/service.go @@ -15,12 +15,13 @@ type ( ) var ( - o sync.Once - DefaultAttachment AttachmentService - DefaultChannel ChannelService - DefaultMessage MessageService - DefaultPubSub *pubSub - DefaultEvent EventService + o sync.Once + DefaultAttachment AttachmentService + DefaultChannel ChannelService + DefaultMessage MessageService + DefaultPermissions PermissionsService + DefaultPubSub *pubSub + DefaultEvent EventService ) func Init() { @@ -33,6 +34,7 @@ func Init() { DefaultEvent = Event() DefaultAttachment = Attachment(fs) DefaultMessage = Message() + DefaultPermissions = Permissions() DefaultChannel = Channel() DefaultPubSub = PubSub() })