diff options
Diffstat (limited to 'api4')
-rw-r--r-- | api4/api.go | 5 | ||||
-rw-r--r-- | api4/apitestlib.go | 25 | ||||
-rw-r--r-- | api4/channel.go | 51 | ||||
-rw-r--r-- | api4/channel_test.go | 78 | ||||
-rw-r--r-- | api4/emoji.go | 66 | ||||
-rw-r--r-- | api4/emoji_test.go | 124 | ||||
-rw-r--r-- | api4/post.go | 41 | ||||
-rw-r--r-- | api4/role.go | 1 | ||||
-rw-r--r-- | api4/scheme.go | 211 | ||||
-rw-r--r-- | api4/scheme_test.go | 737 | ||||
-rw-r--r-- | api4/team.go | 53 | ||||
-rw-r--r-- | api4/team_test.go | 76 |
12 files changed, 1442 insertions, 26 deletions
diff --git a/api4/api.go b/api4/api.go index 918154c0d..02b884db7 100644 --- a/api4/api.go +++ b/api4/api.go @@ -97,7 +97,8 @@ type Routes struct { Reactions *mux.Router // 'api/v4/reactions' - Roles *mux.Router // 'api/v4/roles' + Roles *mux.Router // 'api/v4/roles' + Schemes *mux.Router // 'api/v4/schemes' Emojis *mux.Router // 'api/v4/emoji' Emoji *mux.Router // 'api/v4/emoji/{emoji_id:[A-Za-z0-9]+}' @@ -198,6 +199,7 @@ func Init(a *app.App, root *mux.Router) *API { api.BaseRoutes.OpenGraph = api.BaseRoutes.ApiRoot.PathPrefix("/opengraph").Subrouter() api.BaseRoutes.Roles = api.BaseRoutes.ApiRoot.PathPrefix("/roles").Subrouter() + api.BaseRoutes.Schemes = api.BaseRoutes.ApiRoot.PathPrefix("/schemes").Subrouter() api.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter() @@ -227,6 +229,7 @@ func Init(a *app.App, root *mux.Router) *API { api.InitOpenGraph() api.InitPlugin() api.InitRole() + api.InitScheme() api.InitImage() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404)) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 86150e05a..22084a1d6 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -125,6 +125,7 @@ func setupTestHelper(enterprise bool) *TestHelper { wsapi.Init(th.App, th.App.Srv.WebSocketRouter) th.App.Srv.Store.MarkSystemRanUnitTests() th.App.DoAdvancedPermissionsMigration() + th.App.DoEmojisPermissionsMigration() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) @@ -767,7 +768,7 @@ func (me *TestHelper) MakeUserChannelAdmin(user *model.User, channel *model.Chan if cmr := <-me.App.Srv.Store.Channel().GetMember(channel.Id, user.Id); cmr.Err == nil { cm := cmr.Data.(*model.ChannelMember) - cm.Roles = "channel_admin channel_user" + cm.SchemeAdmin = true if sr := <-me.App.Srv.Store.Channel().UpdateMember(cm); sr.Err != nil { utils.EnableDebugLogForTest() panic(sr.Err) @@ -783,28 +784,42 @@ func (me *TestHelper) MakeUserChannelAdmin(user *model.User, channel *model.Chan func (me *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) { utils.DisableDebugLogForTest() - tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID + " " + model.TEAM_ADMIN_ROLE_ID} - if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { + if tmr := <-me.App.Srv.Store.Team().GetMember(team.Id, user.Id); tmr.Err == nil { + tm := tmr.Data.(*model.TeamMember) + tm.SchemeAdmin = true + if sr := <-me.App.Srv.Store.Team().UpdateMember(tm); sr.Err != nil { + utils.EnableDebugLogForTest() + panic(sr.Err) + } + } else { utils.EnableDebugLogForTest() mlog.Error(tmr.Err.Error()) time.Sleep(time.Second) panic(tmr.Err) } + utils.EnableDebugLogForTest() } func (me *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Team) { utils.DisableDebugLogForTest() - tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id, Roles: model.TEAM_USER_ROLE_ID} - if tmr := <-me.App.Srv.Store.Team().UpdateMember(tm); tmr.Err != nil { + if tmr := <-me.App.Srv.Store.Team().GetMember(team.Id, user.Id); tmr.Err == nil { + tm := tmr.Data.(*model.TeamMember) + tm.SchemeAdmin = false + if sr := <-me.App.Srv.Store.Team().UpdateMember(tm); sr.Err != nil { + utils.EnableDebugLogForTest() + panic(sr.Err) + } + } else { utils.EnableDebugLogForTest() mlog.Error(tmr.Err.Error()) time.Sleep(time.Second) panic(tmr.Err) } + utils.EnableDebugLogForTest() } diff --git a/api4/channel.go b/api4/channel.go index 1026a41ad..e5101ada8 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -15,6 +15,7 @@ func (api *API) InitChannel() { api.BaseRoutes.Channels.Handle("/direct", api.ApiSessionRequired(createDirectChannel)).Methods("POST") api.BaseRoutes.Channels.Handle("/group", api.ApiSessionRequired(createGroupChannel)).Methods("POST") api.BaseRoutes.Channels.Handle("/members/{user_id:[A-Za-z0-9]+}/view", api.ApiSessionRequired(viewChannel)).Methods("POST") + api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/scheme", api.ApiSessionRequired(updateChannelScheme)).Methods("PUT") api.BaseRoutes.ChannelsForTeam.Handle("", api.ApiSessionRequired(getPublicChannelsForTeam)).Methods("GET") api.BaseRoutes.ChannelsForTeam.Handle("/deleted", api.ApiSessionRequired(getDeletedChannelsForTeam)).Methods("GET") @@ -946,3 +947,53 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) } + +func updateChannelScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireChannelId() + if c.Err != nil { + return + } + + schemeID := model.SchemeIDFromJson(r.Body) + if schemeID == nil || len(*schemeID) != 26 { + c.SetInvalidParam("scheme_id") + return + } + + if c.App.License() == nil { + c.Err = model.NewAppError("Api4.UpdateChannelScheme", "api.channel.update_channel_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionToChannel(c.Session, c.Params.ChannelId, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scheme, err := c.App.GetScheme(*schemeID) + if err != nil { + c.Err = err + return + } + + if scheme.Scope != model.SCHEME_SCOPE_CHANNEL { + c.Err = model.NewAppError("Api4.UpdateChannelScheme", "api.channel.update_channel_scheme.scheme_scope.error", nil, "", http.StatusBadRequest) + return + } + + channel, err := c.App.GetChannel(c.Params.ChannelId) + if err != nil { + c.Err = err + return + } + + channel.SchemeId = &scheme.Id + + _, err = c.App.UpdateChannelScheme(channel) + if err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} diff --git a/api4/channel_test.go b/api4/channel_test.go index 2a1e78753..7b677f77f 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1463,7 +1463,7 @@ func TestUpdateChannelRoles(t *testing.T) { defer th.TearDown() Client := th.Client - const CHANNEL_ADMIN = "channel_admin channel_user" + const CHANNEL_ADMIN = "channel_user channel_admin" const CHANNEL_MEMBER = "channel_user" // User 1 creates a channel, making them channel admin by default. @@ -1914,3 +1914,79 @@ func TestAutocompleteChannels(t *testing.T) { } } } + +func TestUpdateChannelScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + team := &model.Team{ + DisplayName: "Name", + Description: "Some description", + CompanyName: "Some company name", + AllowOpenInvite: false, + InviteId: "inviteid0", + Name: "z-z-" + model.NewId() + "a", + Email: "success+" + model.NewId() + "@simulator.amazonses.com", + Type: model.TEAM_OPEN, + } + team, _ = th.SystemAdminClient.CreateTeam(team) + + channel := &model.Channel{ + DisplayName: "Name", + Name: "z-z-" + model.NewId() + "a", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, _ = th.SystemAdminClient.CreateChannel(channel) + + channelScheme := &model.Scheme{ + DisplayName: "DisplayName", + Name: model.NewId(), + Description: "Some description", + Scope: model.SCHEME_SCOPE_CHANNEL, + } + channelScheme, _ = th.SystemAdminClient.CreateScheme(channelScheme) + teamScheme := &model.Scheme{ + DisplayName: "DisplayName", + Name: model.NewId(), + Description: "Some description", + Scope: model.SCHEME_SCOPE_TEAM, + } + teamScheme, _ = th.SystemAdminClient.CreateScheme(teamScheme) + + // Test the setup/base case. + _, resp := th.SystemAdminClient.UpdateChannelScheme(channel.Id, channelScheme.Id) + CheckNoError(t, resp) + + // Test various invalid channel and scheme id combinations. + _, resp = th.SystemAdminClient.UpdateChannelScheme(channel.Id, "x") + CheckBadRequestStatus(t, resp) + _, resp = th.SystemAdminClient.UpdateChannelScheme("x", channelScheme.Id) + CheckBadRequestStatus(t, resp) + _, resp = th.SystemAdminClient.UpdateChannelScheme("x", "x") + CheckBadRequestStatus(t, resp) + + // Test that permissions are required. + _, resp = th.Client.UpdateChannelScheme(channel.Id, channelScheme.Id) + CheckForbiddenStatus(t, resp) + + // Test that a license is requried. + th.App.SetLicense(nil) + _, resp = th.SystemAdminClient.UpdateChannelScheme(channel.Id, channelScheme.Id) + CheckNotImplementedStatus(t, resp) + th.App.SetLicense(model.NewTestLicense("")) + + // Test an invalid scheme scope. + _, resp = th.SystemAdminClient.UpdateChannelScheme(channel.Id, teamScheme.Id) + fmt.Printf("resp: %+v\n", resp) + CheckBadRequestStatus(t, resp) + + // Test that an unauthenticated user gets rejected. + th.SystemAdminClient.Logout() + _, resp = th.SystemAdminClient.UpdateChannelScheme(channel.Id, channelScheme.Id) + CheckUnauthorizedStatus(t, resp) +} diff --git a/api4/emoji.go b/api4/emoji.go index cfb5dd6ab..42f66a22a 100644 --- a/api4/emoji.go +++ b/api4/emoji.go @@ -33,12 +33,6 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - if emojiInterface := c.App.Emoji; emojiInterface != nil && - !emojiInterface.CanUserCreateEmoji(c.Session.Roles, c.Session.TeamMembers) { - c.Err = model.NewAppError("getEmoji", "api.emoji.disabled.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized) - return - } - if len(*c.App.Config().FileSettings.DriverName) == 0 { c.Err = model.NewAppError("createEmoji", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) return @@ -54,6 +48,28 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Allow any user with MANAGE_EMOJIS permission at Team level to manage emojis at system level + memberships, err := c.App.GetTeamMembersForUser(c.Session.UserId) + + if err != nil { + c.Err = err + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_EMOJIS) { + hasPermission := false + for _, membership := range memberships { + if c.App.SessionHasPermissionToTeam(c.Session, membership.TeamId, model.PERMISSION_MANAGE_EMOJIS) { + hasPermission = true + break + } + } + if !hasPermission { + c.SetPermissionError(model.PERMISSION_MANAGE_EMOJIS) + return + } + } + m := r.MultipartForm props := m.Value @@ -110,11 +126,45 @@ func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - if c.Session.UserId != emoji.CreatorId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.Err = model.NewAppError("deleteImage", "api.emoji.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized) + // Allow any user with MANAGE_EMOJIS permission at Team level to manage emojis at system level + memberships, err := c.App.GetTeamMembersForUser(c.Session.UserId) + + if err != nil { + c.Err = err return } + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_EMOJIS) { + hasPermission := false + for _, membership := range memberships { + if c.App.SessionHasPermissionToTeam(c.Session, membership.TeamId, model.PERMISSION_MANAGE_EMOJIS) { + hasPermission = true + break + } + } + if !hasPermission { + c.SetPermissionError(model.PERMISSION_MANAGE_EMOJIS) + return + } + } + + if c.Session.UserId != emoji.CreatorId { + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OTHERS_EMOJIS) { + hasPermission := false + for _, membership := range memberships { + if c.App.SessionHasPermissionToTeam(c.Session, membership.TeamId, model.PERMISSION_MANAGE_OTHERS_EMOJIS) { + hasPermission = true + break + } + } + + if !hasPermission { + c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_EMOJIS) + return + } + } + } + err = c.App.DeleteEmoji(emoji) if err != nil { c.Err = err diff --git a/api4/emoji_test.go b/api4/emoji_test.go index 39da4aaef..cb6398312 100644 --- a/api4/emoji_test.go +++ b/api4/emoji_test.go @@ -26,6 +26,11 @@ func TestCreateEmoji(t *testing.T) { }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = false }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + emoji := &model.Emoji{ CreatorId: th.BasicUser.Id, Name: model.NewId(), @@ -141,6 +146,28 @@ func TestCreateEmoji(t *testing.T) { _, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") CheckForbiddenStatus(t, resp) + + // try to create an emoji without permissions + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + + emoji = &model.Emoji{ + CreatorId: th.BasicUser.Id, + Name: model.NewId(), + } + + _, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckForbiddenStatus(t, resp) + + // create an emoji with permissions in one team + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.TEAM_USER_ROLE_ID) + + emoji = &model.Emoji{ + CreatorId: th.BasicUser.Id, + Name: model.NewId(), + } + + _, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckNoError(t, resp) } func TestGetEmojiList(t *testing.T) { @@ -186,7 +213,7 @@ func TestGetEmojiList(t *testing.T) { } } if !found { - t.Fatalf("failed to get emoji with id %v", emoji.Id) + t.Fatalf("failed to get emoji with id %v, %v", emoji.Id, len(listEmoji)) } } @@ -231,6 +258,11 @@ func TestDeleteEmoji(t *testing.T) { }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + emoji := &model.Emoji{ CreatorId: th.BasicUser.Id, Name: model.NewId(), @@ -277,14 +309,100 @@ func TestDeleteEmoji(t *testing.T) { _, resp = Client.DeleteEmoji("") CheckNotFoundStatus(t, resp) - //Try to delete other user's custom emoji + //Try to delete my custom emoji without permissions + newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckNoError(t, resp) + + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + _, resp = Client.DeleteEmoji(newEmoji.Id) + CheckForbiddenStatus(t, resp) + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + + //Try to delete other user's custom emoji without MANAGE_EMOJIS permissions + emoji = &model.Emoji{ + CreatorId: th.BasicUser.Id, + Name: model.NewId(), + } + newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") CheckNoError(t, resp) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) Client.Logout() th.LoginBasic2() ok, resp = Client.DeleteEmoji(newEmoji.Id) - CheckUnauthorizedStatus(t, resp) + CheckForbiddenStatus(t, resp) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OTHERS_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + Client.Logout() + th.LoginBasic() + + //Try to delete other user's custom emoji without MANAGE_OTHERS_EMOJIS permissions + emoji = &model.Emoji{ + CreatorId: th.BasicUser.Id, + Name: model.NewId(), + } + + newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckNoError(t, resp) + + Client.Logout() + th.LoginBasic2() + ok, resp = Client.DeleteEmoji(newEmoji.Id) + CheckForbiddenStatus(t, resp) + Client.Logout() + th.LoginBasic() + + //Try to delete other user's custom emoji with permissions + emoji = &model.Emoji{ + CreatorId: th.BasicUser.Id, + Name: model.NewId(), + } + + newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckNoError(t, resp) + + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + Client.Logout() + th.LoginBasic2() + ok, resp = Client.DeleteEmoji(newEmoji.Id) + CheckNoError(t, resp) + + Client.Logout() + th.LoginBasic() + + //Try to delete my custom emoji with permissions at team level + newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckNoError(t, resp) + + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.TEAM_USER_ROLE_ID) + _, resp = Client.DeleteEmoji(newEmoji.Id) + CheckNoError(t, resp) + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.TEAM_USER_ROLE_ID) + + //Try to delete other user's custom emoji with permissions at team level + emoji = &model.Emoji{ + CreatorId: th.BasicUser.Id, + Name: model.NewId(), + } + + newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") + CheckNoError(t, resp) + + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OTHERS_EMOJIS.Id, model.SYSTEM_USER_ROLE_ID) + + th.AddPermissionToRole(model.PERMISSION_MANAGE_EMOJIS.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OTHERS_EMOJIS.Id, model.TEAM_USER_ROLE_ID) + + Client.Logout() + th.LoginBasic2() + ok, resp = Client.DeleteEmoji(newEmoji.Id) + CheckNoError(t, resp) } func TestGetEmoji(t *testing.T) { diff --git a/api4/post.go b/api4/post.go index 189edfc20..b4392a74e 100644 --- a/api4/post.go +++ b/api4/post.go @@ -246,11 +246,24 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToPost(c.Session, c.Params.PostId, model.PERMISSION_DELETE_OTHERS_POSTS) { - c.SetPermissionError(model.PERMISSION_DELETE_OTHERS_POSTS) + post, err := c.App.GetSinglePost(c.Params.PostId) + if err != nil { + c.SetPermissionError(model.PERMISSION_DELETE_POST) return } + if c.Session.UserId == post.UserId { + if !c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_DELETE_POST) { + c.SetPermissionError(model.PERMISSION_DELETE_POST) + return + } + } else { + if !c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_DELETE_OTHERS_POSTS) { + c.SetPermissionError(model.PERMISSION_DELETE_OTHERS_POSTS) + return + } + } + if _, err := c.App.DeletePost(c.Params.PostId); err != nil { c.Err = err return @@ -364,11 +377,19 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToPost(c.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS) + originalPost, err := c.App.GetSinglePost(c.Params.PostId) + if err != nil { + c.SetPermissionError(model.PERMISSION_EDIT_POST) return } + if c.Session.UserId != originalPost.UserId { + if !c.App.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS) + return + } + } + post.Id = c.Params.PostId rpost, err := c.App.UpdatePost(c.App.PostWithProxyRemovedFromImageURLs(post), false) @@ -398,11 +419,19 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToPost(c.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) { - c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS) + originalPost, err := c.App.GetSinglePost(c.Params.PostId) + if err != nil { + c.SetPermissionError(model.PERMISSION_EDIT_POST) return } + if c.Session.UserId != originalPost.UserId { + if !c.App.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS) + return + } + } + patchedPost, err := c.App.PatchPost(c.Params.PostId, c.App.PostPatchWithProxyRemovedFromImageURLs(post)) if err != nil { c.Err = err diff --git a/api4/role.go b/api4/role.go index c4203137b..2c0465891 100644 --- a/api4/role.go +++ b/api4/role.go @@ -100,6 +100,7 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) { model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_OAUTH.Id, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + model.PERMISSION_MANAGE_EMOJIS.Id, } changedPermissions := model.PermissionsChangedByPatch(oldRole, patch) diff --git a/api4/scheme.go b/api4/scheme.go new file mode 100644 index 000000000..5070d1c4a --- /dev/null +++ b/api4/scheme.go @@ -0,0 +1,211 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + + "github.com/mattermost/mattermost-server/model" +) + +func (api *API) InitScheme() { + api.BaseRoutes.Schemes.Handle("", api.ApiSessionRequired(getSchemes)).Methods("GET") + api.BaseRoutes.Schemes.Handle("", api.ApiSessionRequired(createScheme)).Methods("POST") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}", api.ApiSessionRequired(deleteScheme)).Methods("DELETE") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}", api.ApiSessionRequiredTrustRequester(getScheme)).Methods("GET") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}/patch", api.ApiSessionRequired(patchScheme)).Methods("PUT") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}/teams", api.ApiSessionRequiredTrustRequester(getTeamsForScheme)).Methods("GET") + api.BaseRoutes.Schemes.Handle("/{scheme_id:[A-Za-z0-9]+}/channels", api.ApiSessionRequiredTrustRequester(getChannelsForScheme)).Methods("GET") +} + +func createScheme(c *Context, w http.ResponseWriter, r *http.Request) { + scheme := model.SchemeFromJson(r.Body) + if scheme == nil { + c.SetInvalidParam("scheme") + return + } + + if c.App.License() == nil || !*c.App.License().Features.CustomPermissionsSchemes { + c.Err = model.NewAppError("Api4.CreateScheme", "api.scheme.create_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + var err *model.AppError + if scheme, err = c.App.CreateScheme(scheme); err != nil { + c.Err = err + return + } else { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(scheme.ToJson())) + } +} + +func getScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if scheme, err := c.App.GetScheme(c.Params.SchemeId); err != nil { + c.Err = err + return + } else { + w.Write([]byte(scheme.ToJson())) + } +} + +func getSchemes(c *Context, w http.ResponseWriter, r *http.Request) { + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scope := c.Params.Scope + if scope != "" && scope != model.SCHEME_SCOPE_TEAM && scope != model.SCHEME_SCOPE_CHANNEL { + c.SetInvalidParam("scope") + return + } + + if schemes, err := c.App.GetSchemesPage(c.Params.Scope, c.Params.Page, c.Params.PerPage); err != nil { + c.Err = err + return + } else { + w.Write([]byte(model.SchemesToJson(schemes))) + } +} + +func getTeamsForScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scheme, err := c.App.GetScheme(c.Params.SchemeId) + if err != nil { + c.Err = err + return + } + + if scheme.Scope != model.SCHEME_SCOPE_TEAM { + c.Err = model.NewAppError("Api4.GetTeamsForScheme", "api.scheme.get_teams_for_scheme.scope.error", nil, "", http.StatusBadRequest) + return + } + + if teams, err := c.App.GetTeamsForSchemePage(scheme, c.Params.Page, c.Params.PerPage); err != nil { + c.Err = err + return + } else { + w.Write([]byte(model.TeamListToJson(teams))) + } +} + +func getChannelsForScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + scheme, err := c.App.GetScheme(c.Params.SchemeId) + if err != nil { + c.Err = err + return + } + + if scheme.Scope != model.SCHEME_SCOPE_CHANNEL { + c.Err = model.NewAppError("Api4.GetChannelsForScheme", "api.scheme.get_channels_for_scheme.scope.error", nil, "", http.StatusBadRequest) + return + } + + if channels, err := c.App.GetChannelsForSchemePage(scheme, c.Params.Page, c.Params.PerPage); err != nil { + c.Err = err + return + } else { + w.Write([]byte(channels.ToJson())) + } +} + +func patchScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + patch := model.SchemePatchFromJson(r.Body) + if patch == nil { + c.SetInvalidParam("scheme") + return + } + + if c.App.License() == nil || !*c.App.License().Features.CustomPermissionsSchemes { + c.Err = model.NewAppError("Api4.PatchScheme", "api.scheme.patch_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + scheme, err := c.App.GetScheme(c.Params.SchemeId) + if err != nil { + c.Err = err + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if scheme, err = c.App.PatchScheme(scheme, patch); err != nil { + c.Err = err + return + } else { + c.LogAudit("") + w.Write([]byte(scheme.ToJson())) + } +} + +func deleteScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireSchemeId() + if c.Err != nil { + return + } + + if c.App.License() == nil || !*c.App.License().Features.CustomPermissionsSchemes { + c.Err = model.NewAppError("Api4.DeleteScheme", "api.scheme.delete_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if _, err := c.App.DeleteScheme(c.Params.SchemeId); err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} diff --git a/api4/scheme_test.go b/api4/scheme_test.go new file mode 100644 index 000000000..67cfda4fc --- /dev/null +++ b/api4/scheme_test.go @@ -0,0 +1,737 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost-server/model" +) + +func TestCreateScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + // Basic test of creating a team scheme. + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + assert.Equal(t, s1.DisplayName, scheme1.DisplayName) + assert.Equal(t, s1.Name, scheme1.Name) + assert.Equal(t, s1.Description, scheme1.Description) + assert.NotZero(t, s1.CreateAt) + assert.Equal(t, s1.CreateAt, s1.UpdateAt) + assert.Zero(t, s1.DeleteAt) + assert.Equal(t, s1.Scope, scheme1.Scope) + assert.NotZero(t, len(s1.DefaultTeamAdminRole)) + assert.NotZero(t, len(s1.DefaultTeamUserRole)) + assert.NotZero(t, len(s1.DefaultChannelAdminRole)) + assert.NotZero(t, len(s1.DefaultChannelUserRole)) + + // Check the default roles have been created. + _, roleRes1 := th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + _, roleRes2 := th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + _, roleRes3 := th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + _, roleRes4 := th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + // Basic Test of a Channel scheme. + scheme2 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + s2, r2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, r2) + + assert.Equal(t, s2.DisplayName, scheme2.DisplayName) + assert.Equal(t, s2.Name, scheme2.Name) + assert.Equal(t, s2.Description, scheme2.Description) + assert.NotZero(t, s2.CreateAt) + assert.Equal(t, s2.CreateAt, s2.UpdateAt) + assert.Zero(t, s2.DeleteAt) + assert.Equal(t, s2.Scope, scheme2.Scope) + assert.Zero(t, len(s2.DefaultTeamAdminRole)) + assert.Zero(t, len(s2.DefaultTeamUserRole)) + assert.NotZero(t, len(s2.DefaultChannelAdminRole)) + assert.NotZero(t, len(s2.DefaultChannelUserRole)) + + // Check the default roles have been created. + _, roleRes5 := th.SystemAdminClient.GetRole(s2.DefaultChannelAdminRole) + CheckNoError(t, roleRes5) + _, roleRes6 := th.SystemAdminClient.GetRole(s2.DefaultChannelUserRole) + CheckNoError(t, roleRes6) + + // Try and create a scheme with an invalid scope. + scheme3 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.NewId(), + } + + _, r3 := th.SystemAdminClient.CreateScheme(scheme3) + CheckBadRequestStatus(t, r3) + + // Try and create a scheme with an invalid display name. + scheme4 := &model.Scheme{ + DisplayName: strings.Repeat(model.NewId(), 100), + Name: "Name", + Description: model.NewId(), + Scope: model.NewId(), + } + _, r4 := th.SystemAdminClient.CreateScheme(scheme4) + CheckBadRequestStatus(t, r4) + + // Try and create a scheme with an invalid name. + scheme8 := &model.Scheme{ + DisplayName: "DisplayName", + Name: strings.Repeat(model.NewId(), 100), + Description: model.NewId(), + Scope: model.NewId(), + } + _, r8 := th.SystemAdminClient.CreateScheme(scheme8) + CheckBadRequestStatus(t, r8) + + // Try and create a scheme without the appropriate permissions. + scheme5 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + _, r5 := th.Client.CreateScheme(scheme5) + CheckForbiddenStatus(t, r5) + + // Try and create a scheme without a license. + th.App.SetLicense(nil) + scheme6 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + _, r6 := th.SystemAdminClient.CreateScheme(scheme6) + CheckNotImplementedStatus(t, r6) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + th.LoginSystemAdmin() + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + scheme7 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + _, r7 := th.SystemAdminClient.CreateScheme(scheme7) + CheckNotImplementedStatus(t, r7) +} + +func TestGetScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + // Basic test of creating a team scheme. + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + th.App.SetPhase2PermissionsMigrationStatus(true) + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + assert.Equal(t, s1.DisplayName, scheme1.DisplayName) + assert.Equal(t, s1.Name, scheme1.Name) + assert.Equal(t, s1.Description, scheme1.Description) + assert.NotZero(t, s1.CreateAt) + assert.Equal(t, s1.CreateAt, s1.UpdateAt) + assert.Zero(t, s1.DeleteAt) + assert.Equal(t, s1.Scope, scheme1.Scope) + assert.NotZero(t, len(s1.DefaultTeamAdminRole)) + assert.NotZero(t, len(s1.DefaultTeamUserRole)) + assert.NotZero(t, len(s1.DefaultChannelAdminRole)) + assert.NotZero(t, len(s1.DefaultChannelUserRole)) + + s2, r2 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNoError(t, r2) + + assert.Equal(t, s1, s2) + + _, r3 := th.SystemAdminClient.GetScheme(model.NewId()) + CheckNotFoundStatus(t, r3) + + _, r4 := th.SystemAdminClient.GetScheme("12345") + CheckBadRequestStatus(t, r4) + + th.SystemAdminClient.Logout() + _, r5 := th.SystemAdminClient.GetScheme(s1.Id) + CheckUnauthorizedStatus(t, r5) + + th.SystemAdminClient.Login(th.SystemAdminUser.Username, th.SystemAdminUser.Password) + th.App.SetLicense(nil) + _, r6 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNoError(t, r6) + + _, r7 := th.Client.GetScheme(s1.Id) + CheckForbiddenStatus(t, r7) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + _, r8 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNotImplementedStatus(t, r8) +} + +func TestGetSchemes(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + scheme2 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + th.App.SetPhase2PermissionsMigrationStatus(true) + + _, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + _, r2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, r2) + + l3, r3 := th.SystemAdminClient.GetSchemes("", 0, 100) + CheckNoError(t, r3) + + assert.NotZero(t, len(l3)) + + l4, r4 := th.SystemAdminClient.GetSchemes("team", 0, 100) + CheckNoError(t, r4) + + for _, s := range l4 { + assert.Equal(t, "team", s.Scope) + } + + l5, r5 := th.SystemAdminClient.GetSchemes("channel", 0, 100) + CheckNoError(t, r5) + + for _, s := range l5 { + assert.Equal(t, "channel", s.Scope) + } + + _, r6 := th.SystemAdminClient.GetSchemes("asdf", 0, 100) + CheckBadRequestStatus(t, r6) + + th.Client.Logout() + _, r7 := th.Client.GetSchemes("", 0, 100) + CheckUnauthorizedStatus(t, r7) + + th.Client.Login(th.BasicUser.Username, th.BasicUser.Password) + _, r8 := th.Client.GetSchemes("", 0, 100) + CheckForbiddenStatus(t, r8) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + _, r9 := th.SystemAdminClient.GetSchemes("", 0, 100) + CheckNotImplementedStatus(t, r9) +} + +func TestGetTeamsForScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + scheme1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + team1 := &model.Team{ + Name: GenerateTestUsername(), + DisplayName: "A Test Team", + Type: model.TEAM_OPEN, + } + + result1 := <-th.App.Srv.Store.Team().Save(team1) + assert.Nil(t, result1.Err) + team1 = result1.Data.(*model.Team) + + l2, r2 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r2) + assert.Zero(t, len(l2)) + + team1.SchemeId = &scheme1.Id + result2 := <-th.App.Srv.Store.Team().Update(team1) + assert.Nil(t, result2.Err) + team1 = result2.Data.(*model.Team) + + l3, r3 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r3) + assert.Len(t, l3, 1) + assert.Equal(t, team1.Id, l3[0].Id) + + team2 := &model.Team{ + Name: GenerateTestUsername(), + DisplayName: "B Test Team", + Type: model.TEAM_OPEN, + SchemeId: &scheme1.Id, + } + result3 := <-th.App.Srv.Store.Team().Save(team2) + assert.Nil(t, result3.Err) + team2 = result3.Data.(*model.Team) + + l4, r4 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r4) + assert.Len(t, l4, 2) + assert.Equal(t, team1.Id, l4[0].Id) + assert.Equal(t, team2.Id, l4[1].Id) + + l5, r5 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 1, 1) + CheckNoError(t, r5) + assert.Len(t, l5, 1) + assert.Equal(t, team2.Id, l5[0].Id) + + // Check various error cases. + _, ri1 := th.SystemAdminClient.GetTeamsForScheme(model.NewId(), 0, 100) + CheckNotFoundStatus(t, ri1) + + _, ri2 := th.SystemAdminClient.GetTeamsForScheme("", 0, 100) + CheckBadRequestStatus(t, ri2) + + th.Client.Logout() + _, ri3 := th.Client.GetTeamsForScheme(model.NewId(), 0, 100) + CheckUnauthorizedStatus(t, ri3) + + th.Client.Login(th.BasicUser.Username, th.BasicUser.Password) + _, ri4 := th.Client.GetTeamsForScheme(model.NewId(), 0, 100) + CheckForbiddenStatus(t, ri4) + + scheme2 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + scheme2, rs2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, rs2) + + _, ri5 := th.SystemAdminClient.GetTeamsForScheme(scheme2.Id, 0, 100) + CheckBadRequestStatus(t, ri5) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + _, ri6 := th.SystemAdminClient.GetTeamsForScheme(scheme1.Id, 0, 100) + CheckNotImplementedStatus(t, ri6) +} + +func TestGetChannelsForScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + scheme1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + channel1 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "A Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + } + + result1 := <-th.App.Srv.Store.Channel().Save(channel1, 1000000) + assert.Nil(t, result1.Err) + channel1 = result1.Data.(*model.Channel) + + l2, r2 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r2) + assert.Zero(t, len(l2)) + + channel1.SchemeId = &scheme1.Id + result2 := <-th.App.Srv.Store.Channel().Update(channel1) + assert.Nil(t, result2.Err) + channel1 = result2.Data.(*model.Channel) + + l3, r3 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r3) + assert.Len(t, l3, 1) + assert.Equal(t, channel1.Id, l3[0].Id) + + channel2 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "B Name", + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + SchemeId: &scheme1.Id, + } + result3 := <-th.App.Srv.Store.Channel().Save(channel2, 1000000) + assert.Nil(t, result3.Err) + channel2 = result3.Data.(*model.Channel) + + l4, r4 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNoError(t, r4) + assert.Len(t, l4, 2) + assert.Equal(t, channel1.Id, l4[0].Id) + assert.Equal(t, channel2.Id, l4[1].Id) + + l5, r5 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 1, 1) + CheckNoError(t, r5) + assert.Len(t, l5, 1) + assert.Equal(t, channel2.Id, l5[0].Id) + + // Check various error cases. + _, ri1 := th.SystemAdminClient.GetChannelsForScheme(model.NewId(), 0, 100) + CheckNotFoundStatus(t, ri1) + + _, ri2 := th.SystemAdminClient.GetChannelsForScheme("", 0, 100) + CheckBadRequestStatus(t, ri2) + + th.Client.Logout() + _, ri3 := th.Client.GetChannelsForScheme(model.NewId(), 0, 100) + CheckUnauthorizedStatus(t, ri3) + + th.Client.Login(th.BasicUser.Username, th.BasicUser.Password) + _, ri4 := th.Client.GetChannelsForScheme(model.NewId(), 0, 100) + CheckForbiddenStatus(t, ri4) + + scheme2 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + scheme2, rs2 := th.SystemAdminClient.CreateScheme(scheme2) + CheckNoError(t, rs2) + + _, ri5 := th.SystemAdminClient.GetChannelsForScheme(scheme2.Id, 0, 100) + CheckBadRequestStatus(t, ri5) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + _, ri6 := th.SystemAdminClient.GetChannelsForScheme(scheme1.Id, 0, 100) + CheckNotImplementedStatus(t, ri6) +} + +func TestPatchScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + // Basic test of creating a team scheme. + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + assert.Equal(t, s1.DisplayName, scheme1.DisplayName) + assert.Equal(t, s1.Name, scheme1.Name) + assert.Equal(t, s1.Description, scheme1.Description) + assert.NotZero(t, s1.CreateAt) + assert.Equal(t, s1.CreateAt, s1.UpdateAt) + assert.Zero(t, s1.DeleteAt) + assert.Equal(t, s1.Scope, scheme1.Scope) + assert.NotZero(t, len(s1.DefaultTeamAdminRole)) + assert.NotZero(t, len(s1.DefaultTeamUserRole)) + assert.NotZero(t, len(s1.DefaultChannelAdminRole)) + assert.NotZero(t, len(s1.DefaultChannelUserRole)) + + s2, r2 := th.SystemAdminClient.GetScheme(s1.Id) + CheckNoError(t, r2) + + assert.Equal(t, s1, s2) + + // Test with a valid patch. + schemePatch := &model.SchemePatch{ + DisplayName: new(string), + Name: new(string), + Description: new(string), + } + *schemePatch.DisplayName = model.NewId() + *schemePatch.Name = model.NewId() + *schemePatch.Description = model.NewId() + + s3, r3 := th.SystemAdminClient.PatchScheme(s2.Id, schemePatch) + CheckNoError(t, r3) + assert.Equal(t, s3.Id, s2.Id) + assert.Equal(t, s3.DisplayName, *schemePatch.DisplayName) + assert.Equal(t, s3.Name, *schemePatch.Name) + assert.Equal(t, s3.Description, *schemePatch.Description) + + s4, r4 := th.SystemAdminClient.GetScheme(s3.Id) + CheckNoError(t, r4) + assert.Equal(t, s3, s4) + + // Test with a partial patch. + *schemePatch.Name = model.NewId() + *schemePatch.DisplayName = model.NewId() + schemePatch.Description = nil + + s5, r5 := th.SystemAdminClient.PatchScheme(s4.Id, schemePatch) + CheckNoError(t, r5) + assert.Equal(t, s5.Id, s4.Id) + assert.Equal(t, s5.DisplayName, *schemePatch.DisplayName) + assert.Equal(t, s5.Name, *schemePatch.Name) + assert.Equal(t, s5.Description, s4.Description) + + s6, r6 := th.SystemAdminClient.GetScheme(s5.Id) + CheckNoError(t, r6) + assert.Equal(t, s5, s6) + + // Test with invalid patch. + *schemePatch.Name = strings.Repeat(model.NewId(), 20) + _, r7 := th.SystemAdminClient.PatchScheme(s6.Id, schemePatch) + CheckBadRequestStatus(t, r7) + + // Test with unknown ID. + *schemePatch.Name = model.NewId() + _, r8 := th.SystemAdminClient.PatchScheme(model.NewId(), schemePatch) + CheckNotFoundStatus(t, r8) + + // Test with invalid ID. + _, r9 := th.SystemAdminClient.PatchScheme("12345", schemePatch) + CheckBadRequestStatus(t, r9) + + // Test without required permissions. + _, r10 := th.Client.PatchScheme(s6.Id, schemePatch) + CheckForbiddenStatus(t, r10) + + // Test without license. + th.App.SetLicense(nil) + _, r11 := th.SystemAdminClient.PatchScheme(s6.Id, schemePatch) + CheckNotImplementedStatus(t, r11) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + th.LoginSystemAdmin() + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + _, r12 := th.SystemAdminClient.PatchScheme(s6.Id, schemePatch) + CheckNotImplementedStatus(t, r12) +} + +func TestDeleteScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + t.Run("ValidTeamScheme", func(t *testing.T) { + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + // Create a team scheme. + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + // Retrieve the roles and check they are not deleted. + role1, roleRes1 := th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + role2, roleRes2 := th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + role3, roleRes3 := th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 := th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.Zero(t, role1.DeleteAt) + assert.Zero(t, role2.DeleteAt) + assert.Zero(t, role3.DeleteAt) + assert.Zero(t, role4.DeleteAt) + + // Make sure this scheme is in use by a team. + res := <-th.App.Srv.Store.Team().Save(&model.Team{ + Name: model.NewId(), + DisplayName: model.NewId(), + Email: model.NewId() + "@nowhere.com", + Type: model.TEAM_OPEN, + SchemeId: &s1.Id, + }) + assert.Nil(t, res.Err) + team := res.Data.(*model.Team) + + // Delete the Scheme. + _, r3 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNoError(t, r3) + + // Check the roles were deleted. + role1, roleRes1 = th.SystemAdminClient.GetRole(s1.DefaultTeamAdminRole) + CheckNoError(t, roleRes1) + role2, roleRes2 = th.SystemAdminClient.GetRole(s1.DefaultTeamUserRole) + CheckNoError(t, roleRes2) + role3, roleRes3 = th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 = th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.NotZero(t, role1.DeleteAt) + assert.NotZero(t, role2.DeleteAt) + assert.NotZero(t, role3.DeleteAt) + assert.NotZero(t, role4.DeleteAt) + + // Check the team now uses the default scheme + c2, resp := th.SystemAdminClient.GetTeam(team.Id, "") + CheckNoError(t, resp) + assert.Equal(t, "", *c2.SchemeId) + }) + + t.Run("ValidChannelScheme", func(t *testing.T) { + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + // Create a channel scheme. + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + // Retrieve the roles and check they are not deleted. + role3, roleRes3 := th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 := th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.Zero(t, role3.DeleteAt) + assert.Zero(t, role4.DeleteAt) + + // Make sure this scheme is in use by a team. + res := <-th.App.Srv.Store.Channel().Save(&model.Channel{ + TeamId: model.NewId(), + DisplayName: model.NewId(), + Name: model.NewId(), + Type: model.CHANNEL_OPEN, + SchemeId: &s1.Id, + }, -1) + assert.Nil(t, res.Err) + channel := res.Data.(*model.Channel) + + // Delete the Scheme. + _, r3 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNoError(t, r3) + + // Check the roles were deleted. + role3, roleRes3 = th.SystemAdminClient.GetRole(s1.DefaultChannelAdminRole) + CheckNoError(t, roleRes3) + role4, roleRes4 = th.SystemAdminClient.GetRole(s1.DefaultChannelUserRole) + CheckNoError(t, roleRes4) + + assert.NotZero(t, role3.DeleteAt) + assert.NotZero(t, role4.DeleteAt) + + // Check the channel now uses the default scheme + c2, resp := th.SystemAdminClient.GetChannelByName(channel.Name, channel.TeamId, "") + CheckNoError(t, resp) + assert.Equal(t, "", *c2.SchemeId) + }) + + t.Run("FailureCases", func(t *testing.T) { + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + scheme1 := &model.Scheme{ + DisplayName: model.NewId(), + Name: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + + s1, r1 := th.SystemAdminClient.CreateScheme(scheme1) + CheckNoError(t, r1) + + // Test with unknown ID. + _, r2 := th.SystemAdminClient.DeleteScheme(model.NewId()) + CheckNotFoundStatus(t, r2) + + // Test with invalid ID. + _, r3 := th.SystemAdminClient.DeleteScheme("12345") + CheckBadRequestStatus(t, r3) + + // Test without required permissions. + _, r4 := th.Client.DeleteScheme(s1.Id) + CheckForbiddenStatus(t, r4) + + // Test without license. + th.App.SetLicense(nil) + _, r5 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNotImplementedStatus(t, r5) + + th.App.SetPhase2PermissionsMigrationStatus(false) + + th.App.SetLicense(model.NewTestLicense("custom_permissions_schemes")) + + _, r6 := th.SystemAdminClient.DeleteScheme(s1.Id) + CheckNotImplementedStatus(t, r6) + }) +} diff --git a/api4/team.go b/api4/team.go index 44771ae60..74b385122 100644 --- a/api4/team.go +++ b/api4/team.go @@ -20,6 +20,7 @@ const ( func (api *API) InitTeam() { api.BaseRoutes.Teams.Handle("", api.ApiSessionRequired(createTeam)).Methods("POST") api.BaseRoutes.Teams.Handle("", api.ApiSessionRequired(getAllTeams)).Methods("GET") + api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/scheme", api.ApiSessionRequired(updateTeamScheme)).Methods("PUT") api.BaseRoutes.Teams.Handle("/search", api.ApiSessionRequired(searchTeams)).Methods("POST") api.BaseRoutes.TeamsForUser.Handle("", api.ApiSessionRequired(getTeamsForUser)).Methods("GET") api.BaseRoutes.TeamsForUser.Handle("/unread", api.ApiSessionRequired(getTeamsUnreadForUser)).Methods("GET") @@ -833,3 +834,55 @@ func removeTeamIcon(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("") ReturnStatusOK(w) } + +func updateTeamScheme(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireTeamId() + if c.Err != nil { + return + } + + schemeID := model.SchemeIDFromJson(r.Body) + if schemeID == nil || (len(*schemeID) != 26 && *schemeID != "") { + c.SetInvalidParam("scheme_id") + return + } + + if c.App.License() == nil { + c.Err = model.NewAppError("Api4.UpdateTeamScheme", "api.team.update_team_scheme.license.error", nil, "", http.StatusNotImplemented) + return + } + + if !c.App.SessionHasPermissionToTeam(c.Session, c.Params.TeamId, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if *schemeID != "" { + scheme, err := c.App.GetScheme(*schemeID) + if err != nil { + c.Err = err + return + } + + if scheme.Scope != model.SCHEME_SCOPE_TEAM { + c.Err = model.NewAppError("Api4.UpdateTeamScheme", "api.team.update_team_scheme.scheme_scope.error", nil, "", http.StatusBadRequest) + return + } + } + + team, err := c.App.GetTeam(c.Params.TeamId) + if err != nil { + c.Err = err + return + } + + team.SchemeId = schemeID + + _, err = c.App.UpdateTeamScheme(team) + if err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} diff --git a/api4/team_test.go b/api4/team_test.go index bf67d8fde..079ba37ec 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -1675,7 +1675,7 @@ func TestUpdateTeamMemberRoles(t *testing.T) { // user 1 (team admin) tries to demote system admin (not member of a team) _, resp = Client.UpdateTeamMemberRoles(th.BasicTeam.Id, th.SystemAdminUser.Id, TEAM_MEMBER) - CheckBadRequestStatus(t, resp) + CheckNotFoundStatus(t, resp) // user 1 (team admin) demotes system admin (member of a team) th.LinkUserToTeam(th.SystemAdminUser, th.BasicTeam) @@ -1701,7 +1701,7 @@ func TestUpdateTeamMemberRoles(t *testing.T) { // user 1 (team admin) tries to promote a random user _, resp = Client.UpdateTeamMemberRoles(th.BasicTeam.Id, model.NewId(), TEAM_ADMIN) - CheckBadRequestStatus(t, resp) + CheckNotFoundStatus(t, resp) // user 1 (team admin) tries to promote invalid team permission _, resp = Client.UpdateTeamMemberRoles(th.BasicTeam.Id, th.BasicUser.Id, "junk") @@ -2055,3 +2055,75 @@ func TestRemoveTeamIcon(t *testing.T) { _, resp = Client.RemoveTeamIcon(team.Id) CheckForbiddenStatus(t, resp) } + +func TestUpdateTeamScheme(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + th.App.SetLicense(model.NewTestLicense("")) + + th.App.SetPhase2PermissionsMigrationStatus(true) + + team := &model.Team{ + DisplayName: "Name", + Description: "Some description", + CompanyName: "Some company name", + AllowOpenInvite: false, + InviteId: "inviteid0", + Name: "z-z-" + model.NewId() + "a", + Email: "success+" + model.NewId() + "@simulator.amazonses.com", + Type: model.TEAM_OPEN, + } + team, _ = th.SystemAdminClient.CreateTeam(team) + + teamScheme := &model.Scheme{ + DisplayName: "DisplayName", + Name: model.NewId(), + Description: "Some description", + Scope: model.SCHEME_SCOPE_TEAM, + } + teamScheme, _ = th.SystemAdminClient.CreateScheme(teamScheme) + channelScheme := &model.Scheme{ + DisplayName: "DisplayName", + Name: model.NewId(), + Description: "Some description", + Scope: model.SCHEME_SCOPE_CHANNEL, + } + channelScheme, _ = th.SystemAdminClient.CreateScheme(channelScheme) + + // Test the setup/base case. + _, resp := th.SystemAdminClient.UpdateTeamScheme(team.Id, teamScheme.Id) + CheckNoError(t, resp) + + // Test the return to default scheme + _, resp = th.SystemAdminClient.UpdateTeamScheme(team.Id, "") + CheckNoError(t, resp) + + // Test various invalid team and scheme id combinations. + _, resp = th.SystemAdminClient.UpdateTeamScheme(team.Id, "x") + CheckBadRequestStatus(t, resp) + _, resp = th.SystemAdminClient.UpdateTeamScheme("x", teamScheme.Id) + CheckBadRequestStatus(t, resp) + _, resp = th.SystemAdminClient.UpdateTeamScheme("x", "x") + CheckBadRequestStatus(t, resp) + + // Test that permissions are required. + _, resp = th.Client.UpdateTeamScheme(team.Id, teamScheme.Id) + CheckForbiddenStatus(t, resp) + + // Test that a license is requried. + th.App.SetLicense(nil) + _, resp = th.SystemAdminClient.UpdateTeamScheme(team.Id, teamScheme.Id) + CheckNotImplementedStatus(t, resp) + th.App.SetLicense(model.NewTestLicense("")) + + // Test an invalid scheme scope. + _, resp = th.SystemAdminClient.UpdateTeamScheme(team.Id, channelScheme.Id) + fmt.Printf("resp: %+v\n", resp) + CheckBadRequestStatus(t, resp) + + // Test that an unauthenticated user gets rejected. + th.SystemAdminClient.Logout() + _, resp = th.SystemAdminClient.UpdateTeamScheme(team.Id, teamScheme.Id) + CheckUnauthorizedStatus(t, resp) +} |