diff --git a/api4/team.go b/api4/team.go index e0d899a61b..3e1e164f8d 100644 --- a/api4/team.go +++ b/api4/team.go @@ -43,6 +43,7 @@ func (api *API) InitTeam() { api.BaseRoutes.Team.Handle("", api.ApiSessionRequired(updateTeam)).Methods("PUT") api.BaseRoutes.Team.Handle("", api.ApiSessionRequired(deleteTeam)).Methods("DELETE") api.BaseRoutes.Team.Handle("/patch", api.ApiSessionRequired(patchTeam)).Methods("PUT") + api.BaseRoutes.Team.Handle("/restore", api.ApiSessionRequired(restoreTeam)).Methods("POST") api.BaseRoutes.Team.Handle("/privacy", api.ApiSessionRequired(updateTeamPrivacy)).Methods("PUT") api.BaseRoutes.Team.Handle("/stats", api.ApiSessionRequired(getTeamStats)).Methods("GET") api.BaseRoutes.Team.Handle("/regenerate_invite_id", api.ApiSessionRequired(regenerateTeamInviteId)).Methods("POST") @@ -232,6 +233,40 @@ func patchTeam(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(patchedTeam.ToJson())) } +func restoreTeam(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireTeamId() + if c.Err != nil { + return + } + + auditRec := c.MakeAuditRecord("restoreTeam", audit.Fail) + defer c.LogAuditRec(auditRec) + auditRec.AddMeta("team_id", c.Params.TeamId) + + if !c.App.SessionHasPermissionToTeam(*c.App.Session(), c.Params.TeamId, model.PERMISSION_MANAGE_TEAM) { + c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) + return + } + + err := c.App.RestoreTeam(c.Params.TeamId) + if err != nil { + c.Err = err + return + } + + // Return the restored team to be consistent with RestoreChannel. + team, err := c.App.GetTeam(c.Params.TeamId) + if err != nil { + c.Err = err + return + } + + auditRec.AddMeta("team", team) + auditRec.Success() + + w.Write([]byte(team.ToJson())) +} + func updateTeamPrivacy(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireTeamId() if c.Err != nil { diff --git a/api4/team_test.go b/api4/team_test.go index de0ddfbc49..7e4dcfbc96 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -427,6 +427,75 @@ func TestPatchTeam(t *testing.T) { CheckNoError(t, resp) } +func TestRestoreTeam(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + Client := th.Client + + createTeam := func(t *testing.T, deleted bool, teamType string) *model.Team { + t.Helper() + team := &model.Team{ + DisplayName: "Some Team", + Description: "Some description", + CompanyName: "Some company name", + AllowOpenInvite: (teamType == model.TEAM_OPEN), + InviteId: model.NewId(), + Name: "aa-" + model.NewRandomTeamName() + "zz", + Email: "success+" + model.NewId() + "@simulator.amazonses.com", + Type: teamType, + } + team, _ = Client.CreateTeam(team) + require.NotNil(t, team) + if deleted { + _, resp := th.SystemAdminClient.SoftDeleteTeam(team.Id) + CheckOKStatus(t, resp) + } + return team + } + teamPublic := createTeam(t, true, model.TEAM_OPEN) + + t.Run("invalid team", func(t *testing.T) { + _, resp := Client.RestoreTeam(model.NewId()) + CheckForbiddenStatus(t, resp) + }) + + t.Run("restore archived public team", func(t *testing.T) { + team := createTeam(t, true, model.TEAM_OPEN) + team, resp := Client.RestoreTeam(team.Id) + CheckOKStatus(t, resp) + require.Zero(t, team.DeleteAt) + require.Equal(t, model.TEAM_OPEN, team.Type) + }) + + t.Run("restore archived private team", func(t *testing.T) { + team := createTeam(t, true, model.TEAM_INVITE) + team, resp := Client.RestoreTeam(team.Id) + CheckOKStatus(t, resp) + require.Zero(t, team.DeleteAt) + require.Equal(t, model.TEAM_INVITE, team.Type) + }) + + t.Run("restore active public team", func(t *testing.T) { + team := createTeam(t, false, model.TEAM_OPEN) + team, resp := Client.RestoreTeam(team.Id) + CheckOKStatus(t, resp) + require.Zero(t, team.DeleteAt) + require.Equal(t, model.TEAM_OPEN, team.Type) + }) + + t.Run("not logged in", func(t *testing.T) { + Client.Logout() + _, resp := Client.RestoreTeam(teamPublic.Id) + CheckUnauthorizedStatus(t, resp) + }) + + t.Run("no permission to manage team", func(t *testing.T) { + th.LoginBasic2() + _, resp := Client.RestoreTeam(teamPublic.Id) + CheckForbiddenStatus(t, resp) + }) +} + func TestPatchTeamSanitization(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() diff --git a/model/client4.go b/model/client4.go index 0b2bcf9278..b4ad6079d6 100644 --- a/model/client4.go +++ b/model/client4.go @@ -1763,6 +1763,16 @@ func (c *Client4) PatchTeam(teamId string, patch *TeamPatch) (*Team, *Response) return TeamFromJson(r.Body), BuildResponse(r) } +// RestoreTeam restores a previously deleted team. +func (c *Client4) RestoreTeam(teamId string) (*Team, *Response) { + r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/restore", "") + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) +} + // RegenerateTeamInviteId requests a new invite ID to be generated. func (c *Client4) RegenerateTeamInviteId(teamId string) (*Team, *Response) { r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/regenerate_invite_id", "")