satellite/console: delete project members endpoint

This commit adds a new endpoint on the console api to delete project
members and invitations.

issue: https://github.com/storj/storj/issues/6136

Change-Id: I980bb97afd1ed2ed8f0f27cc2e8dc1d80d7eef05
This commit is contained in:
Cameron 2023-08-07 12:16:53 -04:00
parent d7d196fe61
commit 969599b60b
3 changed files with 133 additions and 1 deletions

View File

@ -524,6 +524,39 @@ func (p *Projects) RespondToInvitation(w http.ResponseWriter, r *http.Request) {
}
}
// DeleteMembersAndInvitations deletes members and invitations from a project.
func (p *Projects) DeleteMembersAndInvitations(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
var ok bool
var idParam string
if idParam, ok = mux.Vars(r)["id"]; !ok {
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing project id route param"))
return
}
id, err := uuid.FromString(idParam)
if err != nil {
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
}
emailsStr := r.URL.Query().Get("emails")
if emailsStr == "" {
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing emails parameter"))
return
}
emails := strings.Split(emailsStr, ",")
err = p.service.DeleteProjectMembersAndInvitations(ctx, id, emails)
if err != nil {
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
}
}
// serveJSONError writes JSON error to response output stream.
func (p *Projects) serveJSONError(ctx context.Context, w http.ResponseWriter, status int, err error) {
web.ServeJSONError(ctx, p.log, w, status, err)

View File

@ -41,9 +41,15 @@ func createTestMembers(ctx context.Context, t *testing.T, db console.DB, p uuid.
PasswordHash: []byte("test"),
})
require.NoError(t, err)
members[memberID] = *member
status := console.UserStatus(1)
err = db.Users().Update(ctx, memberID, console.UpdateUserRequest{
Status: &status,
})
require.NoError(t, err)
_, err = db.ProjectMembers().Insert(ctx, member.ID, p)
require.NoError(t, err)
@ -382,3 +388,95 @@ func TestGetProjectMembersAndInvitationsLimitAndPage(t *testing.T) {
}
})
}
func TestDeleteProjectMembers(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
p := planet.Uplinks[0].Projects[0].ID
user, err := sat.DB.Console().Users().GetByEmail(ctx, planet.Uplinks[0].User[sat.ID()].Email)
require.NoError(t, err)
members, invitees := createTestMembers(ctx, t, sat.DB.Console(), p, &user.ID)
tokenInfo, err := sat.API.Console.Service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
require.NoError(t, err)
expire := time.Now().AddDate(0, 0, 1)
cookie := http.Cookie{
Name: "_tokenKey",
Path: "/",
Value: tokenInfo.Token.String(),
Expires: expire,
}
addr := planet.Satellites[0].API.Console.Listener.Addr().String()
var emails string
var firstAppendDone bool
for _, m := range members {
if firstAppendDone {
emails += ","
} else {
firstAppendDone = true
}
emails += m.Email
}
for e := range invitees {
if len(members) > 0 {
emails += ","
}
emails += e
}
endpoint := fmt.Sprintf("http://%s/api/v0/projects/%s/members?", addr, p.String())
params := url.Values{}
params.Add("emails", emails)
endpoint += params.Encode()
req, err := http.NewRequestWithContext(ctx, "DELETE", endpoint, nil)
require.NoError(t, err)
req.AddCookie(&cookie)
client := http.Client{}
res, err := client.Do(req)
require.NoError(t, err)
require.NotNil(t, res)
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
require.NoError(t, res.Body.Close())
require.NotContains(t, string(body), "error")
require.Equal(t, http.StatusOK, res.StatusCode)
page, err := sat.DB.Console().ProjectMembers().GetPagedWithInvitationsByProjectID(ctx, p, console.ProjectMembersCursor{Limit: 1, Page: 1})
require.NoError(t, err)
require.Len(t, page.ProjectMembers, 1)
require.Equal(t, user.ID, page.ProjectMembers[0].MemberID)
// test error
endpoint = fmt.Sprintf("http://%s/api/v0/projects/%s/members?", addr, p.String())
params = url.Values{}
params.Add("emails", "nonmember@storj.test")
endpoint += params.Encode()
req, err = http.NewRequestWithContext(ctx, "DELETE", endpoint, nil)
require.NoError(t, err)
req.AddCookie(&cookie)
client = http.Client{}
res, err = client.Do(req)
require.NoError(t, err)
require.NotNil(t, res)
body, err = io.ReadAll(res.Body)
require.NoError(t, err)
require.NoError(t, res.Body.Close())
require.Contains(t, string(body), "error")
})
}

View File

@ -278,6 +278,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
projectsRouter.Handle("", http.HandlerFunc(projectsController.GetUserProjects)).Methods(http.MethodGet, http.MethodOptions)
projectsRouter.Handle("/paged", http.HandlerFunc(projectsController.GetPagedProjects)).Methods(http.MethodGet, http.MethodOptions)
projectsRouter.Handle("/{id}", http.HandlerFunc(projectsController.UpdateProject)).Methods(http.MethodPatch, http.MethodOptions)
projectsRouter.Handle("/{id}/members", http.HandlerFunc(projectsController.DeleteMembersAndInvitations)).Methods(http.MethodDelete, http.MethodOptions)
projectsRouter.Handle("/{id}/salt", http.HandlerFunc(projectsController.GetSalt)).Methods(http.MethodGet, http.MethodOptions)
projectsRouter.Handle("/{id}/members", http.HandlerFunc(projectsController.GetMembersAndInvitations)).Methods(http.MethodGet, http.MethodOptions)
projectsRouter.Handle("/{id}/invite", http.HandlerFunc(projectsController.InviteUsers)).Methods(http.MethodPost, http.MethodOptions)