*: implement add/update org member
This commit is contained in:
parent
8069063e0d
commit
620bae68df
|
@ -17,6 +17,7 @@ package action
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sorintlab/agola/internal/datamanager"
|
"github.com/sorintlab/agola/internal/datamanager"
|
||||||
|
@ -49,12 +50,12 @@ func (h *ActionHandler) CreateOrg(ctx context.Context, org *types.Organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check duplicate org name
|
// check duplicate org name
|
||||||
u, err := h.readDB.GetOrgByName(tx, org.Name)
|
o, err := h.readDB.GetOrgByName(tx, org.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if u != nil {
|
if o != nil {
|
||||||
return util.NewErrBadRequest(errors.Errorf("org %q already exists", u.Name))
|
return util.NewErrBadRequest(errors.Errorf("org %q already exists", o.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if org.CreatorUserID != "" {
|
if org.CreatorUserID != "" {
|
||||||
|
@ -180,3 +181,84 @@ func (h *ActionHandler) DeleteOrg(ctx context.Context, orgRef string) error {
|
||||||
_, err = h.dm.WriteWal(ctx, actions, cgt)
|
_, err = h.dm.WriteWal(ctx, actions, cgt)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddOrgMember add/updates an org member.
|
||||||
|
// TODO(sgotti) handle invitation when implemented
|
||||||
|
func (h *ActionHandler) AddOrgMember(ctx context.Context, orgRef, userRef string, role types.MemberRole) (*types.OrganizationMember, error) {
|
||||||
|
if !types.IsValidMemberRole(role) {
|
||||||
|
return nil, util.NewErrBadRequest(errors.Errorf("invalid role %q", role))
|
||||||
|
}
|
||||||
|
|
||||||
|
var org *types.Organization
|
||||||
|
var user *types.User
|
||||||
|
var orgmember *types.OrganizationMember
|
||||||
|
var cgt *datamanager.ChangeGroupsUpdateToken
|
||||||
|
|
||||||
|
// must do all the checks in a single transaction to avoid concurrent changes
|
||||||
|
err := h.readDB.Do(func(tx *db.Tx) error {
|
||||||
|
var err error
|
||||||
|
// check existing org
|
||||||
|
org, err = h.readDB.GetOrg(tx, orgRef)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if org == nil {
|
||||||
|
return util.NewErrBadRequest(errors.Errorf("org %q doesn't exists", orgRef))
|
||||||
|
}
|
||||||
|
// check existing user
|
||||||
|
user, err = h.readDB.GetUser(tx, userRef)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return util.NewErrBadRequest(errors.Errorf("user %q doesn't exists", userRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch org member if it already exist
|
||||||
|
orgmember, err = h.readDB.GetOrgMemberByOrgUserID(tx, org.ID, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cgNames := []string{util.EncodeSha256Hex(fmt.Sprintf("orgmember-%s-%s", org.ID, user.ID))}
|
||||||
|
cgt, err = h.readDB.GetChangeGroupsUpdateTokens(tx, cgNames)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update if role changed
|
||||||
|
if orgmember != nil {
|
||||||
|
if orgmember.MemberRole == role {
|
||||||
|
return orgmember, nil
|
||||||
|
}
|
||||||
|
orgmember.MemberRole = role
|
||||||
|
} else {
|
||||||
|
orgmember = &types.OrganizationMember{
|
||||||
|
ID: uuid.NewV4().String(),
|
||||||
|
OrganizationID: org.ID,
|
||||||
|
UserID: user.ID,
|
||||||
|
MemberRole: role,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actions := []*datamanager.Action{}
|
||||||
|
orgmemberj, err := json.Marshal(orgmember)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to marshal project group")
|
||||||
|
}
|
||||||
|
actions = append(actions, &datamanager.Action{
|
||||||
|
ActionType: datamanager.ActionTypePut,
|
||||||
|
DataType: string(types.ConfigTypeOrgMember),
|
||||||
|
ID: orgmember.ID,
|
||||||
|
Data: orgmemberj,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = h.dm.WriteWal(ctx, actions, cgt)
|
||||||
|
return orgmember, err
|
||||||
|
}
|
||||||
|
|
|
@ -460,6 +460,20 @@ func (c *Client) DeleteOrg(ctx context.Context, orgRef string) (*http.Response,
|
||||||
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/orgs/%s", orgRef), nil, jsonContent, nil)
|
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/orgs/%s", orgRef), nil, jsonContent, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) AddOrgMember(ctx context.Context, orgRef, userRef string, role types.MemberRole) (*types.OrganizationMember, *http.Response, error) {
|
||||||
|
req := &AddOrgMemberRequest{
|
||||||
|
Role: role,
|
||||||
|
}
|
||||||
|
omj, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orgmember := new(types.OrganizationMember)
|
||||||
|
resp, err := c.getParsedResponse(ctx, "PUT", fmt.Sprintf("/orgs/%s/members/%s", orgRef, userRef), nil, jsonContent, bytes.NewReader(omj), orgmember)
|
||||||
|
return orgmember, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetOrgs(ctx context.Context, start string, limit int, asc bool) ([]*types.Organization, *http.Response, error) {
|
func (c *Client) GetOrgs(ctx context.Context, start string, limit int, asc bool) ([]*types.Organization, *http.Response, error) {
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
if start != "" {
|
if start != "" {
|
||||||
|
|
|
@ -177,3 +177,41 @@ func (h *OrgsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
h.log.Errorf("err: %+v", err)
|
h.log.Errorf("err: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddOrgMemberRequest struct {
|
||||||
|
Role types.MemberRole
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddOrgMemberHandler struct {
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
ah *action.ActionHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddOrgMemberHandler(logger *zap.Logger, ah *action.ActionHandler) *AddOrgMemberHandler {
|
||||||
|
return &AddOrgMemberHandler{log: logger.Sugar(), ah: ah}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
orgRef := vars["orgref"]
|
||||||
|
userRef := vars["userref"]
|
||||||
|
|
||||||
|
var req AddOrgMemberRequest
|
||||||
|
d := json.NewDecoder(r.Body)
|
||||||
|
if err := d.Decode(&req); err != nil {
|
||||||
|
httpError(w, util.NewErrBadRequest(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
org, err := h.ah.AddOrgMember(ctx, orgRef, userRef, req.Role)
|
||||||
|
if httpError(w, err) {
|
||||||
|
h.log.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := httpResponse(w, http.StatusCreated, org); err != nil {
|
||||||
|
h.log.Errorf("err: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -163,6 +163,8 @@ func (s *Configstore) Run(ctx context.Context) error {
|
||||||
createOrgHandler := api.NewCreateOrgHandler(logger, s.ah)
|
createOrgHandler := api.NewCreateOrgHandler(logger, s.ah)
|
||||||
deleteOrgHandler := api.NewDeleteOrgHandler(logger, s.ah)
|
deleteOrgHandler := api.NewDeleteOrgHandler(logger, s.ah)
|
||||||
|
|
||||||
|
addOrgMemberHandler := api.NewAddOrgMemberHandler(logger, s.ah)
|
||||||
|
|
||||||
remoteSourceHandler := api.NewRemoteSourceHandler(logger, s.readDB)
|
remoteSourceHandler := api.NewRemoteSourceHandler(logger, s.readDB)
|
||||||
remoteSourcesHandler := api.NewRemoteSourcesHandler(logger, s.readDB)
|
remoteSourcesHandler := api.NewRemoteSourcesHandler(logger, s.readDB)
|
||||||
createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(logger, s.ah)
|
createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(logger, s.ah)
|
||||||
|
@ -213,6 +215,7 @@ func (s *Configstore) Run(ctx context.Context) error {
|
||||||
apirouter.Handle("/orgs", orgsHandler).Methods("GET")
|
apirouter.Handle("/orgs", orgsHandler).Methods("GET")
|
||||||
apirouter.Handle("/orgs", createOrgHandler).Methods("POST")
|
apirouter.Handle("/orgs", createOrgHandler).Methods("POST")
|
||||||
apirouter.Handle("/orgs/{orgref}", deleteOrgHandler).Methods("DELETE")
|
apirouter.Handle("/orgs/{orgref}", deleteOrgHandler).Methods("DELETE")
|
||||||
|
apirouter.Handle("/orgs/{orgref}/members/{userref}", addOrgMemberHandler).Methods("PUT")
|
||||||
|
|
||||||
apirouter.Handle("/remotesources/{remotesourceref}", remoteSourceHandler).Methods("GET")
|
apirouter.Handle("/remotesources/{remotesourceref}", remoteSourceHandler).Methods("GET")
|
||||||
apirouter.Handle("/remotesources", remoteSourcesHandler).Methods("GET")
|
apirouter.Handle("/remotesources", remoteSourcesHandler).Methods("GET")
|
||||||
|
|
|
@ -232,6 +232,26 @@ func (r *ReadDB) deleteOrgMember(tx *db.Tx, orgmemberID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ReadDB) GetOrgMemberByOrgUserID(tx *db.Tx, orgID, userID string) (*types.OrganizationMember, error) {
|
||||||
|
q, args, err := orgmemberSelect.Where(sq.Eq{"orgmember.orgid": orgID, "orgmember.userid": userID}).ToSql()
|
||||||
|
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to build query")
|
||||||
|
}
|
||||||
|
|
||||||
|
oms, _, err := fetchOrgMembers(tx, q, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if len(oms) > 1 {
|
||||||
|
return nil, errors.Errorf("too many rows returned")
|
||||||
|
}
|
||||||
|
if len(oms) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return oms[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
func fetchOrgMembers(tx *db.Tx, q string, args ...interface{}) ([]*types.OrganizationMember, []string, error) {
|
func fetchOrgMembers(tx *db.Tx, q string, args ...interface{}) ([]*types.OrganizationMember, []string, error) {
|
||||||
rows, err := tx.Query(q, args...)
|
rows, err := tx.Query(q, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -100,3 +100,39 @@ func (h *ActionHandler) DeleteOrg(ctx context.Context, orgRef string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddOrgMemberResponse struct {
|
||||||
|
OrganizationMember *types.OrganizationMember
|
||||||
|
Org *types.Organization
|
||||||
|
User *types.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ActionHandler) AddOrgMember(ctx context.Context, orgRef, userRef string, role types.MemberRole) (*AddOrgMemberResponse, error) {
|
||||||
|
org, resp, err := h.configstoreClient.GetOrg(ctx, orgRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrFromRemote(resp, err)
|
||||||
|
}
|
||||||
|
user, resp, err := h.configstoreClient.GetUser(ctx, userRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrFromRemote(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isOrgOwner, err := h.IsOrgOwner(ctx, org.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to determine ownership")
|
||||||
|
}
|
||||||
|
if !isOrgOwner {
|
||||||
|
return nil, util.NewErrForbidden(errors.Errorf("user not authorized"))
|
||||||
|
}
|
||||||
|
|
||||||
|
orgmember, resp, err := h.configstoreClient.AddOrgMember(ctx, orgRef, userRef, role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrFromRemote(resp, errors.Wrapf(err, "failed to add/update organization member"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AddOrgMemberResponse{
|
||||||
|
OrganizationMember: orgmember,
|
||||||
|
Org: org,
|
||||||
|
User: user,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -195,3 +195,56 @@ func (h *OrgsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
h.log.Errorf("err: %+v", err)
|
h.log.Errorf("err: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrgMemberResponse struct {
|
||||||
|
Organization *OrgResponse `json:"organization,omitempty"`
|
||||||
|
User *UserResponse `json:"user,omitempty"`
|
||||||
|
Role types.MemberRole `json:"role,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOrgMemberResponse(org *types.Organization, user *types.User, role types.MemberRole) *OrgMemberResponse {
|
||||||
|
return &OrgMemberResponse{
|
||||||
|
Organization: createOrgResponse(org),
|
||||||
|
User: createUserResponse(user),
|
||||||
|
Role: role,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddOrgMemberRequest struct {
|
||||||
|
Role types.MemberRole `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddOrgMemberHandler struct {
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
ah *action.ActionHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddOrgMemberHandler(logger *zap.Logger, ah *action.ActionHandler) *AddOrgMemberHandler {
|
||||||
|
return &AddOrgMemberHandler{log: logger.Sugar(), ah: ah}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
orgRef := vars["orgref"]
|
||||||
|
userRef := vars["userref"]
|
||||||
|
|
||||||
|
var req AddOrgMemberRequest
|
||||||
|
d := json.NewDecoder(r.Body)
|
||||||
|
if err := d.Decode(&req); err != nil {
|
||||||
|
httpError(w, util.NewErrBadRequest(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ares, err := h.ah.AddOrgMember(ctx, orgRef, userRef, req.Role)
|
||||||
|
if httpError(w, err) {
|
||||||
|
h.log.Errorf("err: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := createOrgMemberResponse(ares.Org, ares.User, ares.OrganizationMember.MemberRole)
|
||||||
|
if err := httpResponse(w, http.StatusOK, res); err != nil {
|
||||||
|
h.log.Errorf("err: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -188,6 +188,8 @@ func (g *Gateway) Run(ctx context.Context) error {
|
||||||
createOrgHandler := api.NewCreateOrgHandler(logger, g.ah)
|
createOrgHandler := api.NewCreateOrgHandler(logger, g.ah)
|
||||||
deleteOrgHandler := api.NewDeleteOrgHandler(logger, g.ah)
|
deleteOrgHandler := api.NewDeleteOrgHandler(logger, g.ah)
|
||||||
|
|
||||||
|
addOrgMemberHandler := api.NewAddOrgMemberHandler(logger, g.ah)
|
||||||
|
|
||||||
runHandler := api.NewRunHandler(logger, g.ah)
|
runHandler := api.NewRunHandler(logger, g.ah)
|
||||||
runsHandler := api.NewRunsHandler(logger, g.ah)
|
runsHandler := api.NewRunsHandler(logger, g.ah)
|
||||||
runtaskHandler := api.NewRuntaskHandler(logger, g.ah)
|
runtaskHandler := api.NewRuntaskHandler(logger, g.ah)
|
||||||
|
@ -261,6 +263,7 @@ func (g *Gateway) Run(ctx context.Context) error {
|
||||||
apirouter.Handle("/orgs", authForcedHandler(orgsHandler)).Methods("GET")
|
apirouter.Handle("/orgs", authForcedHandler(orgsHandler)).Methods("GET")
|
||||||
apirouter.Handle("/orgs", authForcedHandler(createOrgHandler)).Methods("POST")
|
apirouter.Handle("/orgs", authForcedHandler(createOrgHandler)).Methods("POST")
|
||||||
apirouter.Handle("/orgs/{orgref}", authForcedHandler(deleteOrgHandler)).Methods("DELETE")
|
apirouter.Handle("/orgs/{orgref}", authForcedHandler(deleteOrgHandler)).Methods("DELETE")
|
||||||
|
apirouter.Handle("/orgs/{orgref}/members/{userref}", authForcedHandler(addOrgMemberHandler)).Methods("PUT")
|
||||||
|
|
||||||
apirouter.Handle("/runs/{runid}", authForcedHandler(runHandler)).Methods("GET")
|
apirouter.Handle("/runs/{runid}", authForcedHandler(runHandler)).Methods("GET")
|
||||||
apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")
|
apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")
|
||||||
|
|
|
@ -59,6 +59,16 @@ const (
|
||||||
MemberRoleMember MemberRole = "member"
|
MemberRoleMember MemberRole = "member"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func IsValidMemberRole(r MemberRole) bool {
|
||||||
|
switch r {
|
||||||
|
case MemberRoleOwner:
|
||||||
|
case MemberRoleMember:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type Parent struct {
|
type Parent struct {
|
||||||
Type ConfigType `json:"type,omitempty"`
|
Type ConfigType `json:"type,omitempty"`
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
|
|
Loading…
Reference in New Issue