diff --git a/internal/services/configstore/action/org.go b/internal/services/configstore/action/org.go index 1b5f0d2..7d2953b 100644 --- a/internal/services/configstore/action/org.go +++ b/internal/services/configstore/action/org.go @@ -262,3 +262,62 @@ func (h *ActionHandler) AddOrgMember(ctx context.Context, orgRef, userRef string _, err = h.dm.WriteWal(ctx, actions, cgt) return orgmember, err } + +// DeleteOrgMember deletes an org member. +func (h *ActionHandler) DeleteOrgMember(ctx context.Context, orgRef, userRef string) error { + 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)) + } + + // check that org member exists + orgmember, err = h.readDB.GetOrgMemberByOrgUserID(tx, org.ID, user.ID) + if err != nil { + return err + } + if orgmember == nil { + return util.NewErrBadRequest(errors.Errorf("orgmember for org %q, user %q doesn't exists", orgRef, userRef)) + } + + 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 err + } + + actions := []*datamanager.Action{} + actions = append(actions, &datamanager.Action{ + ActionType: datamanager.ActionTypeDelete, + DataType: string(types.ConfigTypeOrgMember), + ID: orgmember.ID, + }) + + _, err = h.dm.WriteWal(ctx, actions, cgt) + return err +} diff --git a/internal/services/configstore/api/client.go b/internal/services/configstore/api/client.go index c396662..237b716 100644 --- a/internal/services/configstore/api/client.go +++ b/internal/services/configstore/api/client.go @@ -474,6 +474,10 @@ func (c *Client) AddOrgMember(ctx context.Context, orgRef, userRef string, role return orgmember, resp, err } +func (c *Client) DeleteOrgMember(ctx context.Context, orgRef, userRef string) (*http.Response, error) { + return c.getResponse(ctx, "DELETE", fmt.Sprintf("/orgs/%s/members/%s", orgRef, userRef), nil, jsonContent, nil) +} + func (c *Client) GetOrgs(ctx context.Context, start string, limit int, asc bool) ([]*types.Organization, *http.Response, error) { q := url.Values{} if start != "" { diff --git a/internal/services/configstore/api/org.go b/internal/services/configstore/api/org.go index 6c89ec8..e50f34d 100644 --- a/internal/services/configstore/api/org.go +++ b/internal/services/configstore/api/org.go @@ -114,6 +114,7 @@ func (h *DeleteOrgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { err := h.ah.DeleteOrg(ctx, orgRef) if httpError(w, err) { h.log.Errorf("err: %+v", err) + return } if err := httpResponse(w, http.StatusNoContent, nil); err != nil { h.log.Errorf("err: %+v", err) @@ -215,3 +216,30 @@ func (h *AddOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) h.log.Errorf("err: %+v", err) } } + +type DeleteOrgMemberHandler struct { + log *zap.SugaredLogger + ah *action.ActionHandler +} + +func NewDeleteOrgMemberHandler(logger *zap.Logger, ah *action.ActionHandler) *DeleteOrgMemberHandler { + return &DeleteOrgMemberHandler{log: logger.Sugar(), ah: ah} +} + +func (h *DeleteOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + orgRef := vars["orgref"] + userRef := vars["userref"] + + err := h.ah.DeleteOrgMember(ctx, orgRef, userRef) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusNoContent, nil); err != nil { + h.log.Errorf("err: %+v", err) + } +} diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index 093bfc9..5d76a6d 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -164,6 +164,7 @@ func (s *Configstore) Run(ctx context.Context) error { deleteOrgHandler := api.NewDeleteOrgHandler(logger, s.ah) addOrgMemberHandler := api.NewAddOrgMemberHandler(logger, s.ah) + deleteOrgMemberHandler := api.NewDeleteOrgMemberHandler(logger, s.ah) remoteSourceHandler := api.NewRemoteSourceHandler(logger, s.readDB) remoteSourcesHandler := api.NewRemoteSourcesHandler(logger, s.readDB) @@ -216,6 +217,7 @@ func (s *Configstore) Run(ctx context.Context) error { apirouter.Handle("/orgs", createOrgHandler).Methods("POST") apirouter.Handle("/orgs/{orgref}", deleteOrgHandler).Methods("DELETE") apirouter.Handle("/orgs/{orgref}/members/{userref}", addOrgMemberHandler).Methods("PUT") + apirouter.Handle("/orgs/{orgref}/members/{userref}", deleteOrgMemberHandler).Methods("DELETE") apirouter.Handle("/remotesources/{remotesourceref}", remoteSourceHandler).Methods("GET") apirouter.Handle("/remotesources", remoteSourcesHandler).Methods("GET") diff --git a/internal/services/gateway/action/org.go b/internal/services/gateway/action/org.go index 0726d84..4828f31 100644 --- a/internal/services/gateway/action/org.go +++ b/internal/services/gateway/action/org.go @@ -136,3 +136,25 @@ func (h *ActionHandler) AddOrgMember(ctx context.Context, orgRef, userRef string User: user, }, nil } + +func (h *ActionHandler) DeleteOrgMember(ctx context.Context, orgRef, userRef string) error { + org, resp, err := h.configstoreClient.GetOrg(ctx, orgRef) + if err != nil { + return ErrFromRemote(resp, err) + } + + isOrgOwner, err := h.IsOrgOwner(ctx, org.ID) + if err != nil { + return errors.Wrapf(err, "failed to determine ownership") + } + if !isOrgOwner { + return util.NewErrForbidden(errors.Errorf("user not authorized")) + } + + resp, err = h.configstoreClient.DeleteOrgMember(ctx, orgRef, userRef) + if err != nil { + return ErrFromRemote(resp, errors.Wrapf(err, "failed to add/update organization member")) + } + + return nil +} diff --git a/internal/services/gateway/api/org.go b/internal/services/gateway/api/org.go index 775d625..25003ed 100644 --- a/internal/services/gateway/api/org.go +++ b/internal/services/gateway/api/org.go @@ -248,3 +248,30 @@ func (h *AddOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) h.log.Errorf("err: %+v", err) } } + +type DeleteOrgMemberHandler struct { + log *zap.SugaredLogger + ah *action.ActionHandler +} + +func NewDeleteOrgMemberHandler(logger *zap.Logger, ah *action.ActionHandler) *DeleteOrgMemberHandler { + return &DeleteOrgMemberHandler{log: logger.Sugar(), ah: ah} +} + +func (h *DeleteOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + orgRef := vars["orgref"] + userRef := vars["userref"] + + err := h.ah.DeleteOrgMember(ctx, orgRef, userRef) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + return + } + + if err := httpResponse(w, http.StatusNoContent, nil); err != nil { + h.log.Errorf("err: %+v", err) + } +} diff --git a/internal/services/gateway/gateway.go b/internal/services/gateway/gateway.go index 3f3055b..a1c50f7 100644 --- a/internal/services/gateway/gateway.go +++ b/internal/services/gateway/gateway.go @@ -189,6 +189,7 @@ func (g *Gateway) Run(ctx context.Context) error { deleteOrgHandler := api.NewDeleteOrgHandler(logger, g.ah) addOrgMemberHandler := api.NewAddOrgMemberHandler(logger, g.ah) + deleteOrgMemberHandler := api.NewDeleteOrgMemberHandler(logger, g.ah) runHandler := api.NewRunHandler(logger, g.ah) runsHandler := api.NewRunsHandler(logger, g.ah) @@ -264,6 +265,7 @@ func (g *Gateway) Run(ctx context.Context) error { apirouter.Handle("/orgs", authForcedHandler(createOrgHandler)).Methods("POST") apirouter.Handle("/orgs/{orgref}", authForcedHandler(deleteOrgHandler)).Methods("DELETE") apirouter.Handle("/orgs/{orgref}/members/{userref}", authForcedHandler(addOrgMemberHandler)).Methods("PUT") + apirouter.Handle("/orgs/{orgref}/members/{userref}", authForcedHandler(deleteOrgMemberHandler)).Methods("DELETE") apirouter.Handle("/runs/{runid}", authForcedHandler(runHandler)).Methods("GET") apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")