3
0

Improve role membership management through REST API

This commit is contained in:
Denis Arh
2019-07-02 18:48:42 +02:00
parent ae85cb8158
commit 6dc85c849d
8 changed files with 442 additions and 12 deletions

View File

@@ -820,7 +820,7 @@
{
"name": "read",
"method": "GET",
"title": "Read user details and memberships",
"title": "Read user details",
"path": "/{userID}",
"parameters": {
"path": [
@@ -905,6 +905,66 @@
}
]
}
},
{
"name": "membershipList",
"method": "GET",
"title": "Add member to a role",
"path": "/{userID}/membership",
"parameters": {
"path": [
{
"type": "uint64",
"name": "userID",
"required": true,
"title": "User ID"
}
]
}
},
{
"name": "membershipAdd",
"method": "POST",
"title": "Add role to a user",
"path": "/{userID}/membership/{roleID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "roleID",
"required": true,
"title": "Role ID"
},
{
"type": "uint64",
"name": "userID",
"required": true,
"title": "User ID"
}
]
}
},
{
"name": "membershipRemove",
"method": "DELETE",
"title": "Remove role from a user",
"path": "/{userID}/membership/{roleID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "roleID",
"required": true,
"title": "Role ID"
},
{
"type": "uint64",
"name": "userID",
"required": true,
"title": "User ID"
}
]
}
}
]
},

View File

@@ -159,7 +159,7 @@
{
"Name": "read",
"Method": "GET",
"Title": "Read user details and memberships",
"Title": "Read user details",
"Path": "/{userID}",
"Parameters": {
"path": [
@@ -244,6 +244,66 @@
}
]
}
},
{
"Name": "membershipList",
"Method": "GET",
"Title": "Add member to a role",
"Path": "/{userID}/membership",
"Parameters": {
"path": [
{
"name": "userID",
"required": true,
"title": "User ID",
"type": "uint64"
}
]
}
},
{
"Name": "membershipAdd",
"Method": "POST",
"Title": "Add role to a user",
"Path": "/{userID}/membership/{roleID}",
"Parameters": {
"path": [
{
"name": "roleID",
"required": true,
"title": "Role ID",
"type": "uint64"
},
{
"name": "userID",
"required": true,
"title": "User ID",
"type": "uint64"
}
]
}
},
{
"Name": "membershipRemove",
"Method": "DELETE",
"Title": "Remove role from a user",
"Path": "/{userID}/membership/{roleID}",
"Parameters": {
"path": [
{
"name": "roleID",
"required": true,
"title": "Role ID",
"type": "uint64"
},
{
"name": "userID",
"required": true,
"title": "User ID",
"type": "uint64"
}
]
}
}
]
}

View File

