diff --git a/api4/channel.go b/api4/channel.go index 03edc36a7b..84275acc09 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -35,6 +35,7 @@ func (api *API) InitChannel() { api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(updateChannel)).Methods("PUT") api.BaseRoutes.Channel.Handle("/patch", api.ApiSessionRequired(patchChannel)).Methods("PUT") api.BaseRoutes.Channel.Handle("/convert", api.ApiSessionRequired(convertChannelToPrivate)).Methods("POST") + api.BaseRoutes.Channel.Handle("/privacy", api.ApiSessionRequired(updateChannelPrivacy)).Methods("PUT") api.BaseRoutes.Channel.Handle("/restore", api.ApiSessionRequired(restoreChannel)).Methods("POST") api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(deleteChannel)).Methods("DELETE") api.BaseRoutes.Channel.Handle("/stats", api.ApiSessionRequired(getChannelStats)).Methods("GET") @@ -229,6 +230,54 @@ func convertChannelToPrivate(c *Context, w http.ResponseWriter, r *http.Request) w.Write([]byte(rchannel.ToJson())) } +func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireChannelId() + if c.Err != nil { + return + } + + props := model.StringInterfaceFromJson(r.Body) + privacy, ok := props["privacy"].(string) + if !ok || (privacy != model.CHANNEL_OPEN && privacy != model.CHANNEL_PRIVATE) { + c.SetInvalidParam("privacy") + return + } + + channel, err := c.App.GetChannel(c.Params.ChannelId) + if err != nil { + c.Err = err + return + } + + if !c.App.SessionHasPermissionToTeam(c.App.Session, channel.TeamId, model.PERMISSION_MANAGE_TEAM) { + c.SetPermissionError(model.PERMISSION_MANAGE_TEAM) + return + } + + if channel.Name == model.DEFAULT_CHANNEL && privacy == model.CHANNEL_PRIVATE { + c.Err = model.NewAppError("updateChannelPrivacy", "api.channel.update_channel_privacy.default_channel_error", nil, "", http.StatusBadRequest) + return + } + + user, err := c.App.GetUser(c.App.Session.UserId) + if err != nil { + c.Err = err + return + } + + channel.Type = privacy + + updatedChannel, err := c.App.UpdateChannelPrivacy(channel, user) + if err != nil { + c.Err = err + return + } + + c.LogAudit("name=" + updatedChannel.Name) + + w.Write([]byte(updatedChannel.ToJson())) +} + func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireChannelId() if c.Err != nil { diff --git a/api4/channel_test.go b/api4/channel_test.go index fb7ac11d15..8ff7a96753 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1387,6 +1387,68 @@ func TestConvertChannelToPrivate(t *testing.T) { } } +func TestUpdateChannelPrivacy(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + Client := th.Client + + type testTable []struct { + name string + channel *model.Channel + expectedPrivacy string + } + + defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false) + privateChannel := th.CreatePrivateChannel() + publicChannel := th.CreatePublicChannel() + + tt := testTable{ + {"Updating default channel should fail with forbidden status if not logged in", defaultChannel, model.CHANNEL_OPEN}, + {"Updating private channel should fail with forbidden status if not logged in", privateChannel, model.CHANNEL_PRIVATE}, + {"Updating public channel should fail with forbidden status if not logged in", publicChannel, model.CHANNEL_OPEN}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + _, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy) + CheckForbiddenStatus(t, resp) + }) + } + + th.LoginTeamAdmin() + + tt = testTable{ + {"Converting default channel to private should fail", defaultChannel, model.CHANNEL_PRIVATE}, + {"Updating privacy to an invalid setting should fail", publicChannel, "invalid"}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + _, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy) + CheckBadRequestStatus(t, resp) + }) + } + + tt = testTable{ + {"Default channel should stay public", defaultChannel, model.CHANNEL_OPEN}, + {"Public channel should stay public", publicChannel, model.CHANNEL_OPEN}, + {"Private channel should stay private", privateChannel, model.CHANNEL_PRIVATE}, + {"Public channel should convert to private", publicChannel, model.CHANNEL_PRIVATE}, + {"Private channel should convert to public", privateChannel, model.CHANNEL_OPEN}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + updatedChannel, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy) + CheckNoError(t, resp) + assert.Equal(t, updatedChannel.Type, tc.expectedPrivacy) + updatedChannel, err := th.App.GetChannel(tc.channel.Id) + require.Nil(t, err) + assert.Equal(t, updatedChannel.Type, tc.expectedPrivacy) + }) + } +} + func TestRestoreChannel(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/i18n/en.json b/i18n/en.json index c2f3ffb85d..2376e75418 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -387,6 +387,10 @@ "id": "api.channel.update_channel_member_roles.scheme_role.app_error", "translation": "The provided role is managed by a Scheme and therefore cannot be applied directly to a Channel Member" }, + { + "id": "api.channel.update_channel_privacy.default_channel_error", + "translation": "The default channel cannot be made private." + }, { "id": "api.channel.update_channel_scheme.license.error", "translation": "Your license does not support updating a channel's scheme" diff --git a/model/client4.go b/model/client4.go index a81696597c..aac5fd4bf3 100644 --- a/model/client4.go +++ b/model/client4.go @@ -2100,6 +2100,17 @@ func (c *Client4) ConvertChannelToPrivate(channelId string) (*Channel, *Response return ChannelFromJson(r.Body), BuildResponse(r) } +// UpdateChannelPrivacy updates channel privacy +func (c *Client4) UpdateChannelPrivacy(channelId string, privacy string) (*Channel, *Response) { + requestBody := map[string]string{"privacy": privacy} + r, err := c.DoApiPut(c.GetChannelRoute(channelId)+"/privacy", MapToJson(requestBody)) + if err != nil { + return nil, BuildErrorResponse(r, err) + } + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) +} + // RestoreChannel restores a previously deleted channel. Any missing fields are not updated. func (c *Client4) RestoreChannel(channelId string) (*Channel, *Response) { r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/restore", "")