diff --git a/satellite/console/consoleweb/consoleapi/projects.go b/satellite/console/consoleweb/consoleapi/projects.go index 3bf42772d..e0e68c9b9 100644 --- a/satellite/console/consoleweb/consoleapi/projects.go +++ b/satellite/console/consoleweb/consoleapi/projects.go @@ -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) diff --git a/satellite/console/consoleweb/consoleapi/projects_test.go b/satellite/console/consoleweb/consoleapi/projects_test.go index 63addbca1..b2d5056b7 100644 --- a/satellite/console/consoleweb/consoleapi/projects_test.go +++ b/satellite/console/consoleweb/consoleapi/projects_test.go @@ -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") + }) +} diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index 0fd4b0ece..2fa3c54f4 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -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)