@@ -730,11 +730,14 @@ An organisation may have many roles. Roles may have many channels available. Acc
| `GET` | `/users/` | Search users (Directory) |
| `POST` | `/users/` | Create user |
| `PUT` | `/users/{userID}` | Update user details |
| `GET` | `/users/{userID}` | Read user details and memberships |
| `GET` | `/users/{userID}` | Read user details |
| `DELETE` | `/users/{userID}` | Remove user |
| `POST` | `/users/{userID}/suspend` | Suspend user |
| `POST` | `/users/{userID}/unsuspend` | Unsuspend user |
| `POST` | `/users/{userID}/password` | Set's or changes user's password |
| `GET` | `/users/{userID}/membership` | Add member to a role |
| `POST` | `/users/{userID}/membership/{roleID}` | Add role to a user |
| `DELETE` | `/users/{userID}/membership/{roleID}` | Remove role from a user |
## Search users (Directory)
@@ -793,7 +796,7 @@ An organisation may have many roles. Roles may have many channels available. Acc
| handle | string | POST | Handle | N/A | NO |
| kind | types.UserKind | POST | Kind (normal, bot) | N/A | NO |
## Read user details and memberships
## Read user details
#### Method
@@ -864,4 +867,48 @@ An organisation may have many roles. Roles may have many channels available. Acc
| userID | uint64 | PATH | User ID | N/A | YES |
| password | string | POST | New password | N/A | YES |
## Add member to a role
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/users/{userID}/membership` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| userID | uint64 | PATH | User ID | N/A | YES |
## Add role to a user
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/users/{userID}/membership/{roleID}` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| roleID | uint64 | PATH | Role ID | N/A | YES |
| userID | uint64 | PATH | User ID | N/A | YES |
## Remove role from a user
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/users/{userID}/membership/{roleID}` | HTTP/S | DELETE | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| roleID | uint64 | PATH | Role ID | N/A | YES |
| userID | uint64 | PATH | User ID | N/A | YES |
---

View File

@@ -27,6 +27,7 @@ type (
MergeByID(id, targetRoleID uint64) error
MoveByID(id, targetOrganisationID uint64) error
MembershipsFindByUserID(userID uint64) ([]*types.RoleMember, error)
MemberFindByRoleID(roleID uint64) ([]*types.RoleMember, error)
MemberAddByID(roleID, userID uint64) error
MemberRemoveByID(roleID, userID uint64) error
@@ -127,6 +128,12 @@ func (r *role) MoveByID(id, targetOrganisationID uint64) error {
return ErrNotImplemented
}
func (r *role) MembershipsFindByUserID(roleID uint64) (mm []*types.RoleMember, err error) {
rval := make([]*types.RoleMember, 0)
sql := "SELECT * FROM " + r.members + " WHERE rel_user = ?"
return rval, r.db().Select(&rval, sql, roleID)
}
func (r *role) MemberFindByRoleID(roleID uint64) (mm []*types.RoleMember, err error) {
rval := make([]*types.RoleMember, 0)
sql := "SELECT * FROM " + r.members + " WHERE rel_role = ?"

View File

@@ -45,6 +45,7 @@ type (
Unarchive(ID uint64) error
Delete(ID uint64) error
Membership(userID uint64) ([]*types.RoleMember, error)
MemberList(roleID uint64) ([]*types.RoleMember, error)
MemberAdd(roleID, userID uint64) error
MemberRemove(roleID, userID uint64) error
@@ -225,6 +226,10 @@ func (svc role) Move(roleID, targetOrganisationID uint64) error {
return svc.role.MoveByID(roleID, targetOrganisationID)
}
func (svc role) Membership(userID uint64) ([]*types.RoleMember, error) {
return svc.role.MembershipsFindByUserID(userID)
}
func (svc role) MemberList(roleID uint64) ([]*types.RoleMember, error) {
_, err := svc.findByID(roleID)
if err != nil {

View File

@@ -37,18 +37,24 @@ type UserAPI interface {
Suspend(context.Context, *request.UserSuspend) (interface{}, error)
Unsuspend(context.Context, *request.UserUnsuspend) (interface{}, error)
SetPassword(context.Context, *request.UserSetPassword) (interface{}, error)
MembershipList(context.Context, *request.UserMembershipList) (interface{}, error)
MembershipAdd(context.Context, *request.UserMembershipAdd) (interface{}, error)
MembershipRemove(context.Context, *request.UserMembershipRemove) (interface{}, error)
}
// HTTP API interface
type User struct {
List func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Suspend func(http.ResponseWriter, *http.Request)
Unsuspend func(http.ResponseWriter, *http.Request)
SetPassword func(http.ResponseWriter, *http.Request)
List func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Suspend func(http.ResponseWriter, *http.Request)
Unsuspend func(http.ResponseWriter, *http.Request)
SetPassword func(http.ResponseWriter, *http.Request)
MembershipList func(http.ResponseWriter, *http.Request)
MembershipAdd func(http.ResponseWriter, *http.Request)
MembershipRemove func(http.ResponseWriter, *http.Request)
}
func NewUser(h UserAPI) *User {
@@ -213,6 +219,66 @@ func NewUser(h UserAPI) *User {
resputil.JSON(w, value)
}
},
MembershipList: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewUserMembershipList()
if err := params.Fill(r); err != nil {
logger.LogParamError("User.MembershipList", r, err)
resputil.JSON(w, err)
return
}
value, err := h.MembershipList(r.Context(), params)
if err != nil {
logger.LogControllerError("User.MembershipList", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("User.MembershipList", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
},
MembershipAdd: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewUserMembershipAdd()
if err := params.Fill(r); err != nil {
logger.LogParamError("User.MembershipAdd", r, err)
resputil.JSON(w, err)
return
}
value, err := h.MembershipAdd(r.Context(), params)
if err != nil {
logger.LogControllerError("User.MembershipAdd", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("User.MembershipAdd", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
},
MembershipRemove: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewUserMembershipRemove()
if err := params.Fill(r); err != nil {
logger.LogParamError("User.MembershipRemove", r, err)
resputil.JSON(w, err)
return
}
value, err := h.MembershipRemove(r.Context(), params)
if err != nil {
logger.LogControllerError("User.MembershipRemove", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("User.MembershipRemove", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
},
}
}
@@ -227,5 +293,8 @@ func (h User) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.H
r.Post("/users/{userID}/suspend", h.Suspend)
r.Post("/users/{userID}/unsuspend", h.Unsuspend)
r.Post("/users/{userID}/password", h.SetPassword)
r.Get("/users/{userID}/membership", h.MembershipList)
r.Post("/users/{userID}/membership/{roleID}", h.MembershipAdd)
r.Delete("/users/{userID}/membership/{roleID}", h.MembershipRemove)
})
}

View File

@@ -523,3 +523,162 @@ func (r *UserSetPassword) Fill(req *http.Request) (err error) {
}
var _ RequestFiller = NewUserSetPassword()
// User membershipList request parameters
type UserMembershipList struct {
UserID uint64 `json:",string"`
}
func NewUserMembershipList() *UserMembershipList {
return &UserMembershipList{}
}
func (r UserMembershipList) Auditable() map[string]interface{} {
var out = map[string]interface{}{}
out["userID"] = r.UserID
return out
}
func (r *UserMembershipList) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
if err = req.ParseForm(); err != nil {
return err
}
get := map[string]string{}
post := map[string]string{}
urlQuery := req.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := req.Form
for name, param := range postVars {
post[name] = string(param[0])
}
r.UserID = parseUInt64(chi.URLParam(req, "userID"))
return err
}
var _ RequestFiller = NewUserMembershipList()
// User membershipAdd request parameters
type UserMembershipAdd struct {
RoleID uint64 `json:",string"`
UserID uint64 `json:",string"`
}
func NewUserMembershipAdd() *UserMembershipAdd {
return &UserMembershipAdd{}
}
func (r UserMembershipAdd) Auditable() map[string]interface{} {
var out = map[string]interface{}{}
out["roleID"] = r.RoleID
out["userID"] = r.UserID
return out
}
func (r *UserMembershipAdd) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
if err = req.ParseForm(); err != nil {
return err
}
get := map[string]string{}
post := map[string]string{}
urlQuery := req.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := req.Form
for name, param := range postVars {
post[name] = string(param[0])
}
r.RoleID = parseUInt64(chi.URLParam(req, "roleID"))
r.UserID = parseUInt64(chi.URLParam(req, "userID"))
return err
}
var _ RequestFiller = NewUserMembershipAdd()
// User membershipRemove request parameters
type UserMembershipRemove struct {
RoleID uint64 `json:",string"`
UserID uint64 `json:",string"`
}
func NewUserMembershipRemove() *UserMembershipRemove {
return &UserMembershipRemove{}
}
func (r UserMembershipRemove) Auditable() map[string]interface{} {
var out = map[string]interface{}{}
out["roleID"] = r.RoleID
out["userID"] = r.UserID
return out
}
func (r *UserMembershipRemove) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
if err = req.ParseForm(); err != nil {
return err
}
get := map[string]string{}
post := map[string]string{}
urlQuery := req.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := req.Form
for name, param := range postVars {
post[name] = string(param[0])
}
r.RoleID = parseUInt64(chi.URLParam(req, "roleID"))
r.UserID = parseUInt64(chi.URLParam(req, "userID"))
return err
}
var _ RequestFiller = NewUserMembershipRemove()

View File

@@ -6,6 +6,7 @@ import (
"github.com/pkg/errors"
"github.com/titpetric/factory/resputil"
"github.com/cortezaproject/corteza-server/internal/payload"
"github.com/cortezaproject/corteza-server/system/internal/service"
"github.com/cortezaproject/corteza-server/system/rest/request"
"github.com/cortezaproject/corteza-server/system/types"
@@ -16,6 +17,7 @@ var _ = errors.Wrap
type (
User struct {
user service.UserService
role service.RoleService
}
userSetPayload struct {
@@ -27,6 +29,7 @@ type (
func (User) New() *User {
ctrl := &User{}
ctrl.user = service.DefaultUser
ctrl.role = service.DefaultRole
return ctrl
}
@@ -89,6 +92,26 @@ func (ctrl User) SetPassword(ctx context.Context, r *request.UserSetPassword) (i
return resputil.OK(), ctrl.user.With(ctx).SetPassword(r.UserID, r.Password)
}
func (ctrl User) MembershipList(ctx context.Context, r *request.UserMembershipList) (interface{}, error) {
if mm, err := ctrl.role.With(ctx).Membership(r.UserID); err != nil {
return nil, err
} else {
rval := make([]string, len(mm))
for i := range mm {
rval[i] = payload.Uint64toa(mm[i].RoleID)
}
return rval, nil
}
}
func (ctrl User) MembershipAdd(ctx context.Context, r *request.UserMembershipAdd) (interface{}, error) {
return resputil.OK(), ctrl.role.With(ctx).MemberAdd(r.RoleID, r.UserID)
}
func (ctrl User) MembershipRemove(ctx context.Context, r *request.UserMembershipRemove) (interface{}, error) {
return resputil.OK(), ctrl.role.With(ctx).MemberRemove(r.RoleID, r.UserID)
}
func (ctrl User) makeFilterPayload(ctx context.Context, uu types.UserSet, f types.UserFilter, err error) (*userSetPayload, error) {
if err != nil {
return nil, err