From e1cd64613591cf5a990442a69ebf188258bd0cb5 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Tue, 6 Feb 2018 15:34:08 +0000 Subject: XYZ-37: Advanced Permissions Phase 1 Backend. (#8159) * XYZ-13: Update Permission and Role structs to new design. * XYZ-10: Role store. * XYZ-9/XYZ-44: Roles API endpoints and WebSocket message. * XYZ-8: Switch server permissions checks to store backed roles. * XYZ-58: Proper validation of roles where required. * XYZ-11/XYZ-55: Migration to store backed roles from policy config. * XYZ-37: Update unit tests to work with database roles. * XYZ-56: Remove the "guest" role. * Changes to SetDefaultRolesFromConfig. * Short-circuit the store if nothing has changed. * Address first round of review comments. * Address second round of review comments. --- api/apitestlib.go | 112 ++++ api/channel_test.go | 728 +++------------------ api/oauth_test.go | 44 +- api/post_test.go | 59 +- api/team.go | 12 +- api/team_test.go | 100 +-- api/webhook_test.go | 143 ++-- api4/api.go | 5 + api4/apitestlib.go | 112 ++++ api4/channel.go | 1 + api4/channel_test.go | 451 ++----------- api4/context.go | 23 + api4/oauth_test.go | 100 +-- api4/params.go | 10 + api4/post_test.go | 7 - api4/role.go | 100 +++ api4/role_test.go | 184 ++++++ api4/team_test.go | 127 +--- api4/webhook_test.go | 72 +- app/app.go | 60 +- app/app_test.go | 340 +++++++++- app/apptestlib.go | 52 +- app/authorization.go | 44 +- app/authorization_test.go | 6 +- app/channel.go | 4 + app/import_test.go | 16 - app/post.go | 2 +- app/role.go | 88 ++- app/team.go | 4 + app/user.go | 4 + cmd/platform/server.go | 2 + i18n/en.json | 28 + model/authorization.go | 522 --------------- model/client4.go | 46 ++ model/cluster_message.go | 1 + model/permission.go | 418 ++++++++++++ model/permission_test.go | 18 + model/role.go | 310 +++++++++ model/user.go | 7 +- model/user_test.go | 4 +- model/websocket_message.go | 1 + store/layered_store.go | 34 + store/layered_store_supplier.go | 6 + store/local_cache_supplier.go | 8 +- store/local_cache_supplier_roles.go | 68 ++ store/redis_supplier.go | 20 + store/sqlstore/role_store_test.go | 14 + store/sqlstore/role_supplier.go | 163 +++++ store/sqlstore/store.go | 1 + store/sqlstore/supplier.go | 6 + store/store.go | 8 + store/storetest/mocks/LayeredStoreDatabaseLayer.go | 108 +++ store/storetest/mocks/LayeredStoreSupplier.go | 92 +++ store/storetest/mocks/RoleStore.go | 78 +++ store/storetest/mocks/SqlStore.go | 16 + store/storetest/mocks/Store.go | 16 + store/storetest/role_store.go | 244 +++++++ store/storetest/store.go | 3 + utils/authorization.go | 18 +- web/web_test.go | 2 + 60 files changed, 3260 insertions(+), 2012 deletions(-) create mode 100644 api4/role.go create mode 100644 api4/role_test.go delete mode 100644 model/authorization.go create mode 100644 model/permission.go create mode 100644 model/permission_test.go create mode 100644 model/role.go create mode 100644 store/local_cache_supplier_roles.go create mode 100644 store/sqlstore/role_store_test.go create mode 100644 store/sqlstore/role_supplier.go create mode 100644 store/storetest/mocks/RoleStore.go create mode 100644 store/storetest/role_store.go diff --git a/api/apitestlib.go b/api/apitestlib.go index 8d7f54902..f53e7b237 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -111,6 +111,7 @@ func setupTestHelper(enterprise bool) *TestHelper { Init(th.App, th.App.Srv.Router) wsapi.Init(th.App, th.App.Srv.WebSocketRouter) th.App.Srv.Store.MarkSystemRanUnitTests() + th.App.DoAdvancedPermissionsMigration() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) @@ -389,3 +390,114 @@ func (me *TestHelper) TearDown() { panic(err) } } + +func (me *TestHelper) SaveDefaultRolePermissions() map[string][]string { + utils.DisableDebugLogForTest() + + results := make(map[string][]string) + + for _, roleName := range []string{ + "system_user", + "system_admin", + "team_user", + "team_admin", + "channel_user", + "channel_admin", + } { + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + results[roleName] = role.Permissions + } + + utils.EnableDebugLogForTest() + return results +} + +func (me *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) { + utils.DisableDebugLogForTest() + + for roleName, permissions := range data { + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") { + continue + } + + role.Permissions = permissions + + _, err2 := me.App.UpdateRole(role) + if err2 != nil { + utils.EnableDebugLogForTest() + panic(err2) + } + } + + utils.EnableDebugLogForTest() +} + +func (me *TestHelper) RemovePermissionFromRole(permission string, roleName string) { + utils.DisableDebugLogForTest() + + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + var newPermissions []string + for _, p := range role.Permissions { + if p != permission { + newPermissions = append(newPermissions, p) + } + } + + if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") { + utils.EnableDebugLogForTest() + return + } + + role.Permissions = newPermissions + + _, err2 := me.App.UpdateRole(role) + if err2 != nil { + utils.EnableDebugLogForTest() + panic(err2) + } + + utils.EnableDebugLogForTest() +} + +func (me *TestHelper) AddPermissionToRole(permission string, roleName string) { + utils.DisableDebugLogForTest() + + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + for _, existingPermission := range role.Permissions { + if existingPermission == permission { + utils.EnableDebugLogForTest() + return + } + } + + role.Permissions = append(role.Permissions, permission) + + _, err2 := me.App.UpdateRole(role) + if err2 != nil { + utils.EnableDebugLogForTest() + panic(err2) + } + + utils.EnableDebugLogForTest() +} diff --git a/api/channel_test.go b/api/channel_test.go index 5533105c3..37dde24bd 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -12,7 +12,6 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateChannel(t *testing.T) { @@ -20,7 +19,6 @@ func TestCreateChannel(t *testing.T) { defer th.TearDown() Client := th.BasicClient - SystemAdminClient := th.SystemAdminClient team := th.BasicTeam th.LoginBasic2() team2 := th.CreateTeam(th.BasicClient) @@ -97,23 +95,14 @@ func TestCreateChannel(t *testing.T) { t.Fatal("Should have errored out on direct channel type") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + + th.AddPermissionToRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) channel2 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel3 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -124,13 +113,10 @@ func TestCreateChannel(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_ADMIN_ROLE_ID) th.LoginBasic2() channel2.Name = "zz" + model.NewId() + "a" @@ -153,50 +139,6 @@ func TestCreateChannel(t *testing.T) { if _, err := Client.CreateChannel(channel3); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - channel2.Name = "zz" + model.NewId() + "a" - channel3.Name = "zz" + model.NewId() + "a" - if _, err := Client.CreateChannel(channel2); err == nil { - t.Fatal("should have errored not system admin") - } - if _, err := Client.CreateChannel(channel3); err == nil { - t.Fatal("should have errored not system admin") - } - - th.LinkUserToTeam(th.SystemAdminUser, team) - - if _, err := SystemAdminClient.CreateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := SystemAdminClient.CreateChannel(channel3); err != nil { - t.Fatal(err) - } - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - - channel4 := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} - channel5 := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - if _, err := Client.CreateChannel(&channel4); err != nil { - t.Fatal("should have succeeded") - } - if _, err := Client.CreateChannel(&channel5); err != nil { - t.Fatal("should have succeeded") - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() } func TestCreateDirectChannel(t *testing.T) { @@ -367,23 +309,17 @@ func TestUpdateChannel(t *testing.T) { t.Fatal("should have failed - channel deleted") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) + + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) channel2 := th.CreateChannel(Client, team) channel3 := th.CreatePrivateChannel(Client, team) @@ -402,16 +338,11 @@ func TestUpdateChannel(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -423,17 +354,6 @@ func TestUpdateChannel(t *testing.T) { t.Fatal("should have errored not channel admin") } - th.UpdateUserToTeamAdmin(th.BasicUser, team) - th.App.InvalidateAllCaches() - if _, err := Client.UpdateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannel(channel3); err != nil { - t.Fatal(err) - } - th.UpdateUserToNonTeamAdmin(th.BasicUser, team) - th.App.InvalidateAllCaches() - th.MakeUserChannelAdmin(th.BasicUser, channel2) th.MakeUserChannelAdmin(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -444,75 +364,6 @@ func TestUpdateChannel(t *testing.T) { if _, err := Client.UpdateChannel(channel3); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannel(channel2); err == nil { - t.Fatal("should have errored not team admin") - } - if _, err := Client.UpdateChannel(channel3); err == nil { - t.Fatal("should have errored not team admin") - } - - th.UpdateUserToTeamAdmin(th.BasicUser, team) - Client.Logout() - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannel(channel3); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannel(channel2); err == nil { - t.Fatal("should have errored not system admin") - } - if _, err := Client.UpdateChannel(channel3); err == nil { - t.Fatal("should have errored not system admin") - } - - th.LoginSystemAdmin() - - if _, err := Client.UpdateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannel(channel3); err != nil { - t.Fatal(err) - } - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannel(channel2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannel(channel3); err != nil { - t.Fatal(err) - } } func TestUpdateChannelDisplayName(t *testing.T) { @@ -548,7 +399,6 @@ func TestUpdateChannelHeader(t *testing.T) { defer th.TearDown() Client := th.BasicClient - SystemAdminClient := th.SystemAdminClient team := th.BasicTeam channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -618,23 +468,17 @@ func TestUpdateChannelHeader(t *testing.T) { t.Fatal("should have errored non-channel member trying to update header") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) + + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) th.LoginBasic() channel2 := th.CreateChannel(Client, team) @@ -657,13 +501,11 @@ func TestUpdateChannelHeader(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -685,72 +527,6 @@ func TestUpdateChannelHeader(t *testing.T) { if _, err := Client.UpdateChannelHeader(data3); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannelHeader(data2); err == nil { - t.Fatal("should have errored not team admin") - } - if _, err := Client.UpdateChannelHeader(data3); err == nil { - t.Fatal("should have errored not team admin") - } - - th.UpdateUserToTeamAdmin(th.BasicUser, team) - Client.Logout() - th.LoginBasic() - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateChannelHeader(data2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannelHeader(data3); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannelHeader(data2); err == nil { - t.Fatal("should have errored not system admin") - } - if _, err := Client.UpdateChannelHeader(data3); err == nil { - t.Fatal("should have errored not system admin") - } - - th.LinkUserToTeam(th.SystemAdminUser, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.SystemAdminUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.SystemAdminUser.Id)) - th.LoginSystemAdmin() - - if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { - t.Fatal(err) - } - if _, err := SystemAdminClient.UpdateChannelHeader(data3); err != nil { - t.Fatal(err) - } - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { - t.Fatal(err) - } - if _, err := SystemAdminClient.UpdateChannelHeader(data3); err != nil { - t.Fatal(err) - } } func TestUpdateChannelPurpose(t *testing.T) { @@ -758,7 +534,6 @@ func TestUpdateChannelPurpose(t *testing.T) { defer th.TearDown() Client := th.BasicClient - SystemAdminClient := th.SystemAdminClient team := th.BasicTeam channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -814,23 +589,17 @@ func TestUpdateChannelPurpose(t *testing.T) { t.Fatal("should have errored non-channel member trying to update purpose") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.TEAM_USER_ROLE_ID) + + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) th.LoginBasic() channel2 := th.CreateChannel(Client, team) @@ -853,13 +622,11 @@ func TestUpdateChannelPurpose(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -881,71 +648,6 @@ func TestUpdateChannelPurpose(t *testing.T) { if _, err := Client.UpdateChannelPurpose(data3); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannelPurpose(data2); err == nil { - t.Fatal("should have errored not team admin") - } - if _, err := Client.UpdateChannelPurpose(data3); err == nil { - t.Fatal("should have errored not team admin") - } - - th.UpdateUserToTeamAdmin(th.BasicUser, team) - Client.Logout() - th.LoginBasic() - Client.SetTeamId(team.Id) - - if _, err := Client.UpdateChannelPurpose(data2); err != nil { - t.Fatal(err) - } - if _, err := Client.UpdateChannelPurpose(data3); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.UpdateChannelPurpose(data2); err == nil { - t.Fatal("should have errored not system admin") - } - if _, err := Client.UpdateChannelPurpose(data3); err == nil { - t.Fatal("should have errored not system admin") - } - - th.LinkUserToTeam(th.SystemAdminUser, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.SystemAdminUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.SystemAdminUser.Id)) - th.LoginSystemAdmin() - - if _, err := SystemAdminClient.UpdateChannelPurpose(data2); err != nil { - t.Fatal(err) - } - if _, err := SystemAdminClient.UpdateChannelPurpose(data3); err != nil { - t.Fatal(err) - } - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { - t.Fatal(err) - } - if _, err := SystemAdminClient.UpdateChannelHeader(data3); err != nil { - t.Fatal(err) - } } func TestGetChannel(t *testing.T) { @@ -1400,23 +1102,14 @@ func TestDeleteChannel(t *testing.T) { t.Fatal("should have failed - channel already deleted") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) th.LoginSystemAdmin() th.LinkUserToTeam(th.BasicUser, team) @@ -1438,16 +1131,10 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) th.LoginSystemAdmin() @@ -1478,78 +1165,6 @@ func TestDeleteChannel(t *testing.T) { th.LoginSystemAdmin() - channel2 = th.CreateChannel(Client, team) - channel3 = th.CreatePrivateChannel(Client, team) - - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - th.UpdateUserToTeamAdmin(th.BasicUser, team) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - th.App.InvalidateAllCaches() - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } - - th.UpdateUserToNonTeamAdmin(th.BasicUser, team) - th.App.InvalidateAllCaches() - - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - th.LoginSystemAdmin() - - channel2 = th.CreateChannel(Client, team) - channel3 = th.CreatePrivateChannel(Client, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.DeleteChannel(channel2.Id); err == nil { - t.Fatal("should have errored not team admin") - } - if _, err := Client.DeleteChannel(channel3.Id); err == nil { - t.Fatal("should have errored not team admin") - } - - th.UpdateUserToTeamAdmin(th.BasicUser, team) - Client.Logout() - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - Client.SetTeamId(team.Id) - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } - - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - th.LoginSystemAdmin() - channel2 = th.CreateChannel(Client, team) channel3 = th.CreatePrivateChannel(Client, team) Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) @@ -1576,29 +1191,6 @@ func TestDeleteChannel(t *testing.T) { if _, err := Client.DeleteChannel(channel3.Id); err != nil { t.Fatal(err) } - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - - channel2 = th.CreateChannel(Client, team) - channel3 = th.CreatePrivateChannel(Client, team) - Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) - Client.Must(Client.AddChannelMember(channel3.Id, th.BasicUser.Id)) - - Client.Login(th.BasicUser.Email, th.BasicUser.Password) - - if _, err := Client.DeleteChannel(channel2.Id); err != nil { - t.Fatal(err) - } - if _, err := Client.DeleteChannel(channel3.Id); err != nil { - t.Fatal(err) - } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() } func TestGetChannelStats(t *testing.T) { @@ -1674,38 +1266,14 @@ func TestAddChannelMember(t *testing.T) { t.Fatal("Should have errored, user not on team") } - // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - channel3 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel3 = Client.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel3.Id, user1.Id)) - if _, err := Client.AddChannelMember(channel3.Id, user2.Id); err != nil { - t.Fatal(err) - } - - // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) // Check that a regular channel user can add other users. channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -1715,14 +1283,10 @@ func TestAddChannelMember(t *testing.T) { t.Fatal(err) } - // Test with CHANNEL_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) @@ -1733,61 +1297,10 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.AddChannelMember(channel5.Id, user2.Id); err != nil { t.Fatal(err) } - - // Test with TEAM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - channel6 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel6 = Client.Must(th.SystemAdminClient.CreateChannel(channel6)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel6.Id, user1.Id)) - if _, err := Client.AddChannelMember(channel6.Id, user2.Id); err == nil { - t.Fatal("Should have failed due to permissions") - } - - th.UpdateUserToTeamAdmin(user1, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.AddChannelMember(channel6.Id, user2.Id); err != nil { - t.Fatal(err) - } - - // Test with SYSTEM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - channel7 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel7 = Client.Must(th.SystemAdminClient.CreateChannel(channel7)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel7.Id, user1.Id)) - if _, err := Client.AddChannelMember(channel7.Id, user2.Id); err == nil { - t.Fatal("Should have failed due to permissions") - } - - if _, err := th.SystemAdminClient.AddChannelMember(channel7.Id, user2.Id); err != nil { - t.Fatal(err) - } } func TestRemoveChannelMember(t *testing.T) { @@ -1859,39 +1372,14 @@ func TestRemoveChannelMember(t *testing.T) { th.LoginBasic() - // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - channel3 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel3 = Client.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel3.Id, user1.Id)) - Client.Must(th.SystemAdminClient.AddChannelMember(channel3.Id, user2.Id)) - if _, err := Client.RemoveChannelMember(channel3.Id, user2.Id); err != nil { - t.Fatal(err) - } - - // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) // Check that a regular channel user can remove other users. channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -1902,14 +1390,10 @@ func TestRemoveChannelMember(t *testing.T) { t.Fatal(err) } - // Test with CHANNEL_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) @@ -1921,62 +1405,10 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() if _, err := Client.RemoveChannelMember(channel5.Id, user2.Id); err != nil { t.Fatal(err) } - - // Test with TEAM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - channel6 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel6 = Client.Must(th.SystemAdminClient.CreateChannel(channel6)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel6.Id, user1.Id)) - Client.Must(th.SystemAdminClient.AddChannelMember(channel6.Id, user2.Id)) - if _, err := Client.RemoveChannelMember(channel6.Id, user2.Id); err == nil { - t.Fatal("Should have failed due to permissions") - } - - th.UpdateUserToTeamAdmin(user1, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.RemoveChannelMember(channel6.Id, user2.Id); err != nil { - t.Fatal(err) - } - - // Test with SYSTEM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - channel7 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} - channel7 = Client.Must(th.SystemAdminClient.CreateChannel(channel7)).Data.(*model.Channel) - Client.Must(th.SystemAdminClient.AddChannelMember(channel7.Id, user1.Id)) - Client.Must(th.SystemAdminClient.AddChannelMember(channel7.Id, user2.Id)) - if _, err := Client.RemoveChannelMember(channel7.Id, user2.Id); err == nil { - t.Fatal("Should have failed due to permissions") - } - - if _, err := th.SystemAdminClient.RemoveChannelMember(channel7.Id, user2.Id); err != nil { - t.Fatal(err) - } } func TestUpdateNotifyProps(t *testing.T) { diff --git a/api/oauth_test.go b/api/oauth_test.go index ec1e557da..2663c784b 100644 --- a/api/oauth_test.go +++ b/api/oauth_test.go @@ -88,7 +88,12 @@ func TestOAuthRegisterApp(t *testing.T) { t.Fatal("should have failed. not enough permissions") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.LoginBasic() @@ -210,7 +215,12 @@ func TestOAuthGetAppsByUser(t *testing.T) { t.Fatal("Should have failed.") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) if result, err := Client.GetOAuthAppsByUser(); err != nil { t.Fatal(err) @@ -250,7 +260,9 @@ func TestOAuthGetAppsByUser(t *testing.T) { user := &model.User{Email: strings.ToLower("test+"+model.NewId()) + "@simulator.amazonses.com", Password: "hello1", Username: "n" + model.NewId(), EmailVerified: true} ruser := Client.Must(AdminClient.CreateUser(user, "")).Data.(*model.User) - th.App.UpdateUserRoles(ruser.Id, "", false) + if _, err := th.App.UpdateUserRoles(ruser.Id, "", false); err != nil { + t.Fatal(err) + } Client.Logout() Client.Login(user.Email, user.Password) @@ -437,7 +449,13 @@ func TestOAuthDeleteApp(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -492,13 +510,17 @@ func TestOAuthAccessToken(t *testing.T) { Client := th.BasicClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} oauthApp = Client.Must(Client.RegisterApp(oauthApp)).Data.(*model.OAuthApp) @@ -739,7 +761,13 @@ func TestOAuthComplete(t *testing.T) { // We are going to use mattermost as the provider emulating gitlab th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oauthApp := &model.OAuthApp{ Name: "TestApp5" + model.NewId(), diff --git a/api/post_test.go b/api/post_test.go index 299fdf046..fe0c2a6b7 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -167,10 +167,8 @@ func TestCreatePost(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = disableTownSquareReadOnly }) utils.SetIsLicensed(isLicensed) utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetDefaultRolesBasedOnConfig() utils.SetIsLicensed(true) utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() @@ -965,9 +963,6 @@ func TestDeletePosts(t *testing.T) { channel1 := th.BasicChannel team1 := th.BasicTeam - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_ALL }) - th.App.SetDefaultRolesBasedOnConfig() - time.Sleep(10 * time.Millisecond) post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post) @@ -1020,16 +1015,11 @@ func TestDeletePosts(t *testing.T) { t.Fatal(err) } - // Test licensed policy controls for delete post - isLicensed := utils.IsLicensed() - license := utils.License() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) @@ -1041,10 +1031,8 @@ func TestDeletePosts(t *testing.T) { SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post4b.Id)) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + th.RemovePermissionFromRole(model.PERMISSION_DELETE_POST.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_POST.Id, model.TEAM_ADMIN_ROLE_ID) th.LoginBasic() @@ -1065,43 +1053,6 @@ func TestDeletePosts(t *testing.T) { Client.Must(Client.DeletePost(channel1.Id, post5a.Id)) SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post5b.Id)) - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - th.LoginBasic() - - time.Sleep(10 * time.Millisecond) - post6a := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post6a = Client.Must(Client.CreatePost(post6a)).Data.(*model.Post) - - if _, err := Client.DeletePost(channel1.Id, post6a.Id); err == nil { - t.Fatal(err) - } - - th.LoginBasic2() - - if _, err := Client.DeletePost(channel1.Id, post6a.Id); err == nil { - t.Fatal(err) - } - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - - time.Sleep(10 * time.Millisecond) - post7 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} - post7 = Client.Must(Client.CreatePost(post7)).Data.(*model.Post) - - if _, err := Client.DeletePost(channel1.Id, post7.Id); err != nil { - t.Fatal(err) - } - - SystemAdminClient.Must(SystemAdminClient.DeletePost(channel1.Id, post6a.Id)) - } func TestEmailMention(t *testing.T) { diff --git a/api/team.go b/api/team.go index 48377f970..e89f368ec 100644 --- a/api/team.go +++ b/api/team.go @@ -13,7 +13,6 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitTeam() { @@ -117,15 +116,8 @@ func getAll(c *Context, w http.ResponseWriter, r *http.Request) { func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { invites := model.InvitesFromJson(r.Body) - if utils.IsLicensed() && !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_INVITE_USER) { - errorId := "" - if *c.App.Config().TeamSettings.RestrictTeamInvite == model.PERMISSIONS_SYSTEM_ADMIN { - errorId = "api.team.invite_members.restricted_system_admin.app_error" - } else if *c.App.Config().TeamSettings.RestrictTeamInvite == model.PERMISSIONS_TEAM_ADMIN { - errorId = "api.team.invite_members.restricted_team_admin.app_error" - } - - c.Err = model.NewAppError("inviteMembers", errorId, nil, "", http.StatusForbidden) + if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_INVITE_USER) { + c.SetPermissionError(model.PERMISSION_INVITE_USER) return } diff --git a/api/team_test.go b/api/team_test.go index b1c892544..3db454b62 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -139,32 +139,17 @@ func TestAddUserToTeam(t *testing.T) { t.Fatal(err) } - // Restore config/license at end of test case. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() // Set the config so that only team admins can add a user to a team. - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() - - // Test without the EE license to see that the permission restriction is ignored. - user3 := th.CreateUser(th.BasicClient) - if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user3.Id); err != nil { - t.Fatal(err) - } - - // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) // Check that a regular user can't add someone to the team. user4 := th.CreateUser(th.BasicClient) @@ -175,32 +160,17 @@ func TestAddUserToTeam(t *testing.T) { // Should work as team admin. th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + // Change permission level to team user + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) user5 := th.CreateUser(th.BasicClient) if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user5.Id); err != nil { t.Fatal(err) } - - // Change permission level to System Admin - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() - - // Should not work as team admin. - user6 := th.CreateUser(th.BasicClient) - if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user6.Id); err == nil { - t.Fatal("should have failed due to permissions error") - } - - // Should work as system admin. - user7 := th.CreateUser(th.BasicClient) - if _, err := th.SystemAdminClient.AddUserToTeam(th.BasicTeam.Id, user7.Id); err != nil { - t.Fatal(err) - } } func TestRemoveUserFromTeam(t *testing.T) { @@ -534,7 +504,6 @@ func TestInviteMembers(t *testing.T) { defer th.TearDown() Client := th.BasicClient - SystemAdminClient := th.SystemAdminClient team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) @@ -565,33 +534,21 @@ func TestInviteMembers(t *testing.T) { t.Fatal("Should have errored out on no invites to send") } - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() + + // Set the config so that only team admins can add a user to a team. + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) th.LoginBasic2() th.LinkUserToTeam(th.BasicUser2, team) - if _, err := Client.InviteMembers(invites); err != nil { - t.Fatal(err) - } - - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - if _, err := Client.InviteMembers(invites); err == nil { t.Fatal("should have errored not team admin and licensed") } @@ -604,19 +561,6 @@ func TestInviteMembers(t *testing.T) { if _, err := Client.InviteMembers(invites); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() - - if _, err := Client.InviteMembers(invites); err == nil { - t.Fatal("should have errored not system admin and licensed") - } - - th.LinkUserToTeam(th.SystemAdminUser, team) - - if _, err := SystemAdminClient.InviteMembers(invites); err != nil { - t.Fatal(err) - } } func TestUpdateTeamDisplayName(t *testing.T) { diff --git a/api/webhook_test.go b/api/webhook_test.go index b5a836603..b6b754ad3 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -25,8 +25,16 @@ func TestCreateIncomingHook(t *testing.T) { user2 := th.CreateUser(Client) th.LinkUserToTeam(user2, team) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.IncomingWebhook{ChannelId: channel1.Id} @@ -92,8 +100,8 @@ func TestCreateIncomingHook(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) if _, err := Client.CreateIncomingWebhook(hook); err != nil { t.Fatal(err) @@ -132,8 +140,12 @@ func TestUpdateIncomingHook(t *testing.T) { th.UpdateUserToTeamAdmin(user3, team2) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) hook := createIncomingWebhook(channel1.Id, Client, t) @@ -216,8 +228,9 @@ func TestUpdateIncomingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) t.Run("OnlyAdminIntegrationsDisabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) t.Run("UpdateHookOfSameUser", func(t *testing.T) { sameUserHook := &model.IncomingWebhook{ChannelId: channel1.Id, UserId: user2.Id} @@ -239,8 +252,8 @@ func TestUpdateIncomingHook(t *testing.T) { }) }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) Client.Logout() th.UpdateUserToTeamAdmin(user2, team) @@ -323,8 +336,15 @@ func TestListIncomingHooks(t *testing.T) { th.LinkUserToTeam(user2, team) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook1 := &model.IncomingWebhook{ChannelId: channel1.Id} hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook) @@ -350,8 +370,8 @@ func TestListIncomingHooks(t *testing.T) { t.Fatal("should have errored - not system/team admin") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) if _, err := Client.ListIncomingWebhooks(); err != nil { t.Fatal(err) @@ -375,8 +395,15 @@ func TestDeleteIncomingHook(t *testing.T) { th.LinkUserToTeam(user2, team) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.IncomingWebhook{ChannelId: channel1.Id} hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook) @@ -409,8 +436,8 @@ func TestDeleteIncomingHook(t *testing.T) { t.Fatal("should have failed - not system/team admin") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil { t.Fatal("should have failed - not creator or team admin") @@ -446,8 +473,15 @@ func TestCreateOutgoingHook(t *testing.T) { th.LinkUserToTeam(user3, team2) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} @@ -517,8 +551,8 @@ func TestCreateOutgoingHook(t *testing.T) { t.Fatal("should have failed - not system/team admin") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) if _, err := Client.CreateOutgoingWebhook(hook); err != nil { t.Fatal(err) @@ -550,8 +584,15 @@ func TestListOutgoingHooks(t *testing.T) { th.LinkUserToTeam(user2, team) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook1 = Client.Must(Client.CreateOutgoingWebhook(hook1)).Data.(*model.OutgoingWebhook) @@ -577,8 +618,8 @@ func TestListOutgoingHooks(t *testing.T) { t.Fatal("should have failed - not system/team admin") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) if _, err := Client.ListOutgoingWebhooks(); err != nil { t.Fatal(err) @@ -608,8 +649,15 @@ func TestUpdateOutgoingHook(t *testing.T) { th.LinkUserToTeam(user3, team2) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"cats"}, Client, t) createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"dogs"}, Client, t) @@ -682,16 +730,17 @@ func TestUpdateOutgoingHook(t *testing.T) { t.Fatal("should have failed - user does not have permissions to manage webhooks") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) + hook2 := createOutgoingWebhook(channel1.Id, []string{"http://nowhereelse.com"}, []string{"dogs"}, Client, t) if _, err := Client.UpdateOutgoingWebhook(hook2); err != nil { t.Fatal("update webhook failed when admin only integrations is turned off") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) Client.Logout() th.LinkUserToTeam(user3, team) @@ -778,8 +827,15 @@ func TestDeleteOutgoingHook(t *testing.T) { th.LinkUserToTeam(user2, team) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -812,8 +868,8 @@ func TestDeleteOutgoingHook(t *testing.T) { t.Fatal("should have failed - not system/team admin") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil { t.Fatal("should have failed - not creator or team admin") @@ -847,8 +903,15 @@ func TestRegenOutgoingHookToken(t *testing.T) { th.LinkUserToTeam(user3, team2) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -882,8 +945,8 @@ func TestRegenOutgoingHookToken(t *testing.T) { t.Fatal("should have failed - not system/team admin") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -969,10 +1032,8 @@ func TestIncomingWebhooks(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = disableTownSquareReadOnly }) utils.SetIsLicensed(isLicensed) utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetDefaultRolesBasedOnConfig() utils.SetIsLicensed(true) utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() diff --git a/api4/api.go b/api4/api.go index 580bd8c58..583a6041b 100644 --- a/api4/api.go +++ b/api4/api.go @@ -96,6 +96,8 @@ type Routes struct { Reactions *mux.Router // 'api/v4/reactions' + Roles *mux.Router // 'api/v4/roles' + Emojis *mux.Router // 'api/v4/emoji' Emoji *mux.Router // 'api/v4/emoji/{emoji_id:[A-Za-z0-9]+}' EmojiByName *mux.Router // 'api/v4/emoji/name/{emoji_name:[A-Za-z0-9_-\.]+}' @@ -194,6 +196,8 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.BaseRoutes.OpenGraph = api.BaseRoutes.ApiRoot.PathPrefix("/opengraph").Subrouter() + api.BaseRoutes.Roles = api.BaseRoutes.ApiRoot.PathPrefix("/roles").Subrouter() + api.InitUser() api.InitTeam() api.InitChannel() @@ -219,6 +223,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.InitWebrtc() api.InitOpenGraph() api.InitPlugin() + api.InitRole() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index a7e64ae84..bdca072c5 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -118,6 +118,7 @@ func setupTestHelper(enterprise bool) *TestHelper { Init(th.App, th.App.Srv.Router, true) wsapi.Init(th.App, th.App.Srv.WebSocketRouter) th.App.Srv.Store.MarkSystemRanUnitTests() + th.App.DoAdvancedPermissionsMigration() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) @@ -799,3 +800,114 @@ func (me *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Tea } utils.EnableDebugLogForTest() } + +func (me *TestHelper) SaveDefaultRolePermissions() map[string][]string { + utils.DisableDebugLogForTest() + + results := make(map[string][]string) + + for _, roleName := range []string{ + "system_user", + "system_admin", + "team_user", + "team_admin", + "channel_user", + "channel_admin", + } { + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + results[roleName] = role.Permissions + } + + utils.EnableDebugLogForTest() + return results +} + +func (me *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) { + utils.DisableDebugLogForTest() + + for roleName, permissions := range data { + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") { + continue + } + + role.Permissions = permissions + + _, err2 := me.App.UpdateRole(role) + if err2 != nil { + utils.EnableDebugLogForTest() + panic(err2) + } + } + + utils.EnableDebugLogForTest() +} + +func (me *TestHelper) RemovePermissionFromRole(permission string, roleName string) { + utils.DisableDebugLogForTest() + + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + var newPermissions []string + for _, p := range role.Permissions { + if p != permission { + newPermissions = append(newPermissions, p) + } + } + + if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") { + utils.EnableDebugLogForTest() + return + } + + role.Permissions = newPermissions + + _, err2 := me.App.UpdateRole(role) + if err2 != nil { + utils.EnableDebugLogForTest() + panic(err2) + } + + utils.EnableDebugLogForTest() +} + +func (me *TestHelper) AddPermissionToRole(permission string, roleName string) { + utils.DisableDebugLogForTest() + + role, err1 := me.App.GetRoleByName(roleName) + if err1 != nil { + utils.EnableDebugLogForTest() + panic(err1) + } + + for _, existingPermission := range role.Permissions { + if existingPermission == permission { + utils.EnableDebugLogForTest() + return + } + } + + role.Permissions = append(role.Permissions, permission) + + _, err2 := me.App.UpdateRole(role) + if err2 != nil { + utils.EnableDebugLogForTest() + panic(err2) + } + + utils.EnableDebugLogForTest() +} diff --git a/api4/channel.go b/api4/channel.go index 9801c9dc0..d46c3d559 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -7,6 +7,7 @@ import ( "net/http" l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/model" ) diff --git a/api4/channel_test.go b/api4/channel_test.go index 724b0d84b..4deceb4c4 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -14,7 +14,6 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateChannel(t *testing.T) { @@ -79,26 +78,16 @@ func TestCreateChannel(t *testing.T) { _, resp = Client.CreateChannel(private) CheckForbiddenStatus(t, resp) - th.LoginBasic() - - // Check permissions with policy config changes - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelCreation - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelCreation + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + th.AddPermissionToRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + + th.LoginBasic() channel.Name = GenerateTestChannelName() _, resp = Client.CreateChannel(channel) @@ -108,13 +97,10 @@ func TestCreateChannel(t *testing.T) { _, resp = Client.CreateChannel(private) CheckNoError(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + th.AddPermissionToRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) _, resp = Client.CreateChannel(channel) CheckForbiddenStatus(t, resp) @@ -140,51 +126,7 @@ func TestCreateChannel(t *testing.T) { _, resp = th.SystemAdminClient.CreateChannel(private) CheckNoError(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - th.LoginBasic() - - _, resp = Client.CreateChannel(channel) - CheckForbiddenStatus(t, resp) - - _, resp = Client.CreateChannel(private) - CheckForbiddenStatus(t, resp) - - th.LoginTeamAdmin() - - _, resp = Client.CreateChannel(channel) - CheckForbiddenStatus(t, resp) - - _, resp = Client.CreateChannel(private) - CheckForbiddenStatus(t, resp) - - channel.Name = GenerateTestChannelName() - _, resp = th.SystemAdminClient.CreateChannel(channel) - CheckNoError(t, resp) - - private.Name = GenerateTestChannelName() - _, resp = th.SystemAdminClient.CreateChannel(private) - CheckNoError(t, resp) - - // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() - - channel.Name = GenerateTestChannelName() - _, resp = Client.CreateChannel(channel) - CheckNoError(t, resp) - - private.Name = GenerateTestChannelName() - _, resp = Client.CreateChannel(private) - CheckNoError(t, resp) - + // Test posting Garbage if r, err := Client.DoApiPost("/channels", "garbage"); err == nil { t.Fatal("should have errored") } else { @@ -887,23 +829,16 @@ func TestDeleteChannel(t *testing.T) { th.InitBasic().InitSystemAdmin() - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) Client = th.Client team = th.BasicTeam @@ -924,13 +859,11 @@ func TestDeleteChannel(t *testing.T) { _, resp = Client.DeleteChannel(privateChannel7.Id) CheckNoError(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() + // Restrict permissions to Channel Admins + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -957,129 +890,9 @@ func TestDeleteChannel(t *testing.T) { _, resp = Client.DeleteChannel(privateChannel7.Id) CheckNoError(t, resp) - // // channels created by SystemAdmin - publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) - privateChannel7 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) - th.App.AddUserToChannel(user, publicChannel6) - th.App.AddUserToChannel(user, privateChannel7) - th.App.AddUserToChannel(user2, privateChannel7) - - // successful delete by team admin - th.UpdateUserToTeamAdmin(user, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckNoError(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckNoError(t, resp) - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - th.UpdateUserToNonTeamAdmin(user, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - - // channels created by SystemAdmin - publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) - privateChannel7 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) - th.App.AddUserToChannel(user, publicChannel6) - th.App.AddUserToChannel(user, privateChannel7) - th.App.AddUserToChannel(user2, privateChannel7) - - // cannot delete by user - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckForbiddenStatus(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckForbiddenStatus(t, resp) - - // // cannot delete by channel admin - th.MakeUserChannelAdmin(user, publicChannel6) - th.MakeUserChannelAdmin(user, privateChannel7) - sqlstore.ClearChannelCaches() - - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckForbiddenStatus(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckForbiddenStatus(t, resp) - - // successful delete by team admin - th.UpdateUserToTeamAdmin(th.BasicUser, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckNoError(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckNoError(t, resp) - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - // channels created by SystemAdmin - publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) - privateChannel7 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) - th.App.AddUserToChannel(user, publicChannel6) - th.App.AddUserToChannel(user, privateChannel7) - th.App.AddUserToChannel(user2, privateChannel7) - - // cannot delete by user - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckForbiddenStatus(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckForbiddenStatus(t, resp) - - // cannot delete by channel admin - th.MakeUserChannelAdmin(user, publicChannel6) - th.MakeUserChannelAdmin(user, privateChannel7) - sqlstore.ClearChannelCaches() - - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckForbiddenStatus(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckForbiddenStatus(t, resp) - - // cannot delete by team admin - th.UpdateUserToTeamAdmin(th.BasicUser, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - - _, resp = Client.DeleteChannel(publicChannel6.Id) - CheckForbiddenStatus(t, resp) - - _, resp = Client.DeleteChannel(privateChannel7.Id) - CheckForbiddenStatus(t, resp) - - // successful delete by SystemAdmin - _, resp = th.SystemAdminClient.DeleteChannel(publicChannel6.Id) - CheckNoError(t, resp) - - _, resp = th.SystemAdminClient.DeleteChannel(privateChannel7.Id) - CheckNoError(t, resp) + // Make sure team admins don't have permission to delete channels. + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) // last member of a public channel should have required permission to delete publicChannel6 = th.CreateChannelWithClient(th.Client, model.CHANNEL_OPEN) @@ -1822,42 +1635,13 @@ func TestAddChannelMember(t *testing.T) { _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user2.Id) CheckNoError(t, resp) - // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - Client.Login(user2.Username, user2.Password) - privateChannel = th.CreatePrivateChannel() - _, resp = Client.AddChannelMember(privateChannel.Id, user.Id) - CheckNoError(t, resp) - Client.Logout() - Client.Login(user.Username, user.Password) - _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) - CheckNoError(t, resp) - Client.Logout() - - // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) // Check that a regular channel user can add other users. Client.Login(user2.Username, user2.Password) @@ -1871,14 +1655,9 @@ func TestAddChannelMember(t *testing.T) { CheckNoError(t, resp) Client.Logout() - // Test with CHANNEL_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + // Restrict the permission for adding users to Channel Admins + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1893,70 +1672,11 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user, privateChannel) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - Client.Login(user.Username, user.Password) - _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) - CheckNoError(t, resp) - Client.Logout() - - // Test with TEAM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - Client.Login(user2.Username, user2.Password) - privateChannel = th.CreatePrivateChannel() - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user.Id) - CheckNoError(t, resp) - Client.Logout() - - Client.Login(user.Username, user.Password) - _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) - CheckForbiddenStatus(t, resp) - Client.Logout() - - th.UpdateUserToTeamAdmin(user, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user.Username, user.Password) _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) CheckNoError(t, resp) Client.Logout() - - // Test with SYSTEM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - Client.Login(user2.Username, user2.Password) - privateChannel = th.CreatePrivateChannel() - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user.Id) - CheckNoError(t, resp) - Client.Logout() - - Client.Login(user.Username, user.Password) - _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) - CheckForbiddenStatus(t, resp) - Client.Logout() - - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user3.Id) - CheckNoError(t, resp) } func TestRemoveChannelMember(t *testing.T) { @@ -2018,43 +1738,16 @@ func TestRemoveChannelMember(t *testing.T) { th.UpdateUserToNonTeamAdmin(user1, team) th.App.InvalidateAllCaches() - // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.SetDefaultRolesBasedOnConfig() - - privateChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) - CheckNoError(t, resp) - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user2.Id) - CheckNoError(t, resp) - _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) - CheckNoError(t, resp) - - // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) // Check that a regular channel user can remove other users. - privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) + privateChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) CheckNoError(t, resp) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user2.Id) @@ -2063,14 +1756,9 @@ func TestRemoveChannelMember(t *testing.T) { _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) CheckNoError(t, resp) - // Test with CHANNEL_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + // Restrict the permission for adding users to Channel Admins + th.AddPermissionToRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.CHANNEL_USER_ROLE_ID) privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2083,58 +1771,7 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, privateChannel) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - - _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) - CheckNoError(t, resp) - - // Test with TEAM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) - CheckNoError(t, resp) - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user2.Id) - CheckNoError(t, resp) - - _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) - CheckForbiddenStatus(t, resp) - - th.UpdateUserToTeamAdmin(user1, team) - th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) CheckNoError(t, resp) - - // Test with SYSTEM_ADMIN level permission. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN - }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() - - privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) - CheckNoError(t, resp) - _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user2.Id) - CheckNoError(t, resp) - - _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) - CheckForbiddenStatus(t, resp) - - _, resp = th.SystemAdminClient.RemoveUserFromChannel(privateChannel.Id, user2.Id) - CheckNoError(t, resp) } diff --git a/api4/context.go b/api4/context.go index b10ea7a9b..cdb9f83db 100644 --- a/api4/context.go +++ b/api4/context.go @@ -641,3 +641,26 @@ func (c *Context) RequireActionId() *Context { } return c } + +func (c *Context) RequireRoleId() *Context { + if c.Err != nil { + return c + } + + if len(c.Params.RoleId) != 26 { + c.SetInvalidUrlParam("role_id") + } + return c +} + +func (c *Context) RequireRoleName() *Context { + if c.Err != nil { + return c + } + + if !model.IsValidRoleName(c.Params.RoleName) { + c.SetInvalidUrlParam("role_name") + } + + return c +} diff --git a/api4/oauth_test.go b/api4/oauth_test.go index 8dd602456..0959442f0 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -19,13 +19,15 @@ func TestCreateOAuthApp(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true} @@ -41,13 +43,15 @@ func TestCreateOAuthApp(t *testing.T) { t.Fatal("trusted did no match") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + _, resp = Client.CreateOAuthApp(oapp) CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + rapp, resp = Client.CreateOAuthApp(oapp) CheckNoError(t, resp) CheckCreatedStatus(t, resp) @@ -87,13 +91,15 @@ func TestUpdateOAuthApp(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{ Name: "oapp", @@ -171,8 +177,9 @@ func TestUpdateOAuthApp(t *testing.T) { th.LoginBasic() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + _, resp = Client.UpdateOAuthApp(oapp) CheckForbiddenStatus(t, resp) @@ -181,6 +188,7 @@ func TestUpdateOAuthApp(t *testing.T) { CheckNotFoundStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) + _, resp = AdminClient.UpdateOAuthApp(oapp) CheckNotImplementedStatus(t, resp) @@ -200,14 +208,15 @@ func TestGetOAuthApps(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -250,8 +259,8 @@ func TestGetOAuthApps(t *testing.T) { t.Fatal("wrong apps returned") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) _, resp = Client.GetOAuthApps(0, 1000) CheckForbiddenStatus(t, resp) @@ -273,14 +282,15 @@ func TestGetOAuthApp(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -319,8 +329,8 @@ func TestGetOAuthApp(t *testing.T) { _, resp = Client.GetOAuthApp(rapp.Id) CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) _, resp = Client.GetOAuthApp(rapp2.Id) CheckForbiddenStatus(t, resp) @@ -348,14 +358,15 @@ func TestGetOAuthAppInfo(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -394,8 +405,8 @@ func TestGetOAuthAppInfo(t *testing.T) { _, resp = Client.GetOAuthAppInfo(rapp.Id) CheckNoError(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) _, resp = Client.GetOAuthAppInfo(rapp2.Id) CheckNoError(t, resp) @@ -423,14 +434,15 @@ func TestDeleteOAuthApp(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -464,8 +476,9 @@ func TestDeleteOAuthApp(t *testing.T) { _, resp = Client.DeleteOAuthApp(rapp2.Id) CheckNoError(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + _, resp = Client.DeleteOAuthApp(rapp.Id) CheckForbiddenStatus(t, resp) @@ -491,14 +504,15 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { AdminClient := th.SystemAdminClient enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -536,8 +550,9 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { _, resp = Client.RegenerateOAuthAppSecret(rapp2.Id) CheckNoError(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + _, resp = Client.RegenerateOAuthAppSecret(rapp.Id) CheckForbiddenStatus(t, resp) @@ -627,7 +642,6 @@ func TestAuthorizeOAuthApp(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} diff --git a/api4/params.go b/api4/params.go index 30638578b..357036e80 100644 --- a/api4/params.go +++ b/api4/params.go @@ -44,6 +44,8 @@ type ApiParams struct { JobId string JobType string ActionId string + RoleId string + RoleName string Page int PerPage int LogsPerPage int @@ -151,6 +153,14 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams { params.ActionId = val } + if val, ok := props["role_id"]; ok { + params.RoleId = val + } + + if val, ok := props["role_name"]; ok { + params.RoleName = val + } + if val, err := strconv.Atoi(r.URL.Query().Get("page")); err != nil || val < 0 { params.Page = PAGE_DEFAULT } else { diff --git a/api4/post_test.go b/api4/post_test.go index 6f770b70a..de627d8bc 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -136,14 +136,12 @@ func testCreatePostWithOutgoingHook( defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks }) - th.App.SetDefaultRolesBasedOnConfig() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections }) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" }) @@ -496,14 +494,11 @@ func TestUpdatePost(t *testing.T) { utils.SetIsLicensed(isLicensed) utils.SetLicense(license) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) - th.App.SetDefaultRolesBasedOnConfig() }() utils.SetIsLicensed(true) utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - th.App.SetDefaultRolesBasedOnConfig() post := &model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a"} rpost, resp := Client.CreatePost(post) @@ -581,14 +576,12 @@ func TestPatchPost(t *testing.T) { utils.SetIsLicensed(isLicensed) utils.SetLicense(license) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) - th.App.SetDefaultRolesBasedOnConfig() }() utils.SetIsLicensed(true) utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - th.App.SetDefaultRolesBasedOnConfig() post := &model.Post{ ChannelId: channel.Id, diff --git a/api4/role.go b/api4/role.go new file mode 100644 index 000000000..a401a8034 --- /dev/null +++ b/api4/role.go @@ -0,0 +1,100 @@ +// 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) InitRole() { + api.BaseRoutes.Roles.Handle("/{role_id:[A-Za-z0-9]+}", api.ApiSessionRequiredTrustRequester(getRole)).Methods("GET") + api.BaseRoutes.Roles.Handle("/name/{role_name:[a-z0-9_]+}", api.ApiSessionRequiredTrustRequester(getRoleByName)).Methods("GET") + api.BaseRoutes.Roles.Handle("/names", api.ApiSessionRequiredTrustRequester(getRolesByNames)).Methods("POST") + api.BaseRoutes.Roles.Handle("/{role_id:[A-Za-z0-9]+}/patch", api.ApiSessionRequired(patchRole)).Methods("PUT") +} + +func getRole(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireRoleId() + if c.Err != nil { + return + } + + if role, err := c.App.GetRole(c.Params.RoleId); err != nil { + c.Err = err + return + } else { + w.Write([]byte(role.ToJson())) + } +} + +func getRoleByName(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireRoleName() + if c.Err != nil { + return + } + + if role, err := c.App.GetRoleByName(c.Params.RoleName); err != nil { + c.Err = err + return + } else { + w.Write([]byte(role.ToJson())) + } +} + +func getRolesByNames(c *Context, w http.ResponseWriter, r *http.Request) { + rolenames := model.ArrayFromJson(r.Body) + + if len(rolenames) == 0 { + c.SetInvalidParam("rolenames") + return + } + + for _, rolename := range rolenames { + if !model.IsValidRoleName(rolename) { + c.SetInvalidParam("rolename") + return + } + } + + if roles, err := c.App.GetRolesByNames(rolenames); err != nil { + c.Err = err + return + } else { + w.Write([]byte(model.RoleListToJson(roles))) + } +} + +func patchRole(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireRoleId() + if c.Err != nil { + return + } + + patch := model.RolePatchFromJson(r.Body) + if patch == nil { + c.SetInvalidParam("role") + return + } + + oldRole, err := c.App.GetRole(c.Params.RoleId) + 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 role, err := c.App.PatchRole(oldRole, patch); err != nil { + c.Err = err + return + } else { + c.LogAudit("") + w.Write([]byte(role.ToJson())) + } +} diff --git a/api4/role_test.go b/api4/role_test.go new file mode 100644 index 000000000..64b8303e2 --- /dev/null +++ b/api4/role_test.go @@ -0,0 +1,184 @@ +// 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 TestGetRole(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + role := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{"manage_system", "create_public_channel"}, + SchemeManaged: true, + } + + res1 := <-th.App.Srv.Store.Role().Save(role) + assert.Nil(t, res1.Err) + role = res1.Data.(*model.Role) + defer th.App.Srv.Store.Job().Delete(role.Id) + + received, resp := th.Client.GetRole(role.Id) + CheckNoError(t, resp) + + assert.Equal(t, received.Id, role.Id) + assert.Equal(t, received.Name, role.Name) + assert.Equal(t, received.DisplayName, role.DisplayName) + assert.Equal(t, received.Description, role.Description) + assert.EqualValues(t, received.Permissions, role.Permissions) + assert.Equal(t, received.SchemeManaged, role.SchemeManaged) + + _, resp = th.SystemAdminClient.GetRole("1234") + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.GetRole(model.NewId()) + CheckNotFoundStatus(t, resp) +} + +func TestGetRoleByName(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + role := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{"manage_system", "create_public_channel"}, + SchemeManaged: true, + } + + res1 := <-th.App.Srv.Store.Role().Save(role) + assert.Nil(t, res1.Err) + role = res1.Data.(*model.Role) + defer th.App.Srv.Store.Job().Delete(role.Id) + + received, resp := th.Client.GetRoleByName(role.Name) + CheckNoError(t, resp) + + assert.Equal(t, received.Id, role.Id) + assert.Equal(t, received.Name, role.Name) + assert.Equal(t, received.DisplayName, role.DisplayName) + assert.Equal(t, received.Description, role.Description) + assert.EqualValues(t, received.Permissions, role.Permissions) + assert.Equal(t, received.SchemeManaged, role.SchemeManaged) + + _, resp = th.SystemAdminClient.GetRoleByName(strings.Repeat("abcdefghij", 10)) + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.GetRoleByName(model.NewId()) + CheckNotFoundStatus(t, resp) +} + +func TestGetRolesByNames(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + role1 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{"manage_system", "create_public_channel"}, + SchemeManaged: true, + } + role2 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{"manage_system", "delete_private_channel"}, + SchemeManaged: true, + } + role3 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{"manage_system", "manage_public_channel_properties"}, + SchemeManaged: true, + } + + res1 := <-th.App.Srv.Store.Role().Save(role1) + assert.Nil(t, res1.Err) + role1 = res1.Data.(*model.Role) + defer th.App.Srv.Store.Job().Delete(role1.Id) + + res2 := <-th.App.Srv.Store.Role().Save(role2) + assert.Nil(t, res2.Err) + role2 = res2.Data.(*model.Role) + defer th.App.Srv.Store.Job().Delete(role2.Id) + + res3 := <-th.App.Srv.Store.Role().Save(role3) + assert.Nil(t, res3.Err) + role3 = res3.Data.(*model.Role) + defer th.App.Srv.Store.Job().Delete(role3.Id) + + // Check all three roles can be found. + received, resp := th.Client.GetRolesByNames([]string{role1.Name, role2.Name, role3.Name}) + CheckNoError(t, resp) + + assert.Contains(t, received, role1) + assert.Contains(t, received, role2) + assert.Contains(t, received, role3) + + // Check a list of invalid roles. + // TODO: Confirm whether no error for invalid role names is intended. + received, resp = th.Client.GetRolesByNames([]string{model.NewId(), model.NewId()}) + CheckNoError(t, resp) + + _, resp = th.SystemAdminClient.GetRolesByNames([]string{}) + CheckBadRequestStatus(t, resp) +} + +func TestPatchRole(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + role := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{"manage_system", "create_public_channel"}, + SchemeManaged: true, + } + + res1 := <-th.App.Srv.Store.Role().Save(role) + assert.Nil(t, res1.Err) + role = res1.Data.(*model.Role) + defer th.App.Srv.Store.Job().Delete(role.Id) + + patch := &model.RolePatch{ + Permissions: &[]string{"manage_system", "delete_public_channel"}, + } + + received, resp := th.SystemAdminClient.PatchRole(role.Id, patch) + CheckNoError(t, resp) + + assert.Equal(t, received.Id, role.Id) + assert.Equal(t, received.Name, role.Name) + assert.Equal(t, received.DisplayName, role.DisplayName) + assert.Equal(t, received.Description, role.Description) + assert.EqualValues(t, received.Permissions, []string{"manage_system", "delete_public_channel"}) + assert.Equal(t, received.SchemeManaged, role.SchemeManaged) + + // Check a no-op patch succeeds. + received, resp = th.SystemAdminClient.PatchRole(role.Id, patch) + CheckNoError(t, resp) + + received, resp = th.SystemAdminClient.PatchRole("junk", patch) + CheckBadRequestStatus(t, resp) + + received, resp = th.Client.PatchRole(model.NewId(), patch) + CheckNotFoundStatus(t, resp) + + received, resp = th.Client.PatchRole(role.Id, patch) + CheckForbiddenStatus(t, resp) +} diff --git a/api4/team_test.go b/api4/team_test.go index a8696a30b..272b7372e 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -67,14 +67,14 @@ func TestCreateTeam(t *testing.T) { _, resp = Client.CreateTeam(rteam) CheckUnauthorizedStatus(t, resp) - // Update permission - enableTeamCreation := th.App.Config().TeamSettings.EnableTeamCreation + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = enableTeamCreation }) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = false }) - th.App.SetDefaultRolesBasedOnConfig() + + th.RemovePermissionFromRole(model.PERMISSION_CREATE_TEAM.Id, model.SYSTEM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_CREATE_TEAM.Id, model.SYSTEM_ADMIN_ROLE_ID) th.LoginBasic() _, resp = Client.CreateTeam(team) @@ -1292,31 +1292,18 @@ func TestAddTeamMember(t *testing.T) { Client.Logout() - // Check effects of config and license changes. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() // Set the config so that only team admins can add a user to a team. - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() - th.LoginBasic() - - // Test without the EE license to see that the permission restriction is ignored. - _, resp = Client.AddTeamMember(team.Id, otherUser.Id) - CheckNoError(t, resp) + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) - // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1326,50 +1313,26 @@ func TestAddTeamMember(t *testing.T) { // Update user to team admin th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Should work as a team admin. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) CheckNoError(t, resp) - // Change permission level to System Admin - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() + // Change permission level to team user + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) - // Should not work as team admin. - _, resp = Client.AddTeamMember(team.Id, otherUser.Id) - CheckForbiddenStatus(t, resp) - - // Should work as system admin. - _, resp = th.SystemAdminClient.AddTeamMember(team.Id, otherUser.Id) - CheckNoError(t, resp) - - // Change permission level to All th.UpdateUserToNonTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Should work as a regular user. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) CheckNoError(t, resp) - // Reset config and license. - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - th.LoginBasic() - // by hash and data Client.Login(otherUser.Email, otherUser.Password) @@ -1506,31 +1469,18 @@ func TestAddTeamMembers(t *testing.T) { Client.Logout() - // Check effects of config and license changes. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() + th.RestoreDefaultRolePermissions(defaultRolePermissions) }() // Set the config so that only team admins can add a user to a team. - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() - th.LoginBasic() - - // Test without the EE license to see that the permission restriction is ignored. - _, resp = Client.AddTeamMembers(team.Id, userList) - CheckNoError(t, resp) + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) - // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1540,37 +1490,20 @@ func TestAddTeamMembers(t *testing.T) { // Update user to team admin th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Should work as a team admin. _, resp = Client.AddTeamMembers(team.Id, userList) CheckNoError(t, resp) - // Change permission level to System Admin - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() - - // Should not work as team admin. - _, resp = Client.AddTeamMembers(team.Id, userList) - CheckForbiddenStatus(t, resp) - - // Should work as system admin. - _, resp = th.SystemAdminClient.AddTeamMembers(team.Id, userList) - CheckNoError(t, resp) + // Change permission level to team user + th.AddPermissionToRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_INVITE_USER.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_ADD_USER_TO_TEAM.Id, model.TEAM_ADMIN_ROLE_ID) - // Change permission level to All th.UpdateUserToNonTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Should work as a regular user. diff --git a/api4/webhook_test.go b/api4/webhook_test.go index 724fd0ea4..0a295b4b2 100644 --- a/api4/webhook_test.go +++ b/api4/webhook_test.go @@ -19,10 +19,16 @@ func TestCreateIncomingWebhook(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = true }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) + hook := &model.IncomingWebhook{ChannelId: th.BasicChannel.Id} rhook, resp := th.SystemAdminClient.CreateIncomingWebhook(hook) @@ -53,7 +59,7 @@ func TestCreateIncomingWebhook(t *testing.T) { _, resp = Client.CreateIncomingWebhook(hook) CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) _, resp = Client.CreateIncomingWebhook(hook) CheckNoError(t, resp) @@ -75,7 +81,13 @@ func TestGetIncomingWebhooks(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.IncomingWebhook{ChannelId: th.BasicChannel.Id} rhook, resp := th.SystemAdminClient.CreateIncomingWebhook(hook) @@ -126,7 +138,7 @@ func TestGetIncomingWebhooks(t *testing.T) { _, resp = Client.GetIncomingWebhooks(0, 1000, "") CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) _, resp = Client.GetIncomingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "") CheckNoError(t, resp) @@ -148,7 +160,6 @@ func TestGetIncomingWebhook(t *testing.T) { Client := th.SystemAdminClient th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) var resp *model.Response var rhook *model.IncomingWebhook @@ -188,7 +199,6 @@ func TestDeleteIncomingWebhook(t *testing.T) { Client := th.SystemAdminClient th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) var resp *model.Response var rhook *model.IncomingWebhook @@ -240,7 +250,13 @@ func TestCreateOutgoingWebhook(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.OutgoingWebhook{ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}} @@ -268,7 +284,7 @@ func TestCreateOutgoingWebhook(t *testing.T) { _, resp = Client.CreateOutgoingWebhook(hook) CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) _, resp = Client.CreateOutgoingWebhook(hook) CheckNoError(t, resp) @@ -284,7 +300,12 @@ func TestGetOutgoingWebhooks(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook := &model.OutgoingWebhook{ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}} rhook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook) @@ -352,7 +373,7 @@ func TestGetOutgoingWebhooks(t *testing.T) { _, resp = Client.GetOutgoingWebhooks(0, 1000, "") CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) _, resp = Client.GetOutgoingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "") CheckNoError(t, resp) @@ -380,7 +401,6 @@ func TestGetOutgoingWebhook(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) hook := &model.OutgoingWebhook{ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}} @@ -411,7 +431,13 @@ func TestUpdateIncomingHook(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook1 := &model.IncomingWebhook{ChannelId: th.BasicChannel.Id} @@ -547,10 +573,11 @@ func TestUpdateIncomingHook(t *testing.T) { CheckForbiddenStatus(t, resp) }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) t.Run("OnlyAdminIntegrationsDisabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) t.Run("UpdateHookOfSameUser", func(t *testing.T) { sameUserHook := &model.IncomingWebhook{ChannelId: th.BasicChannel.Id, UserId: th.BasicUser2.Id} @@ -568,7 +595,8 @@ func TestUpdateIncomingHook(t *testing.T) { }) }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) Client.Logout() th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) @@ -623,7 +651,6 @@ func TestRegenOutgoingHookToken(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) hook := &model.OutgoingWebhook{ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}} rhook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook) @@ -656,7 +683,12 @@ func TestUpdateOutgoingHook(t *testing.T) { Client := th.Client th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) createdHook := &model.OutgoingWebhook{ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}, TriggerWords: []string{"cats"}} @@ -729,7 +761,7 @@ func TestUpdateOutgoingHook(t *testing.T) { CheckForbiddenStatus(t, resp) }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) hook2 := &model.OutgoingWebhook{ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}, TriggerWords: []string{"rats2"}} @@ -739,7 +771,8 @@ func TestUpdateOutgoingHook(t *testing.T) { _, resp = Client.UpdateOutgoingWebhook(createdHook2) CheckForbiddenStatus(t, resp) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID) Client.Logout() th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) @@ -813,7 +846,6 @@ func TestDeleteOutgoingHook(t *testing.T) { Client := th.SystemAdminClient th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) var resp *model.Response var rhook *model.OutgoingWebhook diff --git a/app/app.go b/app/app.go index 1e46d29d0..4cc9ff7df 100644 --- a/app/app.go +++ b/app/app.go @@ -7,6 +7,7 @@ import ( "html/template" "net" "net/http" + "reflect" "strings" "sync" "sync/atomic" @@ -25,6 +26,8 @@ import ( "github.com/mattermost/mattermost-server/utils" ) +const ADVANCED_PERMISSIONS_MIGRATION_KEY = "AdvancedPermissionsMigrationComplete" + type App struct { goroutineCount int32 goroutineExitSignal chan struct{} @@ -62,7 +65,6 @@ type App struct { htmlTemplateWatcher *utils.HTMLTemplateWatcher sessionCache *utils.Cache - roles map[string]*model.Role configListenerId string licenseListenerId string disableConfigWatch bool @@ -120,7 +122,6 @@ func New(options ...Option) (*App, error) { }) app.licenseListenerId = utils.AddLicenseListener(app.configOrLicenseListener) app.regenerateClientConfig() - app.SetDefaultRolesBasedOnConfig() l4g.Info(utils.T("api.server.new_server.init.info")) @@ -157,7 +158,6 @@ func New(options ...Option) (*App, error) { func (a *App) configOrLicenseListener() { a.regenerateClientConfig() - a.SetDefaultRolesBasedOnConfig() } func (a *App) Shutdown() { @@ -450,3 +450,57 @@ func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { utils.RenderWebError(err, w, r) } + +// This function migrates the default built in roles from code/config to the database. +func (a *App) DoAdvancedPermissionsMigration() { + // If the migration is already marked as completed, don't do it again. + if result := <-a.Srv.Store.System().GetByName(ADVANCED_PERMISSIONS_MIGRATION_KEY); result.Err == nil { + return + } + + l4g.Info("Migrating roles to database.") + roles := model.MakeDefaultRoles() + roles = utils.SetRolePermissionsFromConfig(roles, a.Config()) + + allSucceeded := true + + for _, role := range roles { + if result := <-a.Srv.Store.Role().Save(role); result.Err != nil { + // If this failed for reasons other than the role already existing, don't mark the migration as done. + if result2 := <-a.Srv.Store.Role().GetByName(role.Name); result2.Err != nil { + l4g.Critical("Failed to migrate role to database.") + l4g.Critical(result.Err) + allSucceeded = false + } else { + // If the role already existed, check it is the same and update if not. + fetchedRole := result.Data.(*model.Role) + if !reflect.DeepEqual(fetchedRole.Permissions, role.Permissions) || + fetchedRole.DisplayName != role.DisplayName || + fetchedRole.Description != role.Description || + fetchedRole.SchemeManaged != role.SchemeManaged { + role.Id = fetchedRole.Id + if result := <-a.Srv.Store.Role().Save(role); result.Err != nil { + // Role is not the same, but failed to update. + l4g.Critical("Failed to migrate role to database.") + l4g.Critical(result.Err) + allSucceeded = false + } + } + } + } + } + + if !allSucceeded { + return + } + + system := model.System{ + Name: ADVANCED_PERMISSIONS_MIGRATION_KEY, + Value: "true", + } + + if result := <-a.Srv.Store.System().Save(&system); result.Err != nil { + l4g.Critical("Failed to mark advanced permissions migration as completed.") + l4g.Critical(result.Err) + } +} diff --git a/app/app_test.go b/app/app_test.go index 25b19ead8..6d62bf249 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -9,7 +9,6 @@ import ( "testing" l4g "github.com/alecthomas/log4go" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -71,3 +70,342 @@ func TestUpdateConfig(t *testing.T) { *cfg.ServiceSettings.SiteURL = "foo" }) } + +func TestDoAdvancedPermissionsMigration(t *testing.T) { + th := Setup() + defer th.TearDown() + + if testStoreSqlSupplier == nil { + t.Skip("This test requires a TestStore to be run.") + } + + th.ResetRoleMigration() + + th.App.DoAdvancedPermissionsMigration() + + roleNames := []string{ + "system_user", + "system_admin", + "team_user", + "team_admin", + "channel_user", + "channel_admin", + "system_post_all", + "system_post_all_public", + "system_user_access_token", + "team_post_all", + "team_post_all_public", + } + + roles1, err1 := th.App.GetRolesByNames(roleNames) + assert.Nil(t, err1) + assert.Equal(t, len(roles1), len(roleNames)) + + expected1 := map[string][]string{ + "channel_user": []string{ + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + model.PERMISSION_EDIT_POST.Id, + model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_POST.Id, + }, + "channel_admin": []string{ + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + }, + "team_user": []string{ + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + }, + "team_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "team_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "team_admin": []string{ + model.PERMISSION_EDIT_OTHERS_POSTS.Id, + model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, + model.PERMISSION_MANAGE_TEAM.Id, + model.PERMISSION_IMPORT_TEAM.Id, + model.PERMISSION_MANAGE_TEAM_ROLES.Id, + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + }, + "system_user": []string{ + model.PERMISSION_CREATE_DIRECT_CHANNEL.Id, + model.PERMISSION_CREATE_GROUP_CHANNEL.Id, + model.PERMISSION_PERMANENT_DELETE_USER.Id, + model.PERMISSION_CREATE_TEAM.Id, + }, + "system_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "system_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "system_user_access_token": []string{ + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + "system_admin": []string{ + model.PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + model.PERMISSION_MANAGE_SYSTEM.Id, + model.PERMISSION_MANAGE_ROLES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_EDIT_OTHER_USERS.Id, + model.PERMISSION_MANAGE_OAUTH.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + model.PERMISSION_CREATE_TEAM.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + model.PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, + model.PERMISSION_MANAGE_JOBS.Id, + model.PERMISSION_CREATE_POST_PUBLIC.Id, + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + model.PERMISSION_EDIT_POST.Id, + model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_EDIT_OTHERS_POSTS.Id, + model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, + model.PERMISSION_MANAGE_TEAM.Id, + model.PERMISSION_IMPORT_TEAM.Id, + model.PERMISSION_MANAGE_TEAM_ROLES.Id, + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + }, + } + + // Check the migration matches what's expected. + for name, permissions := range expected1 { + role, err := th.App.GetRoleByName(name) + assert.Nil(t, err) + assert.Equal(t, role.Permissions, permissions) + } + + // Add a license and change the policy config. + isLicensed := utils.IsLicensed() + license := utils.License() + restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement + restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement + + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) + utils.SetIsLicensed(isLicensed) + utils.SetLicense(license) + }() + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN + }) + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN + }) + utils.SetIsLicensed(true) + utils.SetLicense(&model.License{Features: &model.Features{}}) + utils.License().Features.SetDefaults() + + // Check the migration doesn't change anything if run again. + th.App.DoAdvancedPermissionsMigration() + + roles2, err2 := th.App.GetRolesByNames(roleNames) + assert.Nil(t, err2) + assert.Equal(t, len(roles2), len(roleNames)) + + for name, permissions := range expected1 { + role, err := th.App.GetRoleByName(name) + assert.Nil(t, err) + assert.Equal(t, permissions, role.Permissions) + } + + // Reset the database + th.ResetRoleMigration() + + // Do the migration again with different policy config settings and a license. + th.App.DoAdvancedPermissionsMigration() + + // Check the role permissions. + expected2 := map[string][]string{ + "channel_user": []string{ + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + model.PERMISSION_EDIT_POST.Id, + model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_POST.Id, + }, + "channel_admin": []string{ + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + }, + "team_user": []string{ + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + }, + "team_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "team_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "team_admin": []string{ + model.PERMISSION_EDIT_OTHERS_POSTS.Id, + model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, + model.PERMISSION_MANAGE_TEAM.Id, + model.PERMISSION_IMPORT_TEAM.Id, + model.PERMISSION_MANAGE_TEAM_ROLES.Id, + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + }, + "system_user": []string{ + model.PERMISSION_CREATE_DIRECT_CHANNEL.Id, + model.PERMISSION_CREATE_GROUP_CHANNEL.Id, + model.PERMISSION_PERMANENT_DELETE_USER.Id, + model.PERMISSION_CREATE_TEAM.Id, + }, + "system_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "system_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "system_user_access_token": []string{ + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + "system_admin": []string{ + model.PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + model.PERMISSION_MANAGE_SYSTEM.Id, + model.PERMISSION_MANAGE_ROLES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_EDIT_OTHER_USERS.Id, + model.PERMISSION_MANAGE_OAUTH.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + model.PERMISSION_CREATE_TEAM.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + model.PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, + model.PERMISSION_MANAGE_JOBS.Id, + model.PERMISSION_CREATE_POST_PUBLIC.Id, + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + model.PERMISSION_EDIT_POST.Id, + model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_EDIT_OTHERS_POSTS.Id, + model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, + model.PERMISSION_MANAGE_TEAM.Id, + model.PERMISSION_IMPORT_TEAM.Id, + model.PERMISSION_MANAGE_TEAM_ROLES.Id, + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + }, + } + + roles3, err3 := th.App.GetRolesByNames(roleNames) + assert.Nil(t, err3) + assert.Equal(t, len(roles3), len(roleNames)) + + for name, permissions := range expected2 { + role, err := th.App.GetRoleByName(name) + assert.Nil(t, err) + assert.Equal(t, permissions, role.Permissions) + } + + // Remove the license. + utils.SetIsLicensed(false) + + // Do the migration again. + th.ResetRoleMigration() + th.App.DoAdvancedPermissionsMigration() + + // Check the role permissions. + roles4, err4 := th.App.GetRolesByNames(roleNames) + assert.Nil(t, err4) + assert.Equal(t, len(roles4), len(roleNames)) + + for name, permissions := range expected1 { + role, err := th.App.GetRoleByName(name) + assert.Nil(t, err) + assert.Equal(t, permissions, role.Permissions) + } +} diff --git a/app/apptestlib.go b/app/apptestlib.go index 09afc8f76..2b56c7ded 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -13,6 +13,7 @@ import ( l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/plugin/pluginenv" @@ -43,12 +44,16 @@ func (*persistentTestStore) Close() {} var testStoreContainer *storetest.RunningContainer var testStore *persistentTestStore +var testStoreSqlSupplier *sqlstore.SqlSupplier +var testClusterInterface *FakeClusterInterface // UseTestStore sets the container and corresponding settings to use for tests. Once the tests are // complete (e.g. at the end of your TestMain implementation), you should call StopTestStore. func UseTestStore(container *storetest.RunningContainer, settings *model.SqlSettings) { + testClusterInterface = &FakeClusterInterface{} testStoreContainer = container - testStore = &persistentTestStore{store.NewLayeredStore(sqlstore.NewSqlSupplier(*settings, nil), nil, nil)} + testStoreSqlSupplier = sqlstore.NewSqlSupplier(*settings, nil) + testStore = &persistentTestStore{store.NewLayeredStore(testStoreSqlSupplier, nil, testClusterInterface)} } func StopTestStore() { @@ -98,6 +103,9 @@ func setupTestHelper(enterprise bool) *TestHelper { } th.App.StartServer() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) + + th.App.DoAdvancedPermissionsMigration() + th.App.Srv.Store.MarkSystemRanUnitTests() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) @@ -313,3 +321,45 @@ func (me *TestHelper) InstallPlugin(manifest *model.Manifest, hooks plugin.Hooks panic(err) } } + +func (me *TestHelper) ResetRoleMigration() { + if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Roles"); err != nil { + panic(err) + } + + testClusterInterface.sendClearRoleCacheMessage() + + if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": ADVANCED_PERMISSIONS_MIGRATION_KEY}); err != nil { + panic(err) + } +} + +type FakeClusterInterface struct { + clusterMessageHandler einterfaces.ClusterMessageHandler +} + +func (me *FakeClusterInterface) StartInterNodeCommunication() {} +func (me *FakeClusterInterface) StopInterNodeCommunication() {} +func (me *FakeClusterInterface) RegisterClusterMessageHandler(event string, crm einterfaces.ClusterMessageHandler) { + me.clusterMessageHandler = crm +} +func (me *FakeClusterInterface) GetClusterId() string { return "" } +func (me *FakeClusterInterface) IsLeader() bool { return false } +func (me *FakeClusterInterface) GetMyClusterInfo() *model.ClusterInfo { return nil } +func (me *FakeClusterInterface) GetClusterInfos() []*model.ClusterInfo { return nil } +func (me *FakeClusterInterface) SendClusterMessage(cluster *model.ClusterMessage) {} +func (me *FakeClusterInterface) NotifyMsg(buf []byte) {} +func (me *FakeClusterInterface) GetClusterStats() ([]*model.ClusterStats, *model.AppError) { + return nil, nil +} +func (me *FakeClusterInterface) GetLogs(page, perPage int) ([]string, *model.AppError) { + return []string{}, nil +} +func (me *FakeClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { + return nil +} +func (me *FakeClusterInterface) sendClearRoleCacheMessage() { + me.clusterMessageHandler(&model.ClusterMessage{ + Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, + }) +} diff --git a/app/authorization.go b/app/authorization.go index 3a64bb717..632dd7566 100644 --- a/app/authorization.go +++ b/app/authorization.go @@ -12,7 +12,7 @@ import ( ) func (a *App) SessionHasPermissionTo(session model.Session, permission *model.Permission) bool { - if !a.CheckIfRolesGrantPermission(session.GetUserRoles(), permission.Id) { + if !a.RolesGrantPermission(session.GetUserRoles(), permission.Id) { a.ClearSessionCacheForUser(session.UserId) return false } @@ -28,12 +28,12 @@ func (a *App) SessionHasPermissionToTeam(session model.Session, teamId string, p teamMember := session.GetTeamByTeamId(teamId) if teamMember != nil { - if a.CheckIfRolesGrantPermission(teamMember.GetRoles(), permission.Id) { + if a.RolesGrantPermission(teamMember.GetRoles(), permission.Id) { return true } } - return a.CheckIfRolesGrantPermission(session.GetUserRoles(), permission.Id) + return a.RolesGrantPermission(session.GetUserRoles(), permission.Id) } func (a *App) SessionHasPermissionToChannel(session model.Session, channelId string, permission *model.Permission) bool { @@ -48,7 +48,7 @@ func (a *App) SessionHasPermissionToChannel(session model.Session, channelId str ids := cmcresult.Data.(map[string]string) if roles, ok := ids[channelId]; ok { channelRoles = strings.Fields(roles) - if a.CheckIfRolesGrantPermission(channelRoles, permission.Id) { + if a.RolesGrantPermission(channelRoles, permission.Id) { return true } } @@ -69,7 +69,7 @@ func (a *App) SessionHasPermissionToChannelByPost(session model.Session, postId if result := <-a.Srv.Store.Channel().GetMemberForPost(postId, session.UserId); result.Err == nil { channelMember = result.Data.(*model.ChannelMember) - if a.CheckIfRolesGrantPermission(channelMember.GetRoles(), permission.Id) { + if a.RolesGrantPermission(channelMember.GetRoles(), permission.Id) { return true } } @@ -119,7 +119,7 @@ func (a *App) HasPermissionTo(askingUserId string, permission *model.Permission) roles := user.GetRoles() - return a.CheckIfRolesGrantPermission(roles, permission.Id) + return a.RolesGrantPermission(roles, permission.Id) } func (a *App) HasPermissionToTeam(askingUserId string, teamId string, permission *model.Permission) bool { @@ -134,7 +134,7 @@ func (a *App) HasPermissionToTeam(askingUserId string, teamId string, permission roles := teamMember.GetRoles() - if a.CheckIfRolesGrantPermission(roles, permission.Id) { + if a.RolesGrantPermission(roles, permission.Id) { return true } @@ -149,7 +149,7 @@ func (a *App) HasPermissionToChannel(askingUserId string, channelId string, perm channelMember, err := a.GetChannelMember(channelId, askingUserId) if err == nil { roles := channelMember.GetRoles() - if a.CheckIfRolesGrantPermission(roles, permission.Id) { + if a.RolesGrantPermission(roles, permission.Id) { return true } } @@ -168,7 +168,7 @@ func (a *App) HasPermissionToChannelByPost(askingUserId string, postId string, p if result := <-a.Srv.Store.Channel().GetMemberForPost(postId, askingUserId); result.Err == nil { channelMember = result.Data.(*model.ChannelMember) - if a.CheckIfRolesGrantPermission(channelMember.GetRoles(), permission.Id) { + if a.RolesGrantPermission(channelMember.GetRoles(), permission.Id) { return true } } @@ -193,17 +193,21 @@ func (a *App) HasPermissionToUser(askingUserId string, userId string) bool { return false } -func (a *App) CheckIfRolesGrantPermission(roles []string, permissionId string) bool { - for _, roleId := range roles { - if role := a.Role(roleId); role == nil { - l4g.Debug("Bad role in system " + roleId) - return false - } else { - permissions := role.Permissions - for _, permission := range permissions { - if permission == permissionId { - return true - } +func (a *App) RolesGrantPermission(roleNames []string, permissionId string) bool { + roles, err := a.GetRolesByNames(roleNames) + if err != nil { + // This should only happen if something is very broken. We can't realistically + // recover the situation, so deny permission and log an error. + l4g.Error("Failed to get roles from database with role names: " + strings.Join(roleNames, ",")) + l4g.Error(err) + return false + } + + for _, role := range roles { + permissions := role.Permissions + for _, permission := range permissions { + if permission == permissionId { + return true } } } diff --git a/app/authorization_test.go b/app/authorization_test.go index a65fe8333..2127a682e 100644 --- a/app/authorization_test.go +++ b/app/authorization_test.go @@ -18,9 +18,9 @@ func TestCheckIfRolesGrantPermission(t *testing.T) { permissionId string shouldGrant bool }{ - {[]string{model.SYSTEM_ADMIN_ROLE_ID}, th.App.Role(model.SYSTEM_ADMIN_ROLE_ID).Permissions[0], true}, + {[]string{model.SYSTEM_ADMIN_ROLE_ID}, model.PERMISSION_MANAGE_SYSTEM.Id, true}, {[]string{model.SYSTEM_ADMIN_ROLE_ID}, "non-existant-permission", false}, - {[]string{model.CHANNEL_USER_ROLE_ID}, th.App.Role(model.CHANNEL_USER_ROLE_ID).Permissions[0], true}, + {[]string{model.CHANNEL_USER_ROLE_ID}, model.PERMISSION_READ_CHANNEL.Id, true}, {[]string{model.CHANNEL_USER_ROLE_ID}, model.PERMISSION_MANAGE_SYSTEM.Id, false}, {[]string{model.SYSTEM_ADMIN_ROLE_ID, model.CHANNEL_USER_ROLE_ID}, model.PERMISSION_MANAGE_SYSTEM.Id, true}, {[]string{model.CHANNEL_USER_ROLE_ID, model.SYSTEM_ADMIN_ROLE_ID}, model.PERMISSION_MANAGE_SYSTEM.Id, true}, @@ -29,7 +29,7 @@ func TestCheckIfRolesGrantPermission(t *testing.T) { } for testnum, testcase := range cases { - if th.App.CheckIfRolesGrantPermission(testcase.roles, testcase.permissionId) != testcase.shouldGrant { + if th.App.RolesGrantPermission(testcase.roles, testcase.permissionId) != testcase.shouldGrant { t.Fatal("Failed test case ", testnum) } } diff --git a/app/channel.go b/app/channel.go index 0054fe14b..c588ebda9 100644 --- a/app/channel.go +++ b/app/channel.go @@ -431,6 +431,10 @@ func (a *App) UpdateChannelMemberRoles(channelId string, userId string, newRoles return nil, err } + if err := a.CheckRolesExist(strings.Fields(newRoles)); err != nil { + return nil, err + } + member.Roles = newRoles if result := <-a.Srv.Store.Channel().UpdateMember(member); result.Err != nil { diff --git a/app/import_test.go b/app/import_test.go index 6a284f63d..be2befd82 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -411,10 +411,6 @@ func TestImportValidateUserImportData(t *testing.T) { } data.Position = ptrStr("The Boss") - data.Roles = ptrStr("system_user wat") - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too unrecognised role.") - } data.Roles = nil if err := validateUserImportData(&data); err != nil { t.Fatal("Validation failed but should have been valid.") @@ -478,12 +474,6 @@ func TestImportValidateUserTeamsImportData(t *testing.T) { } data[0].Name = ptrStr("teamname") - // Invalid Roles - data[0].Roles = ptrStr("wtf") - if err := validateUserTeamsImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid roles.") - } - // Valid (nil roles) data[0].Roles = nil if err := validateUserTeamsImportData(&data); err != nil { @@ -516,12 +506,6 @@ func TestImportValidateUserChannelsImportData(t *testing.T) { } data[0].Name = ptrStr("channelname") - // Invalid Roles - data[0].Roles = ptrStr("wtf") - if err := validateUserChannelsImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid roles.") - } - // Valid (nil roles) data[0].Roles = nil if err := validateUserChannelsImportData(&data); err != nil { diff --git a/app/post.go b/app/post.go index bf4725e77..fc42a6b6b 100644 --- a/app/post.go +++ b/app/post.go @@ -127,7 +127,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo if utils.IsLicensed() && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && !post.IsSystemMessage() && channel.Name == model.DEFAULT_CHANNEL && - !a.CheckIfRolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { + !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) } diff --git a/app/role.go b/app/role.go index 5f39dd623..c99d8365b 100644 --- a/app/role.go +++ b/app/role.go @@ -1,19 +1,91 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app import ( + "reflect" + "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" + "net/http" ) -func (a *App) Role(id string) *model.Role { - return a.roles[id] +func (a *App) GetRole(id string) (*model.Role, *model.AppError) { + if result := <-a.Srv.Store.Role().Get(id); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Role), nil + } +} + +func (a *App) GetRoleByName(name string) (*model.Role, *model.AppError) { + if result := <-a.Srv.Store.Role().GetByName(name); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Role), nil + } +} + +func (a *App) GetRolesByNames(names []string) ([]*model.Role, *model.AppError) { + if result := <-a.Srv.Store.Role().GetByNames(names); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Role), nil + } +} + +func (a *App) PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError) { + // If patch is a no-op then short-circuit the store. + if patch.Permissions != nil && reflect.DeepEqual(*patch.Permissions, role.Permissions) { + return role, nil + } + + role.Patch(patch) + role, err := a.UpdateRole(role) + if err != nil { + return nil, err + } + + return role, err } -// Updates the roles based on the app config and the global license check. You may need to invoke -// this when license changes are made. -func (a *App) SetDefaultRolesBasedOnConfig() { - a.roles = utils.DefaultRolesBasedOnConfig(a.Config()) +func (a *App) UpdateRole(role *model.Role) (*model.Role, *model.AppError) { + if result := <-a.Srv.Store.Role().Save(role); result.Err != nil { + return nil, result.Err + } else { + a.sendUpdatedRoleEvent(role) + + return role, nil + } +} + +func (a *App) CheckRolesExist(roleNames []string) *model.AppError { + roles, err := a.GetRolesByNames(roleNames) + if err != nil { + return err + } + + for _, name := range roleNames { + nameFound := false + for _, role := range roles { + if name == role.Name { + nameFound = true + break + } + } + if !nameFound { + return model.NewAppError("CheckRolesExist", "app.role.check_roles_exist.role_not_found", nil, "role="+name, http.StatusBadRequest) + } + } + + return nil +} + +func (a *App) sendUpdatedRoleEvent(role *model.Role) { + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_ROLE_UPDATED, "", "", "", nil) + message.Add("role", role.ToJson()) + + a.Go(func() { + a.Publish(message) + }) } diff --git a/app/team.go b/app/team.go index 71eb00569..95d9895ec 100644 --- a/app/team.go +++ b/app/team.go @@ -157,6 +157,10 @@ func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles strin return nil, err } + if err := a.CheckRolesExist(strings.Fields(newRoles)); err != nil { + return nil, err + } + member.Roles = newRoles if result := <-a.Srv.Store.Team().UpdateMember(member); result.Err != nil { diff --git a/app/user.go b/app/user.go index 64e49e293..156503fb0 100644 --- a/app/user.go +++ b/app/user.go @@ -1230,6 +1230,10 @@ func (a *App) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent return nil, err } + if err := a.CheckRolesExist(strings.Fields(newRoles)); err != nil { + return nil, err + } + user.Roles = newRoles uchan := a.Srv.Store.User().Update(user, true) schan := a.Srv.Store.Session().UpdateRoles(user.Id, newRoles) diff --git a/cmd/platform/server.go b/cmd/platform/server.go index d289898da..cfcefbba8 100644 --- a/cmd/platform/server.go +++ b/cmd/platform/server.go @@ -78,6 +78,8 @@ func runServer(configFileLocation string, disableConfigWatch bool) { a.LoadLicense() } + a.DoAdvancedPermissionsMigration() + a.InitPlugins(*a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory, nil) a.AddConfigListener(func(prevCfg, cfg *model.Config) { if *cfg.PluginSettings.Enable { diff --git a/i18n/en.json b/i18n/en.json index 88df06f37..606b61181 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2342,10 +2342,18 @@ "id": "api.templates.email_change_body.info", "translation": "Your email address for {{.TeamDisplayName}} has been changed to {{.NewEmail}}.
If you did not make this change, please contact the system administrator." }, + { + "id": "app.role.check_roles_exist.role_not_found", + "translation": "The provided role does not exist" + }, { "id": "api.templates.email_change_body.title", "translation": "You updated your email" }, + { + "id": "store.sql_role.save.invalid_role.app_error", + "translation": "The role was not valid" + }, { "id": "api.templates.email_change_subject", "translation": "[{{ .SiteName }}] Your email address has changed" @@ -6422,6 +6430,26 @@ "id": "store.sql_reaction.save.save.app_error", "translation": "Unable to save reaction" }, + { + "id": "store.sql_role.save.insert.app_error", + "translation": "Unable to save new role" + }, + { + "id": "store.sql_role.save.update.app_error", + "translation": "Unable to update role" + }, + { + "id": "store.sql_role.get.app_error", + "translation": "Unable to get role" + }, + { + "id": "store.sql_role.get_by_name.app_error", + "translation": "Unable to get role" + }, + { + "id": "store.sql_role.get_by_names.app_error", + "translation": "Unable to get roles" + }, { "id": "store.sql_session.analytics_session_count.app_error", "translation": "We couldn't count the sessions" diff --git a/model/authorization.go b/model/authorization.go deleted file mode 100644 index 9f4e36eab..000000000 --- a/model/authorization.go +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -type Permission struct { - Id string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` -} - -type Role struct { - Id string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Permissions []string `json:"permissions"` -} - -var PERMISSION_INVITE_USER *Permission -var PERMISSION_ADD_USER_TO_TEAM *Permission -var PERMISSION_USE_SLASH_COMMANDS *Permission -var PERMISSION_MANAGE_SLASH_COMMANDS *Permission -var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission -var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission -var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission -var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission -var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission -var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission -var PERMISSION_MANAGE_ROLES *Permission -var PERMISSION_MANAGE_TEAM_ROLES *Permission -var PERMISSION_MANAGE_CHANNEL_ROLES *Permission -var PERMISSION_CREATE_DIRECT_CHANNEL *Permission -var PERMISSION_CREATE_GROUP_CHANNEL *Permission -var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission -var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission -var PERMISSION_LIST_TEAM_CHANNELS *Permission -var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission -var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission -var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission -var PERMISSION_EDIT_OTHER_USERS *Permission -var PERMISSION_READ_CHANNEL *Permission -var PERMISSION_READ_PUBLIC_CHANNEL *Permission -var PERMISSION_PERMANENT_DELETE_USER *Permission -var PERMISSION_UPLOAD_FILE *Permission -var PERMISSION_GET_PUBLIC_LINK *Permission -var PERMISSION_MANAGE_WEBHOOKS *Permission -var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission -var PERMISSION_MANAGE_OAUTH *Permission -var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission -var PERMISSION_CREATE_POST *Permission -var PERMISSION_CREATE_POST_PUBLIC *Permission -var PERMISSION_EDIT_POST *Permission -var PERMISSION_EDIT_OTHERS_POSTS *Permission -var PERMISSION_DELETE_POST *Permission -var PERMISSION_DELETE_OTHERS_POSTS *Permission -var PERMISSION_REMOVE_USER_FROM_TEAM *Permission -var PERMISSION_CREATE_TEAM *Permission -var PERMISSION_MANAGE_TEAM *Permission -var PERMISSION_IMPORT_TEAM *Permission -var PERMISSION_VIEW_TEAM *Permission -var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission -var PERMISSION_MANAGE_JOBS *Permission -var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission -var PERMISSION_READ_USER_ACCESS_TOKEN *Permission -var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission - -// General permission that encompases all system admin functions -// in the future this could be broken up to allow access to some -// admin functions but not others -var PERMISSION_MANAGE_SYSTEM *Permission - -const ( - SYSTEM_USER_ROLE_ID = "system_user" - SYSTEM_ADMIN_ROLE_ID = "system_admin" - SYSTEM_POST_ALL_ROLE_ID = "system_post_all" - SYSTEM_POST_ALL_PUBLIC_ROLE_ID = "system_post_all_public" - SYSTEM_USER_ACCESS_TOKEN_ROLE_ID = "system_user_access_token" - - TEAM_USER_ROLE_ID = "team_user" - TEAM_ADMIN_ROLE_ID = "team_admin" - TEAM_POST_ALL_ROLE_ID = "team_post_all" - TEAM_POST_ALL_PUBLIC_ROLE_ID = "team_post_all_public" - - CHANNEL_USER_ROLE_ID = "channel_user" - CHANNEL_ADMIN_ROLE_ID = "channel_admin" - CHANNEL_GUEST_ROLE_ID = "guest" -) - -func initializePermissions() { - PERMISSION_INVITE_USER = &Permission{ - "invite_user", - "authentication.permissions.team_invite_user.name", - "authentication.permissions.team_invite_user.description", - } - PERMISSION_ADD_USER_TO_TEAM = &Permission{ - "add_user_to_team", - "authentication.permissions.add_user_to_team.name", - "authentication.permissions.add_user_to_team.description", - } - PERMISSION_USE_SLASH_COMMANDS = &Permission{ - "use_slash_commands", - "authentication.permissions.team_use_slash_commands.name", - "authentication.permissions.team_use_slash_commands.description", - } - PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{ - "manage_slash_commands", - "authentication.permissions.manage_slash_commands.name", - "authentication.permissions.manage_slash_commands.description", - } - PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{ - "manage_others_slash_commands", - "authentication.permissions.manage_others_slash_commands.name", - "authentication.permissions.manage_others_slash_commands.description", - } - PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{ - "create_public_channel", - "authentication.permissions.create_public_channel.name", - "authentication.permissions.create_public_channel.description", - } - PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{ - "create_private_channel", - "authentication.permissions.create_private_channel.name", - "authentication.permissions.create_private_channel.description", - } - PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{ - "manage_public_channel_members", - "authentication.permissions.manage_public_channel_members.name", - "authentication.permissions.manage_public_channel_members.description", - } - PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{ - "manage_private_channel_members", - "authentication.permissions.manage_private_channel_members.name", - "authentication.permissions.manage_private_channel_members.description", - } - PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{ - "assign_system_admin_role", - "authentication.permissions.assign_system_admin_role.name", - "authentication.permissions.assign_system_admin_role.description", - } - PERMISSION_MANAGE_ROLES = &Permission{ - "manage_roles", - "authentication.permissions.manage_roles.name", - "authentication.permissions.manage_roles.description", - } - PERMISSION_MANAGE_TEAM_ROLES = &Permission{ - "manage_team_roles", - "authentication.permissions.manage_team_roles.name", - "authentication.permissions.manage_team_roles.description", - } - PERMISSION_MANAGE_CHANNEL_ROLES = &Permission{ - "manage_channel_roles", - "authentication.permissions.manage_channel_roles.name", - "authentication.permissions.manage_channel_roles.description", - } - PERMISSION_MANAGE_SYSTEM = &Permission{ - "manage_system", - "authentication.permissions.manage_system.name", - "authentication.permissions.manage_system.description", - } - PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{ - "create_direct_channel", - "authentication.permissions.create_direct_channel.name", - "authentication.permissions.create_direct_channel.description", - } - PERMISSION_CREATE_GROUP_CHANNEL = &Permission{ - "create_group_channel", - "authentication.permissions.create_group_channel.name", - "authentication.permissions.create_group_channel.description", - } - PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{ - "manage__publicchannel_properties", - "authentication.permissions.manage_public_channel_properties.name", - "authentication.permissions.manage_public_channel_properties.description", - } - PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{ - "manage_private_channel_properties", - "authentication.permissions.manage_private_channel_properties.name", - "authentication.permissions.manage_private_channel_properties.description", - } - PERMISSION_LIST_TEAM_CHANNELS = &Permission{ - "list_team_channels", - "authentication.permissions.list_team_channels.name", - "authentication.permissions.list_team_channels.description", - } - PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{ - "join_public_channels", - "authentication.permissions.join_public_channels.name", - "authentication.permissions.join_public_channels.description", - } - PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{ - "delete_public_channel", - "authentication.permissions.delete_public_channel.name", - "authentication.permissions.delete_public_channel.description", - } - PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{ - "delete_private_channel", - "authentication.permissions.delete_private_channel.name", - "authentication.permissions.delete_private_channel.description", - } - PERMISSION_EDIT_OTHER_USERS = &Permission{ - "edit_other_users", - "authentication.permissions.edit_other_users.name", - "authentication.permissions.edit_other_users.description", - } - PERMISSION_READ_CHANNEL = &Permission{ - "read_channel", - "authentication.permissions.read_channel.name", - "authentication.permissions.read_channel.description", - } - PERMISSION_READ_PUBLIC_CHANNEL = &Permission{ - "read_public_channel", - "authentication.permissions.read_public_channel.name", - "authentication.permissions.read_public_channel.description", - } - PERMISSION_PERMANENT_DELETE_USER = &Permission{ - "permanent_delete_user", - "authentication.permissions.permanent_delete_user.name", - "authentication.permissions.permanent_delete_user.description", - } - PERMISSION_UPLOAD_FILE = &Permission{ - "upload_file", - "authentication.permissions.upload_file.name", - "authentication.permissions.upload_file.description", - } - PERMISSION_GET_PUBLIC_LINK = &Permission{ - "get_public_link", - "authentication.permissions.get_public_link.name", - "authentication.permissions.get_public_link.description", - } - PERMISSION_MANAGE_WEBHOOKS = &Permission{ - "manage_webhooks", - "authentication.permissions.manage_webhooks.name", - "authentication.permissions.manage_webhooks.description", - } - PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{ - "manage_others_webhooks", - "authentication.permissions.manage_others_webhooks.name", - "authentication.permissions.manage_others_webhooks.description", - } - PERMISSION_MANAGE_OAUTH = &Permission{ - "manage_oauth", - "authentication.permissions.manage_oauth.name", - "authentication.permissions.manage_oauth.description", - } - PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ - "manage_sytem_wide_oauth", - "authentication.permissions.manage_sytem_wide_oauth.name", - "authentication.permissions.manage_sytem_wide_oauth.description", - } - PERMISSION_CREATE_POST = &Permission{ - "create_post", - "authentication.permissions.create_post.name", - "authentication.permissions.create_post.description", - } - PERMISSION_CREATE_POST_PUBLIC = &Permission{ - "create_post_public", - "authentication.permissions.create_post_public.name", - "authentication.permissions.create_post_public.description", - } - PERMISSION_EDIT_POST = &Permission{ - "edit_post", - "authentication.permissions.edit_post.name", - "authentication.permissions.edit_post.description", - } - PERMISSION_EDIT_OTHERS_POSTS = &Permission{ - "edit_others_posts", - "authentication.permissions.edit_others_posts.name", - "authentication.permissions.edit_others_posts.description", - } - PERMISSION_DELETE_POST = &Permission{ - "delete_post", - "authentication.permissions.delete_post.name", - "authentication.permissions.delete_post.description", - } - PERMISSION_DELETE_OTHERS_POSTS = &Permission{ - "delete_others_posts", - "authentication.permissions.delete_others_posts.name", - "authentication.permissions.delete_others_posts.description", - } - PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{ - "remove_user_from_team", - "authentication.permissions.remove_user_from_team.name", - "authentication.permissions.remove_user_from_team.description", - } - PERMISSION_CREATE_TEAM = &Permission{ - "create_team", - "authentication.permissions.create_team.name", - "authentication.permissions.create_team.description", - } - PERMISSION_MANAGE_TEAM = &Permission{ - "manage_team", - "authentication.permissions.manage_team.name", - "authentication.permissions.manage_team.description", - } - PERMISSION_IMPORT_TEAM = &Permission{ - "import_team", - "authentication.permissions.import_team.name", - "authentication.permissions.import_team.description", - } - PERMISSION_VIEW_TEAM = &Permission{ - "view_team", - "authentication.permissions.view_team.name", - "authentication.permissions.view_team.description", - } - PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{ - "list_users_without_team", - "authentication.permissions.list_users_without_team.name", - "authentication.permissions.list_users_without_team.description", - } - PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{ - "create_user_access_token", - "authentication.permissions.create_user_access_token.name", - "authentication.permissions.create_user_access_token.description", - } - PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{ - "read_user_access_token", - "authentication.permissions.read_user_access_token.name", - "authentication.permissions.read_user_access_token.description", - } - PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{ - "revoke_user_access_token", - "authentication.permissions.revoke_user_access_token.name", - "authentication.permissions.revoke_user_access_token.description", - } - PERMISSION_MANAGE_JOBS = &Permission{ - "manage_jobs", - "authentication.permisssions.manage_jobs.name", - "authentication.permisssions.manage_jobs.description", - } -} - -var DefaultRoles map[string]*Role - -func initializeDefaultRoles() { - DefaultRoles = make(map[string]*Role) - - DefaultRoles[CHANNEL_USER_ROLE_ID] = &Role{ - "channel_user", - "authentication.roles.channel_user.name", - "authentication.roles.channel_user.description", - []string{ - PERMISSION_READ_CHANNEL.Id, - PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, - PERMISSION_UPLOAD_FILE.Id, - PERMISSION_GET_PUBLIC_LINK.Id, - PERMISSION_CREATE_POST.Id, - PERMISSION_EDIT_POST.Id, - PERMISSION_USE_SLASH_COMMANDS.Id, - }, - } - - DefaultRoles[CHANNEL_ADMIN_ROLE_ID] = &Role{ - "channel_admin", - "authentication.roles.channel_admin.name", - "authentication.roles.channel_admin.description", - []string{ - PERMISSION_MANAGE_CHANNEL_ROLES.Id, - }, - } - - DefaultRoles[CHANNEL_GUEST_ROLE_ID] = &Role{ - "guest", - "authentication.roles.global_guest.name", - "authentication.roles.global_guest.description", - []string{}, - } - - DefaultRoles[TEAM_USER_ROLE_ID] = &Role{ - "team_user", - "authentication.roles.team_user.name", - "authentication.roles.team_user.description", - []string{ - PERMISSION_LIST_TEAM_CHANNELS.Id, - PERMISSION_JOIN_PUBLIC_CHANNELS.Id, - PERMISSION_READ_PUBLIC_CHANNEL.Id, - PERMISSION_VIEW_TEAM.Id, - }, - } - - DefaultRoles[TEAM_POST_ALL_ROLE_ID] = &Role{ - "team_post_all", - "authentication.roles.team_post_all.name", - "authentication.roles.team_post_all.description", - []string{ - PERMISSION_CREATE_POST.Id, - }, - } - - DefaultRoles[TEAM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ - "team_post_all_public", - "authentication.roles.team_post_all_public.name", - "authentication.roles.team_post_all_public.description", - []string{ - PERMISSION_CREATE_POST_PUBLIC.Id, - }, - } - - DefaultRoles[TEAM_ADMIN_ROLE_ID] = &Role{ - "team_admin", - "authentication.roles.team_admin.name", - "authentication.roles.team_admin.description", - []string{ - PERMISSION_EDIT_OTHERS_POSTS.Id, - PERMISSION_REMOVE_USER_FROM_TEAM.Id, - PERMISSION_MANAGE_TEAM.Id, - PERMISSION_IMPORT_TEAM.Id, - PERMISSION_MANAGE_TEAM_ROLES.Id, - PERMISSION_MANAGE_CHANNEL_ROLES.Id, - PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, - PERMISSION_MANAGE_SLASH_COMMANDS.Id, - PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, - PERMISSION_MANAGE_WEBHOOKS.Id, - }, - } - - DefaultRoles[SYSTEM_USER_ROLE_ID] = &Role{ - "system_user", - "authentication.roles.global_user.name", - "authentication.roles.global_user.description", - []string{ - PERMISSION_CREATE_DIRECT_CHANNEL.Id, - PERMISSION_CREATE_GROUP_CHANNEL.Id, - PERMISSION_PERMANENT_DELETE_USER.Id, - }, - } - - DefaultRoles[SYSTEM_POST_ALL_ROLE_ID] = &Role{ - "system_post_all", - "authentication.roles.system_post_all.name", - "authentication.roles.system_post_all.description", - []string{ - PERMISSION_CREATE_POST.Id, - }, - } - - DefaultRoles[SYSTEM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ - "system_post_all_public", - "authentication.roles.system_post_all_public.name", - "authentication.roles.system_post_all_public.description", - []string{ - PERMISSION_CREATE_POST_PUBLIC.Id, - }, - } - - DefaultRoles[SYSTEM_USER_ACCESS_TOKEN_ROLE_ID] = &Role{ - "system_user_access_token", - "authentication.roles.system_user_access_token.name", - "authentication.roles.system_user_access_token.description", - []string{ - PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, - PERMISSION_READ_USER_ACCESS_TOKEN.Id, - PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, - }, - } - - DefaultRoles[SYSTEM_ADMIN_ROLE_ID] = &Role{ - "system_admin", - "authentication.roles.global_admin.name", - "authentication.roles.global_admin.description", - // System admins can do anything channel and team admins can do - // plus everything members of teams and channels can do to all teams - // and channels on the system - append( - append( - append( - append( - []string{ - PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, - PERMISSION_MANAGE_SYSTEM.Id, - PERMISSION_MANAGE_ROLES.Id, - PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, - PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, - PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, - PERMISSION_DELETE_PUBLIC_CHANNEL.Id, - PERMISSION_CREATE_PUBLIC_CHANNEL.Id, - PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, - PERMISSION_DELETE_PRIVATE_CHANNEL.Id, - PERMISSION_CREATE_PRIVATE_CHANNEL.Id, - PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, - PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, - PERMISSION_EDIT_OTHER_USERS.Id, - PERMISSION_MANAGE_OAUTH.Id, - PERMISSION_INVITE_USER.Id, - PERMISSION_DELETE_POST.Id, - PERMISSION_DELETE_OTHERS_POSTS.Id, - PERMISSION_CREATE_TEAM.Id, - PERMISSION_ADD_USER_TO_TEAM.Id, - PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, - PERMISSION_MANAGE_JOBS.Id, - PERMISSION_CREATE_POST_PUBLIC.Id, - PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, - PERMISSION_READ_USER_ACCESS_TOKEN.Id, - PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, - }, - DefaultRoles[TEAM_USER_ROLE_ID].Permissions..., - ), - DefaultRoles[CHANNEL_USER_ROLE_ID].Permissions..., - ), - DefaultRoles[TEAM_ADMIN_ROLE_ID].Permissions..., - ), - DefaultRoles[CHANNEL_ADMIN_ROLE_ID].Permissions..., - ), - } -} - -func RoleIdsToString(roles []string) string { - output := "" - for _, role := range roles { - output += role + ", " - } - - if output == "" { - return "[]" - } - - return output[:len(output)-1] -} - -func init() { - initializePermissions() - initializeDefaultRoles() -} diff --git a/model/client4.go b/model/client4.go index 0694ecbdf..55179bdf9 100644 --- a/model/client4.go +++ b/model/client4.go @@ -306,6 +306,10 @@ func (c *Client4) GetJobsRoute() string { return fmt.Sprintf("/jobs") } +func (c *Client4) GetRolesRoute() string { + return fmt.Sprintf("/roles") +} + func (c *Client4) GetAnalyticsRoute() string { return fmt.Sprintf("/analytics") } @@ -3204,6 +3208,48 @@ func (c *Client4) CancelJob(jobId string) (bool, *Response) { } } +// Roles Section + +// GetRole gets a single role by ID. +func (c *Client4) GetRole(id string) (*Role, *Response) { + if r, err := c.DoApiGet(c.GetRolesRoute()+fmt.Sprintf("/%v", id), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleFromJson(r.Body), BuildResponse(r) + } +} + +// GetRoleByName gets a single role by Name. +func (c *Client4) GetRoleByName(name string) (*Role, *Response) { + if r, err := c.DoApiGet(c.GetRolesRoute()+fmt.Sprintf("/name/%v", name), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleFromJson(r.Body), BuildResponse(r) + } +} + +// GetRolesByNames returns a list of roles based on the provided role names. +func (c *Client4) GetRolesByNames(roleNames []string) ([]*Role, *Response) { + if r, err := c.DoApiPost(c.GetRolesRoute()+"/names", ArrayToJson(roleNames)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleListFromJson(r.Body), BuildResponse(r) + } +} + +// PatchRole partially updates a role in the system. Any missing fields are not updated. +func (c *Client4) PatchRole(roleId string, patch *RolePatch) (*Role, *Response) { + if r, err := c.DoApiPut(c.GetRolesRoute()+fmt.Sprintf("/%v/patch", roleId), patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleFromJson(r.Body), BuildResponse(r) + } +} + // Plugin Section // UploadPlugin takes an io.Reader stream pointing to the contents of a .tar.gz plugin. diff --git a/model/cluster_message.go b/model/cluster_message.go index a6dec2e7f..9cf0e4d55 100644 --- a/model/cluster_message.go +++ b/model/cluster_message.go @@ -21,6 +21,7 @@ const ( CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER = "inv_user" CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles" CLUSTER_SEND_BEST_EFFORT = "best_effort" CLUSTER_SEND_RELIABLE = "reliable" diff --git a/model/permission.go b/model/permission.go new file mode 100644 index 000000000..0dc09f49a --- /dev/null +++ b/model/permission.go @@ -0,0 +1,418 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +const ( + PERMISSION_SCOPE_SYSTEM = "system_scope" + PERMISSION_SCOPE_TEAM = "team_scope" + PERMISSION_SCOPE_CHANNEL = "channel_scope" +) + +type Permission struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Scope string `json:"scope"` +} + +var PERMISSION_INVITE_USER *Permission +var PERMISSION_ADD_USER_TO_TEAM *Permission +var PERMISSION_USE_SLASH_COMMANDS *Permission +var PERMISSION_MANAGE_SLASH_COMMANDS *Permission +var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission +var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission +var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission +var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission +var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission +var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission +var PERMISSION_MANAGE_ROLES *Permission +var PERMISSION_MANAGE_TEAM_ROLES *Permission +var PERMISSION_MANAGE_CHANNEL_ROLES *Permission +var PERMISSION_CREATE_DIRECT_CHANNEL *Permission +var PERMISSION_CREATE_GROUP_CHANNEL *Permission +var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission +var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission +var PERMISSION_LIST_TEAM_CHANNELS *Permission +var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission +var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission +var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission +var PERMISSION_EDIT_OTHER_USERS *Permission +var PERMISSION_READ_CHANNEL *Permission +var PERMISSION_READ_PUBLIC_CHANNEL *Permission +var PERMISSION_PERMANENT_DELETE_USER *Permission +var PERMISSION_UPLOAD_FILE *Permission +var PERMISSION_GET_PUBLIC_LINK *Permission +var PERMISSION_MANAGE_WEBHOOKS *Permission +var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission +var PERMISSION_MANAGE_OAUTH *Permission +var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission +var PERMISSION_CREATE_POST *Permission +var PERMISSION_CREATE_POST_PUBLIC *Permission +var PERMISSION_EDIT_POST *Permission +var PERMISSION_EDIT_OTHERS_POSTS *Permission +var PERMISSION_DELETE_POST *Permission +var PERMISSION_DELETE_OTHERS_POSTS *Permission +var PERMISSION_REMOVE_USER_FROM_TEAM *Permission +var PERMISSION_CREATE_TEAM *Permission +var PERMISSION_MANAGE_TEAM *Permission +var PERMISSION_IMPORT_TEAM *Permission +var PERMISSION_VIEW_TEAM *Permission +var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission +var PERMISSION_MANAGE_JOBS *Permission +var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission +var PERMISSION_READ_USER_ACCESS_TOKEN *Permission +var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission + +// General permission that encompasses all system admin functions +// in the future this could be broken up to allow access to some +// admin functions but not others +var PERMISSION_MANAGE_SYSTEM *Permission + +var ALL_PERMISSIONS []*Permission + +func initializePermissions() { + PERMISSION_INVITE_USER = &Permission{ + "invite_user", + "authentication.permissions.team_invite_user.name", + "authentication.permissions.team_invite_user.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_ADD_USER_TO_TEAM = &Permission{ + "add_user_to_team", + "authentication.permissions.add_user_to_team.name", + "authentication.permissions.add_user_to_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_USE_SLASH_COMMANDS = &Permission{ + "use_slash_commands", + "authentication.permissions.team_use_slash_commands.name", + "authentication.permissions.team_use_slash_commands.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{ + "manage_slash_commands", + "authentication.permissions.manage_slash_commands.name", + "authentication.permissions.manage_slash_commands.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{ + "manage_others_slash_commands", + "authentication.permissions.manage_others_slash_commands.name", + "authentication.permissions.manage_others_slash_commands.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{ + "create_public_channel", + "authentication.permissions.create_public_channel.name", + "authentication.permissions.create_public_channel.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{ + "create_private_channel", + "authentication.permissions.create_private_channel.name", + "authentication.permissions.create_private_channel.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{ + "manage_public_channel_members", + "authentication.permissions.manage_public_channel_members.name", + "authentication.permissions.manage_public_channel_members.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{ + "manage_private_channel_members", + "authentication.permissions.manage_private_channel_members.name", + "authentication.permissions.manage_private_channel_members.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{ + "assign_system_admin_role", + "authentication.permissions.assign_system_admin_role.name", + "authentication.permissions.assign_system_admin_role.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_ROLES = &Permission{ + "manage_roles", + "authentication.permissions.manage_roles.name", + "authentication.permissions.manage_roles.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_TEAM_ROLES = &Permission{ + "manage_team_roles", + "authentication.permissions.manage_team_roles.name", + "authentication.permissions.manage_team_roles.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_CHANNEL_ROLES = &Permission{ + "manage_channel_roles", + "authentication.permissions.manage_channel_roles.name", + "authentication.permissions.manage_channel_roles.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_SYSTEM = &Permission{ + "manage_system", + "authentication.permissions.manage_system.name", + "authentication.permissions.manage_system.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{ + "create_direct_channel", + "authentication.permissions.create_direct_channel.name", + "authentication.permissions.create_direct_channel.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_GROUP_CHANNEL = &Permission{ + "create_group_channel", + "authentication.permissions.create_group_channel.name", + "authentication.permissions.create_group_channel.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{ + "manage_public_channel_properties", + "authentication.permissions.manage_public_channel_properties.name", + "authentication.permissions.manage_public_channel_properties.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{ + "manage_private_channel_properties", + "authentication.permissions.manage_private_channel_properties.name", + "authentication.permissions.manage_private_channel_properties.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_LIST_TEAM_CHANNELS = &Permission{ + "list_team_channels", + "authentication.permissions.list_team_channels.name", + "authentication.permissions.list_team_channels.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{ + "join_public_channels", + "authentication.permissions.join_public_channels.name", + "authentication.permissions.join_public_channels.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{ + "delete_public_channel", + "authentication.permissions.delete_public_channel.name", + "authentication.permissions.delete_public_channel.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{ + "delete_private_channel", + "authentication.permissions.delete_private_channel.name", + "authentication.permissions.delete_private_channel.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_EDIT_OTHER_USERS = &Permission{ + "edit_other_users", + "authentication.permissions.edit_other_users.name", + "authentication.permissions.edit_other_users.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_READ_CHANNEL = &Permission{ + "read_channel", + "authentication.permissions.read_channel.name", + "authentication.permissions.read_channel.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_READ_PUBLIC_CHANNEL = &Permission{ + "read_public_channel", + "authentication.permissions.read_public_channel.name", + "authentication.permissions.read_public_channel.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_PERMANENT_DELETE_USER = &Permission{ + "permanent_delete_user", + "authentication.permissions.permanent_delete_user.name", + "authentication.permissions.permanent_delete_user.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_UPLOAD_FILE = &Permission{ + "upload_file", + "authentication.permissions.upload_file.name", + "authentication.permissions.upload_file.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_GET_PUBLIC_LINK = &Permission{ + "get_public_link", + "authentication.permissions.get_public_link.name", + "authentication.permissions.get_public_link.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_WEBHOOKS = &Permission{ + "manage_webhooks", + "authentication.permissions.manage_webhooks.name", + "authentication.permissions.manage_webhooks.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{ + "manage_others_webhooks", + "authentication.permissions.manage_others_webhooks.name", + "authentication.permissions.manage_others_webhooks.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OAUTH = &Permission{ + "manage_oauth", + "authentication.permissions.manage_oauth.name", + "authentication.permissions.manage_oauth.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ + "manage_sytem_wide_oauth", + "authentication.permissions.manage_sytem_wide_oauth.name", + "authentication.permissions.manage_sytem_wide_oauth.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_POST = &Permission{ + "create_post", + "authentication.permissions.create_post.name", + "authentication.permissions.create_post.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_CREATE_POST_PUBLIC = &Permission{ + "create_post_public", + "authentication.permissions.create_post_public.name", + "authentication.permissions.create_post_public.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_EDIT_POST = &Permission{ + "edit_post", + "authentication.permissions.edit_post.name", + "authentication.permissions.edit_post.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_EDIT_OTHERS_POSTS = &Permission{ + "edit_others_posts", + "authentication.permissions.edit_others_posts.name", + "authentication.permissions.edit_others_posts.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_DELETE_POST = &Permission{ + "delete_post", + "authentication.permissions.delete_post.name", + "authentication.permissions.delete_post.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_DELETE_OTHERS_POSTS = &Permission{ + "delete_others_posts", + "authentication.permissions.delete_others_posts.name", + "authentication.permissions.delete_others_posts.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{ + "remove_user_from_team", + "authentication.permissions.remove_user_from_team.name", + "authentication.permissions.remove_user_from_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_TEAM = &Permission{ + "create_team", + "authentication.permissions.create_team.name", + "authentication.permissions.create_team.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_TEAM = &Permission{ + "manage_team", + "authentication.permissions.manage_team.name", + "authentication.permissions.manage_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_IMPORT_TEAM = &Permission{ + "import_team", + "authentication.permissions.import_team.name", + "authentication.permissions.import_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_VIEW_TEAM = &Permission{ + "view_team", + "authentication.permissions.view_team.name", + "authentication.permissions.view_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{ + "list_users_without_team", + "authentication.permissions.list_users_without_team.name", + "authentication.permissions.list_users_without_team.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{ + "create_user_access_token", + "authentication.permissions.create_user_access_token.name", + "authentication.permissions.create_user_access_token.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{ + "read_user_access_token", + "authentication.permissions.read_user_access_token.name", + "authentication.permissions.read_user_access_token.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{ + "revoke_user_access_token", + "authentication.permissions.revoke_user_access_token.name", + "authentication.permissions.revoke_user_access_token.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_JOBS = &Permission{ + "manage_jobs", + "authentication.permisssions.manage_jobs.name", + "authentication.permisssions.manage_jobs.description", + PERMISSION_SCOPE_SYSTEM, + } + + ALL_PERMISSIONS = []*Permission{ + PERMISSION_INVITE_USER, + PERMISSION_ADD_USER_TO_TEAM, + PERMISSION_USE_SLASH_COMMANDS, + PERMISSION_MANAGE_SLASH_COMMANDS, + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS, + PERMISSION_CREATE_PUBLIC_CHANNEL, + PERMISSION_CREATE_PRIVATE_CHANNEL, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS, + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS, + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE, + PERMISSION_MANAGE_ROLES, + PERMISSION_MANAGE_TEAM_ROLES, + PERMISSION_MANAGE_CHANNEL_ROLES, + PERMISSION_CREATE_DIRECT_CHANNEL, + PERMISSION_CREATE_GROUP_CHANNEL, + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES, + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES, + PERMISSION_LIST_TEAM_CHANNELS, + PERMISSION_JOIN_PUBLIC_CHANNELS, + PERMISSION_DELETE_PUBLIC_CHANNEL, + PERMISSION_DELETE_PRIVATE_CHANNEL, + PERMISSION_EDIT_OTHER_USERS, + PERMISSION_READ_CHANNEL, + PERMISSION_READ_PUBLIC_CHANNEL, + PERMISSION_PERMANENT_DELETE_USER, + PERMISSION_UPLOAD_FILE, + PERMISSION_GET_PUBLIC_LINK, + PERMISSION_MANAGE_WEBHOOKS, + PERMISSION_MANAGE_OTHERS_WEBHOOKS, + PERMISSION_MANAGE_OAUTH, + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH, + PERMISSION_CREATE_POST, + PERMISSION_CREATE_POST_PUBLIC, + PERMISSION_EDIT_POST, + PERMISSION_EDIT_OTHERS_POSTS, + PERMISSION_DELETE_POST, + PERMISSION_DELETE_OTHERS_POSTS, + PERMISSION_REMOVE_USER_FROM_TEAM, + PERMISSION_CREATE_TEAM, + PERMISSION_MANAGE_TEAM, + PERMISSION_IMPORT_TEAM, + PERMISSION_VIEW_TEAM, + PERMISSION_LIST_USERS_WITHOUT_TEAM, + PERMISSION_MANAGE_JOBS, + PERMISSION_CREATE_USER_ACCESS_TOKEN, + PERMISSION_READ_USER_ACCESS_TOKEN, + PERMISSION_REVOKE_USER_ACCESS_TOKEN, + PERMISSION_MANAGE_SYSTEM, + } +} + +func init() { + initializePermissions() +} diff --git a/model/permission_test.go b/model/permission_test.go new file mode 100644 index 000000000..cef72af90 --- /dev/null +++ b/model/permission_test.go @@ -0,0 +1,18 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// This is a test to ensure that we don't accidentally add more permissions than can fit +// in the database column for role permissions. +func TestPermissionsLength(t *testing.T) { + permissionsString := "" + for _, permission := range ALL_PERMISSIONS { + permissionsString += " " + permission.Id + } + + assert.True(t, len(permissionsString) < 4096) +} diff --git a/model/role.go b/model/role.go new file mode 100644 index 000000000..254513ae0 --- /dev/null +++ b/model/role.go @@ -0,0 +1,310 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "strings" +) + +const ( + SYSTEM_USER_ROLE_ID = "system_user" + SYSTEM_ADMIN_ROLE_ID = "system_admin" + SYSTEM_POST_ALL_ROLE_ID = "system_post_all" + SYSTEM_POST_ALL_PUBLIC_ROLE_ID = "system_post_all_public" + SYSTEM_USER_ACCESS_TOKEN_ROLE_ID = "system_user_access_token" + + TEAM_USER_ROLE_ID = "team_user" + TEAM_ADMIN_ROLE_ID = "team_admin" + TEAM_POST_ALL_ROLE_ID = "team_post_all" + TEAM_POST_ALL_PUBLIC_ROLE_ID = "team_post_all_public" + + CHANNEL_USER_ROLE_ID = "channel_user" + CHANNEL_ADMIN_ROLE_ID = "channel_admin" + + ROLE_NAME_MAX_LENGTH = 64 + ROLE_DISPLAY_NAME_MAX_LENGTH = 128 + ROLE_DESCRIPTION_MAX_LENGTH = 1024 +) + +type Role struct { + Id string `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + Permissions []string `json:"permissions"` + SchemeManaged bool `json:"scheme_managed"` +} + +type RolePatch struct { + Permissions *[]string `json:"permissions"` +} + +func (role *Role) ToJson() string { + b, _ := json.Marshal(role) + return string(b) +} + +func RoleFromJson(data io.Reader) *Role { + var role *Role + json.NewDecoder(data).Decode(&role) + return role +} + +func RoleListToJson(r []*Role) string { + b, _ := json.Marshal(r) + return string(b) +} + +func RoleListFromJson(data io.Reader) []*Role { + var roles []*Role + json.NewDecoder(data).Decode(&roles) + return roles +} + +func (r *RolePatch) ToJson() string { + b, _ := json.Marshal(r) + return string(b) +} + +func RolePatchFromJson(data io.Reader) *RolePatch { + var rolePatch *RolePatch + json.NewDecoder(data).Decode(&rolePatch) + return rolePatch +} + +func (o *Role) Patch(patch *RolePatch) { + if patch.Permissions != nil { + o.Permissions = *patch.Permissions + } +} + +func (role *Role) IsValid() bool { + if len(role.Id) != 26 { + return false + } + + return role.IsValidWithoutId() +} + +func (role *Role) IsValidWithoutId() bool { + if !IsValidRoleName(role.Name) { + return false + } + + if len(role.DisplayName) == 0 || len(role.DisplayName) > ROLE_DISPLAY_NAME_MAX_LENGTH { + return false + } + + if len(role.Description) > ROLE_DESCRIPTION_MAX_LENGTH { + return false + } + + for _, permission := range role.Permissions { + permissionValidated := false + for _, p := range ALL_PERMISSIONS { + if permission == p.Id { + permissionValidated = true + break + } + } + + if !permissionValidated { + return false + } + } + + return true +} + +func IsValidRoleName(roleName string) bool { + if len(roleName) <= 0 || len(roleName) > ROLE_NAME_MAX_LENGTH { + return false + } + + if strings.TrimLeft(roleName, "abcdefghijklmnopqrstuvwxyz0123456789_") != "" { + return false + } + + return true +} + +func MakeDefaultRoles() map[string]*Role { + roles := make(map[string]*Role) + + roles[CHANNEL_USER_ROLE_ID] = &Role{ + Name: "channel_user", + DisplayName: "authentication.roles.channel_user.name", + Description: "authentication.roles.channel_user.description", + Permissions: []string{ + PERMISSION_READ_CHANNEL.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + PERMISSION_UPLOAD_FILE.Id, + PERMISSION_GET_PUBLIC_LINK.Id, + PERMISSION_CREATE_POST.Id, + PERMISSION_EDIT_POST.Id, + PERMISSION_USE_SLASH_COMMANDS.Id, + }, + SchemeManaged: true, + } + + roles[CHANNEL_ADMIN_ROLE_ID] = &Role{ + Name: "channel_admin", + DisplayName: "authentication.roles.channel_admin.name", + Description: "authentication.roles.channel_admin.description", + Permissions: []string{ + PERMISSION_MANAGE_CHANNEL_ROLES.Id, + }, + SchemeManaged: true, + } + + roles[TEAM_USER_ROLE_ID] = &Role{ + Name: "team_user", + DisplayName: "authentication.roles.team_user.name", + Description: "authentication.roles.team_user.description", + Permissions: []string{ + PERMISSION_LIST_TEAM_CHANNELS.Id, + PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + PERMISSION_READ_PUBLIC_CHANNEL.Id, + PERMISSION_VIEW_TEAM.Id, + }, + SchemeManaged: true, + } + + roles[TEAM_POST_ALL_ROLE_ID] = &Role{ + Name: "team_post_all", + DisplayName: "authentication.roles.team_post_all.name", + Description: "authentication.roles.team_post_all.description", + Permissions: []string{ + PERMISSION_CREATE_POST.Id, + }, + SchemeManaged: true, + } + + roles[TEAM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ + Name: "team_post_all_public", + DisplayName: "authentication.roles.team_post_all_public.name", + Description: "authentication.roles.team_post_all_public.description", + Permissions: []string{ + PERMISSION_CREATE_POST_PUBLIC.Id, + }, + SchemeManaged: true, + } + + roles[TEAM_ADMIN_ROLE_ID] = &Role{ + Name: "team_admin", + DisplayName: "authentication.roles.team_admin.name", + Description: "authentication.roles.team_admin.description", + Permissions: []string{ + PERMISSION_EDIT_OTHERS_POSTS.Id, + PERMISSION_REMOVE_USER_FROM_TEAM.Id, + PERMISSION_MANAGE_TEAM.Id, + PERMISSION_IMPORT_TEAM.Id, + PERMISSION_MANAGE_TEAM_ROLES.Id, + PERMISSION_MANAGE_CHANNEL_ROLES.Id, + PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + PERMISSION_MANAGE_SLASH_COMMANDS.Id, + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + PERMISSION_MANAGE_WEBHOOKS.Id, + }, + SchemeManaged: true, + } + + roles[SYSTEM_USER_ROLE_ID] = &Role{ + Name: "system_user", + DisplayName: "authentication.roles.global_user.name", + Description: "authentication.roles.global_user.description", + Permissions: []string{ + PERMISSION_CREATE_DIRECT_CHANNEL.Id, + PERMISSION_CREATE_GROUP_CHANNEL.Id, + PERMISSION_PERMANENT_DELETE_USER.Id, + }, + SchemeManaged: true, + } + + roles[SYSTEM_POST_ALL_ROLE_ID] = &Role{ + Name: "system_post_all", + DisplayName: "authentication.roles.system_post_all.name", + Description: "authentication.roles.system_post_all.description", + Permissions: []string{ + PERMISSION_CREATE_POST.Id, + }, + SchemeManaged: true, + } + + roles[SYSTEM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ + Name: "system_post_all_public", + DisplayName: "authentication.roles.system_post_all_public.name", + Description: "authentication.roles.system_post_all_public.description", + Permissions: []string{ + PERMISSION_CREATE_POST_PUBLIC.Id, + }, + SchemeManaged: true, + } + + roles[SYSTEM_USER_ACCESS_TOKEN_ROLE_ID] = &Role{ + Name: "system_user_access_token", + DisplayName: "authentication.roles.system_user_access_token.name", + Description: "authentication.roles.system_user_access_token.description", + Permissions: []string{ + PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + PERMISSION_READ_USER_ACCESS_TOKEN.Id, + PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + SchemeManaged: true, + } + + roles[SYSTEM_ADMIN_ROLE_ID] = &Role{ + Name: "system_admin", + DisplayName: "authentication.roles.global_admin.name", + Description: "authentication.roles.global_admin.description", + // System admins can do anything channel and team admins can do + // plus everything members of teams and channels can do to all teams + // and channels on the system + Permissions: append( + append( + append( + append( + []string{ + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + PERMISSION_MANAGE_SYSTEM.Id, + PERMISSION_MANAGE_ROLES.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + PERMISSION_EDIT_OTHER_USERS.Id, + PERMISSION_MANAGE_OAUTH.Id, + PERMISSION_INVITE_USER.Id, + PERMISSION_DELETE_POST.Id, + PERMISSION_DELETE_OTHERS_POSTS.Id, + PERMISSION_CREATE_TEAM.Id, + PERMISSION_ADD_USER_TO_TEAM.Id, + PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, + PERMISSION_MANAGE_JOBS.Id, + PERMISSION_CREATE_POST_PUBLIC.Id, + PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + PERMISSION_READ_USER_ACCESS_TOKEN.Id, + PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + roles[TEAM_USER_ROLE_ID].Permissions..., + ), + roles[CHANNEL_USER_ROLE_ID].Permissions..., + ), + roles[TEAM_ADMIN_ROLE_ID].Permissions..., + ), + roles[CHANNEL_ADMIN_ROLE_ID].Permissions..., + ), + SchemeManaged: true, + } + + return roles +} diff --git a/model/user.go b/model/user.go index 4c08eac24..556ebb1f7 100644 --- a/model/user.go +++ b/model/user.go @@ -432,7 +432,7 @@ func IsValidUserRoles(userRoles string) bool { roles := strings.Fields(userRoles) for _, r := range roles { - if !isValidRole(r) { + if !IsValidRoleName(r) { return false } } @@ -445,11 +445,6 @@ func IsValidUserRoles(userRoles string) bool { return true } -func isValidRole(roleId string) bool { - _, ok := DefaultRoles[roleId] - return ok -} - // Make sure you acually want to use this function. In context.go there are functions to check permissions // This function should not be used to check permissions. func (u *User) IsInRole(inRole string) bool { diff --git a/model/user_test.go b/model/user_test.go index c91051532..5f97b3ff2 100644 --- a/model/user_test.go +++ b/model/user_test.go @@ -263,11 +263,11 @@ func TestCleanUsername(t *testing.T) { func TestRoles(t *testing.T) { - if IsValidUserRoles("admin") { + if !IsValidUserRoles("team_user") { t.Fatal() } - if IsValidUserRoles("junk") { + if IsValidUserRoles("system_admin") { t.Fatal() } diff --git a/model/websocket_message.go b/model/websocket_message.go index bf2535dc3..d0e8cb039 100644 --- a/model/websocket_message.go +++ b/model/websocket_message.go @@ -42,6 +42,7 @@ const ( WEBSOCKET_EVENT_CHANNEL_VIEWED = "channel_viewed" WEBSOCKET_EVENT_PLUGIN_ACTIVATED = "plugin_activated" // EXPERIMENTAL - SUBJECT TO CHANGE WEBSOCKET_EVENT_PLUGIN_DEACTIVATED = "plugin_deactivated" // EXPERIMENTAL - SUBJECT TO CHANGE + WEBSOCKET_EVENT_ROLE_UPDATED = "role_updated" ) type WebSocketMessage interface { diff --git a/store/layered_store.go b/store/layered_store.go index 65b4670c0..cac0f61d3 100644 --- a/store/layered_store.go +++ b/store/layered_store.go @@ -23,6 +23,7 @@ type LayeredStoreDatabaseLayer interface { type LayeredStore struct { TmpContext context.Context ReactionStore ReactionStore + RoleStore RoleStore DatabaseLayer LayeredStoreDatabaseLayer LocalCacheLayer *LocalCacheSupplier RedisLayer *RedisSupplier @@ -37,6 +38,7 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn } store.ReactionStore = &LayeredReactionStore{store} + store.RoleStore = &LayeredRoleStore{store} // Setup the chain if ENABLE_EXPERIMENTAL_REDIS { @@ -161,6 +163,10 @@ func (s *LayeredStore) Plugin() PluginStore { return s.DatabaseLayer.Plugin() } +func (s *LayeredStore) Role() RoleStore { + return s.RoleStore +} + func (s *LayeredStore) MarkSystemRanUnitTests() { s.DatabaseLayer.MarkSystemRanUnitTests() } @@ -218,3 +224,31 @@ func (s *LayeredReactionStore) PermanentDeleteBatch(endTime int64, limit int64) return supplier.ReactionPermanentDeleteBatch(s.TmpContext, endTime, limit) }) } + +type LayeredRoleStore struct { + *LayeredStore +} + +func (s *LayeredRoleStore) Save(role *model.Role) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.RoleSave(s.TmpContext, role) + }) +} + +func (s *LayeredRoleStore) Get(roleId string) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.RoleGet(s.TmpContext, roleId) + }) +} + +func (s *LayeredRoleStore) GetByName(name string) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.RoleGetByName(s.TmpContext, name) + }) +} + +func (s *LayeredRoleStore) GetByNames(names []string) StoreChannel { + return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult { + return supplier.RoleGetByNames(s.TmpContext, names) + }) +} diff --git a/store/layered_store_supplier.go b/store/layered_store_supplier.go index 841b75a32..482ccd126 100644 --- a/store/layered_store_supplier.go +++ b/store/layered_store_supplier.go @@ -31,4 +31,10 @@ type LayeredStoreSupplier interface { ReactionGetForPost(ctx context.Context, postId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + + // Roles + RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult + RoleGetByNames(ctx context.Context, names []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult } diff --git a/store/local_cache_supplier.go b/store/local_cache_supplier.go index 3627c5b39..2343f10a7 100644 --- a/store/local_cache_supplier.go +++ b/store/local_cache_supplier.go @@ -13,7 +13,10 @@ import ( const ( REACTION_CACHE_SIZE = 20000 - REACTION_CACHE_SEC = 1800 // 30 minutes + REACTION_CACHE_SEC = 30 * 60 + + ROLE_CACHE_SIZE = 20000 + ROLE_CACHE_SEC = 30 * 60 CLEAR_CACHE_MESSAGE_DATA = "" ) @@ -21,6 +24,7 @@ const ( type LocalCacheSupplier struct { next LayeredStoreSupplier reactionCache *utils.Cache + roleCache *utils.Cache metrics einterfaces.MetricsInterface cluster einterfaces.ClusterInterface } @@ -28,12 +32,14 @@ type LocalCacheSupplier struct { func NewLocalCacheSupplier(metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) *LocalCacheSupplier { supplier := &LocalCacheSupplier{ reactionCache: utils.NewLruWithParams(REACTION_CACHE_SIZE, "Reaction", REACTION_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS), + roleCache: utils.NewLruWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES), metrics: metrics, cluster: cluster, } if cluster != nil { cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, supplier.handleClusterInvalidateReaction) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, supplier.handleClusterInvalidateRole) } return supplier diff --git a/store/local_cache_supplier_roles.go b/store/local_cache_supplier_roles.go new file mode 100644 index 000000000..a9cbda017 --- /dev/null +++ b/store/local_cache_supplier_roles.go @@ -0,0 +1,68 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "context" + + "github.com/mattermost/mattermost-server/model" +) + +func (s *LocalCacheSupplier) handleClusterInvalidateRole(msg *model.ClusterMessage) { + if msg.Data == CLEAR_CACHE_MESSAGE_DATA { + s.roleCache.Purge() + } else { + s.roleCache.Remove(msg.Data) + } +} + +func (s *LocalCacheSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + if len(role.Id) != 0 { + defer s.doInvalidateCacheCluster(s.roleCache, role.Name) + } + return s.Next().RoleSave(ctx, role, hints...) +} + +func (s *LocalCacheSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + return s.Next().RoleGet(ctx, roleId, hints...) +} + +func (s *LocalCacheSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + if result := s.doStandardReadCache(ctx, s.roleCache, name, hints...); result != nil { + return result + } + + result := s.Next().RoleGetByName(ctx, name, hints...) + + s.doStandardAddToCache(ctx, s.roleCache, name, result, hints...) + + return result +} + +func (s *LocalCacheSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + var foundRoles []*model.Role + var rolesToQuery []string + + for _, roleName := range roleNames { + if result := s.doStandardReadCache(ctx, s.roleCache, roleName, hints...); result != nil { + foundRoles = append(foundRoles, result.Data.(*model.Role)) + } else { + rolesToQuery = append(rolesToQuery, roleName) + } + } + + result := s.Next().RoleGetByNames(ctx, rolesToQuery, hints...) + + if result.Data != nil { + rolesFound := result.Data.([]*model.Role) + for _, role := range rolesFound { + res := NewSupplierResult() + res.Data = role + s.doStandardAddToCache(ctx, s.roleCache, role.Name, res, hints...) + } + result.Data = append(foundRoles, result.Data.([]*model.Role)...) + } + + return result +} diff --git a/store/redis_supplier.go b/store/redis_supplier.go index 32dc12033..b8ec794cf 100644 --- a/store/redis_supplier.go +++ b/store/redis_supplier.go @@ -132,3 +132,23 @@ func (s *RedisSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTim // Ignoring this. It's probably OK to have the emoji slowly expire from Redis. return s.Next().ReactionPermanentDeleteBatch(ctx, endTime, limit, hints...) } + +func (s *RedisSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis Caching. + return s.Next().RoleSave(ctx, role, hints...) +} + +func (s *RedisSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis Caching. + return s.Next().RoleGet(ctx, roleId, hints...) +} + +func (s *RedisSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis Caching. + return s.Next().RoleGetByName(ctx, name, hints...) +} + +func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // TODO: Redis Caching. + return s.Next().RoleGetByNames(ctx, roleNames, hints...) +} diff --git a/store/sqlstore/role_store_test.go b/store/sqlstore/role_store_test.go new file mode 100644 index 000000000..e89930f71 --- /dev/null +++ b/store/sqlstore/role_store_test.go @@ -0,0 +1,14 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package sqlstore + +import ( + "testing" + + "github.com/mattermost/mattermost-server/store/storetest" +) + +func TestRoleStore(t *testing.T) { + StoreTest(t, storetest.TestRoleStore) +} diff --git a/store/sqlstore/role_supplier.go b/store/sqlstore/role_supplier.go new file mode 100644 index 000000000..41eed85e0 --- /dev/null +++ b/store/sqlstore/role_supplier.go @@ -0,0 +1,163 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package sqlstore + +import ( + "context" + "database/sql" + "fmt" + "net/http" + "strings" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" +) + +type Role struct { + Id string + Name string + DisplayName string + Description string + Permissions string + SchemeManaged bool +} + +func NewRoleFromModel(role *model.Role) *Role { + permissionsMap := make(map[string]bool) + permissions := "" + + for _, permission := range role.Permissions { + if !permissionsMap[permission] { + permissions += fmt.Sprintf(" %v", permission) + permissionsMap[permission] = true + } + } + + return &Role{ + Id: role.Id, + Name: role.Name, + DisplayName: role.DisplayName, + Description: role.Description, + Permissions: permissions, + SchemeManaged: role.SchemeManaged, + } +} + +func (role Role) ToModel() *model.Role { + return &model.Role{ + Id: role.Id, + Name: role.Name, + DisplayName: role.DisplayName, + Description: role.Description, + Permissions: strings.Fields(role.Permissions), + SchemeManaged: role.SchemeManaged, + } +} + +func initSqlSupplierRoles(sqlStore SqlStore) { + for _, db := range sqlStore.GetAllConns() { + table := db.AddTableWithName(Role{}, "Roles").SetKeys(false, "Id") + table.ColMap("Name").SetMaxSize(64).SetUnique(true) + table.ColMap("DisplayName").SetMaxSize(128) + table.ColMap("Description").SetMaxSize(1024) + table.ColMap("Permissions").SetMaxSize(4096) + } +} + +func (s *SqlSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + // Check the role is valid before proceeding. + if !role.IsValidWithoutId() { + result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.invalid_role.app_error", nil, "", http.StatusBadRequest) + return result + } + + dbRole := NewRoleFromModel(role) + if len(dbRole.Id) == 0 { + dbRole.Id = model.NewId() + if err := s.GetMaster().Insert(dbRole); err != nil { + result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.insert.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } else { + if rowsChanged, err := s.GetMaster().Update(dbRole); err != nil { + result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.update.app_error", nil, err.Error(), http.StatusInternalServerError) + } else if rowsChanged != 1 { + result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.update.app_error", nil, "no record to update", http.StatusInternalServerError) + } + } + + result.Data = dbRole.ToModel() + + return result +} + +func (s *SqlSupplier) RoleGet(ctx context.Context, roleId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + var dbRole Role + + if err := s.GetReplica().SelectOne(&dbRole, "SELECT * from Roles WHERE Id = :Id", map[string]interface{}{"Id": roleId}); err != nil { + if err == sql.ErrNoRows { + result.Err = model.NewAppError("SqlRoleStore.Get", "store.sql_role.get.app_error", nil, "Id="+roleId+", "+err.Error(), http.StatusNotFound) + } else { + result.Err = model.NewAppError("SqlRoleStore.Get", "store.sql_role.get.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + + result.Data = dbRole.ToModel() + + return result +} + +func (s *SqlSupplier) RoleGetByName(ctx context.Context, name string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + var dbRole Role + + if err := s.GetReplica().SelectOne(&dbRole, "SELECT * from Roles WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil { + if err == sql.ErrNoRows { + result.Err = model.NewAppError("SqlRoleStore.GetByName", "store.sql_role.get_by_name.app_error", nil, "name="+name+",err="+err.Error(), http.StatusNotFound) + } else { + result.Err = model.NewAppError("SqlRoleStore.GetByName", "store.sql_role.get_by_name.app_error", nil, "name="+name+",err="+err.Error(), http.StatusInternalServerError) + } + } + + result.Data = dbRole.ToModel() + + return result +} + +func (s *SqlSupplier) RoleGetByNames(ctx context.Context, names []string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + result := store.NewSupplierResult() + + var dbRoles []*Role + + if len(names) == 0 { + result.Data = []*model.Role{} + return result + } + + var searchPlaceholders []string + var parameters = map[string]interface{}{} + for i, value := range names { + searchPlaceholders = append(searchPlaceholders, fmt.Sprintf(":Name%d", i)) + parameters[fmt.Sprintf("Name%d", i)] = value + } + + searchTerm := "Name IN (" + strings.Join(searchPlaceholders, ", ") + ")" + + if _, err := s.GetReplica().Select(&dbRoles, "SELECT * from Roles WHERE "+searchTerm, parameters); err != nil { + result.Err = model.NewAppError("SqlRoleStore.GetByNames", "store.sql_role.get_by_names.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + var roles []*model.Role + for _, dbRole := range dbRoles { + roles = append(roles, dbRole.ToModel()) + } + + result.Data = roles + + return result +} diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index cfdd7a552..1c623f0b1 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -87,4 +87,5 @@ type SqlStore interface { Job() store.JobStore Plugin() store.PluginStore UserAccessToken() store.UserAccessTokenStore + Role() store.RoleStore } diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 3b9528578..5e43ee0f0 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -86,6 +86,7 @@ type SqlSupplierOldStores struct { userAccessToken store.UserAccessTokenStore plugin store.PluginStore channelMemberHistory store.ChannelMemberHistoryStore + role store.RoleStore } type SqlSupplier struct { @@ -135,6 +136,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter supplier.oldStores.plugin = NewSqlPluginStore(supplier) initSqlSupplierReactions(supplier) + initSqlSupplierRoles(supplier) err := supplier.GetMaster().CreateTablesIfNotExists() if err != nil { @@ -811,6 +813,10 @@ func (ss *SqlSupplier) Plugin() store.PluginStore { return ss.oldStores.plugin } +func (ss *SqlSupplier) Role() store.RoleStore { + return ss.oldStores.role +} + func (ss *SqlSupplier) DropAllTables() { ss.master.TruncateTables() } diff --git a/store/store.go b/store/store.go index 2742c0889..40fd1e23b 100644 --- a/store/store.go +++ b/store/store.go @@ -61,6 +61,7 @@ type Store interface { Status() StatusStore FileInfo() FileInfoStore Reaction() ReactionStore + Role() RoleStore Job() JobStore UserAccessToken() UserAccessTokenStore ChannelMemberHistory() ChannelMemberHistoryStore @@ -461,3 +462,10 @@ type PluginStore interface { Get(pluginId, key string) StoreChannel Delete(pluginId, key string) StoreChannel } + +type RoleStore interface { + Save(role *model.Role) StoreChannel + Get(roleId string) StoreChannel + GetByName(name string) StoreChannel + GetByNames(names []string) StoreChannel +} diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go index 9c66c4aac..d0162a01e 100644 --- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go +++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go @@ -416,6 +416,114 @@ func (_m *LayeredStoreDatabaseLayer) ReactionSave(ctx context.Context, reaction return r0 } +// Role provides a mock function with given fields: +func (_m *LayeredStoreDatabaseLayer) Role() store.RoleStore { + ret := _m.Called() + + var r0 store.RoleStore + if rf, ok := ret.Get(0).(func() store.RoleStore); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.RoleStore) + } + } + + return r0 +} + +// RoleGet provides a mock function with given fields: ctx, roleId, hints +func (_m *LayeredStoreDatabaseLayer) RoleGet(ctx context.Context, roleId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, roleId) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, roleId, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// RoleGetByName provides a mock function with given fields: ctx, name, hints +func (_m *LayeredStoreDatabaseLayer) RoleGetByName(ctx context.Context, name string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, name) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, name, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// RoleGetByNames provides a mock function with given fields: ctx, names, hints +func (_m *LayeredStoreDatabaseLayer) RoleGetByNames(ctx context.Context, names []string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, names) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, []string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, names, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// RoleSave provides a mock function with given fields: ctx, role, hints +func (_m *LayeredStoreDatabaseLayer) RoleSave(ctx context.Context, role *model.Role, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, role) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, *model.Role, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, role, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + // Session provides a mock function with given fields: func (_m *LayeredStoreDatabaseLayer) Session() store.SessionStore { ret := _m.Called() diff --git a/store/storetest/mocks/LayeredStoreSupplier.go b/store/storetest/mocks/LayeredStoreSupplier.go index f4187dae9..59fd31cb8 100644 --- a/store/storetest/mocks/LayeredStoreSupplier.go +++ b/store/storetest/mocks/LayeredStoreSupplier.go @@ -145,6 +145,98 @@ func (_m *LayeredStoreSupplier) ReactionSave(ctx context.Context, reaction *mode return r0 } +// RoleGet provides a mock function with given fields: ctx, roleId, hints +func (_m *LayeredStoreSupplier) RoleGet(ctx context.Context, roleId string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, roleId) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, roleId, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// RoleGetByName provides a mock function with given fields: ctx, name, hints +func (_m *LayeredStoreSupplier) RoleGetByName(ctx context.Context, name string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, name) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, name, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// RoleGetByNames provides a mock function with given fields: ctx, names, hints +func (_m *LayeredStoreSupplier) RoleGetByNames(ctx context.Context, names []string, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, names) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, []string, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, names, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + +// RoleSave provides a mock function with given fields: ctx, role, hints +func (_m *LayeredStoreSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult { + _va := make([]interface{}, len(hints)) + for _i := range hints { + _va[_i] = hints[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, role) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *store.LayeredStoreSupplierResult + if rf, ok := ret.Get(0).(func(context.Context, *model.Role, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok { + r0 = rf(ctx, role, hints...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.LayeredStoreSupplierResult) + } + } + + return r0 +} + // SetChainNext provides a mock function with given fields: _a0 func (_m *LayeredStoreSupplier) SetChainNext(_a0 store.LayeredStoreSupplier) { _m.Called(_a0) diff --git a/store/storetest/mocks/RoleStore.go b/store/storetest/mocks/RoleStore.go new file mode 100644 index 000000000..8150460ae --- /dev/null +++ b/store/storetest/mocks/RoleStore.go @@ -0,0 +1,78 @@ +// Code generated by mockery v1.0.0 + +// Regenerate this file using `make store-mocks`. + +package mocks + +import mock "github.com/stretchr/testify/mock" +import model "github.com/mattermost/mattermost-server/model" +import store "github.com/mattermost/mattermost-server/store" + +// RoleStore is an autogenerated mock type for the RoleStore type +type RoleStore struct { + mock.Mock +} + +// Get provides a mock function with given fields: roleId +func (_m *RoleStore) Get(roleId string) store.StoreChannel { + ret := _m.Called(roleId) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok { + r0 = rf(roleId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + +// GetByName provides a mock function with given fields: name +func (_m *RoleStore) GetByName(name string) store.StoreChannel { + ret := _m.Called(name) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + +// GetByNames provides a mock function with given fields: names +func (_m *RoleStore) GetByNames(names []string) store.StoreChannel { + ret := _m.Called(names) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func([]string) store.StoreChannel); ok { + r0 = rf(names) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + +// Save provides a mock function with given fields: role +func (_m *RoleStore) Save(role *model.Role) store.StoreChannel { + ret := _m.Called(role) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(*model.Role) store.StoreChannel); ok { + r0 = rf(role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go index b9b962101..43709fc0e 100644 --- a/store/storetest/mocks/SqlStore.go +++ b/store/storetest/mocks/SqlStore.go @@ -538,6 +538,22 @@ func (_m *SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, return r0 } +// Role provides a mock function with given fields: +func (_m *SqlStore) Role() store.RoleStore { + ret := _m.Called() + + var r0 store.RoleStore + if rf, ok := ret.Get(0).(func() store.RoleStore); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.RoleStore) + } + } + + return r0 +} + // Session provides a mock function with given fields: func (_m *SqlStore) Session() store.SessionStore { ret := _m.Called() diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go index 40b50a554..cb7e511f6 100644 --- a/store/storetest/mocks/Store.go +++ b/store/storetest/mocks/Store.go @@ -283,6 +283,22 @@ func (_m *Store) Reaction() store.ReactionStore { return r0 } +// Role provides a mock function with given fields: +func (_m *Store) Role() store.RoleStore { + ret := _m.Called() + + var r0 store.RoleStore + if rf, ok := ret.Get(0).(func() store.RoleStore); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.RoleStore) + } + } + + return r0 +} + // Session provides a mock function with given fields: func (_m *Store) Session() store.SessionStore { ret := _m.Called() diff --git a/store/storetest/role_store.go b/store/storetest/role_store.go new file mode 100644 index 000000000..499e36e1e --- /dev/null +++ b/store/storetest/role_store.go @@ -0,0 +1,244 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package storetest + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" +) + +func TestRoleStore(t *testing.T, ss store.Store) { + t.Run("Save", func(t *testing.T) { testRoleStoreSave(t, ss) }) + t.Run("Get", func(t *testing.T) { testRoleStoreGet(t, ss) }) + t.Run("GetByName", func(t *testing.T) { testRoleStoreGetByName(t, ss) }) + t.Run("GetNames", func(t *testing.T) { testRoleStoreGetByNames(t, ss) }) +} + +func testRoleStoreSave(t *testing.T, ss store.Store) { + // Save a new role. + r1 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + + res1 := <-ss.Role().Save(r1) + assert.Nil(t, res1.Err) + d1 := res1.Data.(*model.Role) + assert.Len(t, d1.Id, 26) + assert.Equal(t, r1.Name, d1.Name) + assert.Equal(t, r1.DisplayName, d1.DisplayName) + assert.Equal(t, r1.Description, d1.Description) + assert.Equal(t, r1.Permissions, d1.Permissions) + assert.Equal(t, r1.SchemeManaged, d1.SchemeManaged) + + // Change the role permissions and update. + d1.Permissions = []string{ + "invite_user", + "add_user_to_team", + "delete_public_channel", + } + + res2 := <-ss.Role().Save(d1) + assert.Nil(t, res2.Err) + d2 := res2.Data.(*model.Role) + assert.Len(t, d2.Id, 26) + assert.Equal(t, r1.Name, d2.Name) + assert.Equal(t, r1.DisplayName, d2.DisplayName) + assert.Equal(t, r1.Description, d2.Description) + assert.Equal(t, d1.Permissions, d2.Permissions) + assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged) + + // Try saving one with an invalid ID set. + r3 := &model.Role{ + Id: model.NewId(), + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + + res3 := <-ss.Role().Save(r3) + assert.NotNil(t, res3.Err) + + // Try saving one with a duplicate "name" field. + r4 := &model.Role{ + Name: r1.Name, + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + + res4 := <-ss.Role().Save(r4) + assert.NotNil(t, res4.Err) +} + +func testRoleStoreGet(t *testing.T, ss store.Store) { + // Save a role to test with. + r1 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + + res1 := <-ss.Role().Save(r1) + assert.Nil(t, res1.Err) + d1 := res1.Data.(*model.Role) + assert.Len(t, d1.Id, 26) + + // Get a valid role + res2 := <-ss.Role().Get(d1.Id) + assert.Nil(t, res2.Err) + d2 := res1.Data.(*model.Role) + assert.Equal(t, d1.Id, d2.Id) + assert.Equal(t, r1.Name, d2.Name) + assert.Equal(t, r1.DisplayName, d2.DisplayName) + assert.Equal(t, r1.Description, d2.Description) + assert.Equal(t, r1.Permissions, d2.Permissions) + assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged) + + // Get an invalid role + res3 := <-ss.Role().Get(model.NewId()) + assert.NotNil(t, res3.Err) +} + +func testRoleStoreGetByName(t *testing.T, ss store.Store) { + // Save a role to test with. + r1 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + + res1 := <-ss.Role().Save(r1) + assert.Nil(t, res1.Err) + d1 := res1.Data.(*model.Role) + assert.Len(t, d1.Id, 26) + + // Get a valid role + res2 := <-ss.Role().GetByName(d1.Name) + assert.Nil(t, res2.Err) + d2 := res1.Data.(*model.Role) + assert.Equal(t, d1.Id, d2.Id) + assert.Equal(t, r1.Name, d2.Name) + assert.Equal(t, r1.DisplayName, d2.DisplayName) + assert.Equal(t, r1.Description, d2.Description) + assert.Equal(t, r1.Permissions, d2.Permissions) + assert.Equal(t, r1.SchemeManaged, d2.SchemeManaged) + + // Get an invalid role + res3 := <-ss.Role().GetByName(model.NewId()) + assert.NotNil(t, res3.Err) +} + +func testRoleStoreGetByNames(t *testing.T, ss store.Store) { + // Save some roles to test with. + r1 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + r2 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "read_channel", + "create_public_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + r3 := &model.Role{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Permissions: []string{ + "invite_user", + "delete_private_channel", + "add_user_to_team", + }, + SchemeManaged: false, + } + + res1 := <-ss.Role().Save(r1) + assert.Nil(t, res1.Err) + d1 := res1.Data.(*model.Role) + assert.Len(t, d1.Id, 26) + + res2 := <-ss.Role().Save(r2) + assert.Nil(t, res2.Err) + d2 := res2.Data.(*model.Role) + assert.Len(t, d2.Id, 26) + + res3 := <-ss.Role().Save(r3) + assert.Nil(t, res3.Err) + d3 := res3.Data.(*model.Role) + assert.Len(t, d3.Id, 26) + + // Get two valid roles. + n4 := []string{r1.Name, r2.Name} + res4 := <-ss.Role().GetByNames(n4) + assert.Nil(t, res4.Err) + roles4 := res4.Data.([]*model.Role) + assert.Len(t, roles4, 2) + assert.Contains(t, roles4, d1) + assert.Contains(t, roles4, d2) + assert.NotContains(t, roles4, d3) + + // Get two invalid roles. + n5 := []string{model.NewId(), model.NewId()} + res5 := <-ss.Role().GetByNames(n5) + assert.Nil(t, res5.Err) + roles5 := res5.Data.([]*model.Role) + assert.Len(t, roles5, 0) + + // Get one valid one and one invalid one. + n6 := []string{r1.Name, model.NewId()} + res6 := <-ss.Role().GetByNames(n6) + assert.Nil(t, res6.Err) + roles6 := res6.Data.([]*model.Role) + assert.Len(t, roles6, 1) + assert.Contains(t, roles6, d1) + assert.NotContains(t, roles6, d2) + assert.NotContains(t, roles6, d3) +} diff --git a/store/storetest/store.go b/store/storetest/store.go index 367c5f441..44f426075 100644 --- a/store/storetest/store.go +++ b/store/storetest/store.go @@ -43,6 +43,7 @@ type Store struct { UserAccessTokenStore mocks.UserAccessTokenStore PluginStore mocks.PluginStore ChannelMemberHistoryStore mocks.ChannelMemberHistoryStore + RoleStore mocks.RoleStore } func (s *Store) Team() store.TeamStore { return &s.TeamStore } @@ -68,6 +69,7 @@ func (s *Store) Reaction() store.ReactionStore { return &s.React func (s *Store) Job() store.JobStore { return &s.JobStore } func (s *Store) UserAccessToken() store.UserAccessTokenStore { return &s.UserAccessTokenStore } func (s *Store) Plugin() store.PluginStore { return &s.PluginStore } +func (s *Store) Role() store.RoleStore { return &s.RoleStore } func (s *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore { return &s.ChannelMemberHistoryStore } @@ -104,5 +106,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool { &s.UserAccessTokenStore, &s.ChannelMemberHistoryStore, &s.PluginStore, + &s.RoleStore, ) } diff --git a/utils/authorization.go b/utils/authorization.go index 39a0d606c..b18ece141 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -7,14 +7,7 @@ import ( "github.com/mattermost/mattermost-server/model" ) -func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { - roles := make(map[string]*model.Role) - for id, role := range model.DefaultRoles { - copy := &model.Role{} - *copy = *role - roles[id] = copy - } - +func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Config) map[string]*model.Role { if IsLicensed() { switch *cfg.TeamSettings.RestrictPublicChannelCreation { case model.PERMISSIONS_ALL: @@ -222,8 +215,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { model.PERMISSION_ADD_USER_TO_TEAM.Id, ) } else if *cfg.TeamSettings.RestrictTeamInvite == model.PERMISSIONS_ALL { - roles[model.SYSTEM_USER_ROLE_ID].Permissions = append( - roles[model.SYSTEM_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_INVITE_USER.Id, model.PERMISSION_ADD_USER_TO_TEAM.Id, ) @@ -243,11 +236,6 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_POST.Id, ) - roles[model.CHANNEL_ADMIN_ROLE_ID].Permissions = append( - roles[model.CHANNEL_ADMIN_ROLE_ID].Permissions, - model.PERMISSION_DELETE_POST.Id, - model.PERMISSION_DELETE_OTHERS_POSTS.Id, - ) roles[model.TEAM_ADMIN_ROLE_ID].Permissions = append( roles[model.TEAM_ADMIN_ROLE_ID].Permissions, model.PERMISSION_DELETE_POST.Id, diff --git a/web/web_test.go b/web/web_test.go index 21a7968b3..f43251143 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -52,6 +52,8 @@ func Setup() *app.App { URL = fmt.Sprintf("http://localhost:%v", a.Srv.ListenAddr.Port) ApiClient = model.NewClient(URL) + a.DoAdvancedPermissionsMigration() + a.Srv.Store.MarkSystemRanUnitTests() a.UpdateConfig(func(cfg *model.Config) { -- cgit v1.2.3-1-g7c22 From a735725d116c3e8dca2b4d1cad3425bcd473311c Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Wed, 7 Feb 2018 18:15:07 +0000 Subject: XYZ-59: Implement redis caching layer for Role store. (#8207) * XYZ-59: Implement redis caching layer for Role store. * Use variable for key where used more than once. --- store/local_cache_supplier_roles.go | 2 + store/redis_supplier.go | 67 ---------------------------- store/redis_supplier_reactions.go | 57 ++++++++++++++++++++++++ store/redis_supplier_roles.go | 89 +++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 67 deletions(-) create mode 100644 store/redis_supplier_reactions.go create mode 100644 store/redis_supplier_roles.go diff --git a/store/local_cache_supplier_roles.go b/store/local_cache_supplier_roles.go index a9cbda017..8cbde0a23 100644 --- a/store/local_cache_supplier_roles.go +++ b/store/local_cache_supplier_roles.go @@ -25,6 +25,8 @@ func (s *LocalCacheSupplier) RoleSave(ctx context.Context, role *model.Role, hin } func (s *LocalCacheSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // Roles are cached by name, as that is most commonly how they are looked up. + // This means that no caching is supported on roles being looked up by ID. return s.Next().RoleGet(ctx, roleId, hints...) } diff --git a/store/redis_supplier.go b/store/redis_supplier.go index b8ec794cf..751227be9 100644 --- a/store/redis_supplier.go +++ b/store/redis_supplier.go @@ -5,14 +5,12 @@ package store import ( "bytes" - "context" "encoding/gob" "time" l4g "github.com/alecthomas/log4go" "github.com/go-redis/redis" - "github.com/mattermost/mattermost-server/model" ) const REDIS_EXPIRY_TIME = 30 * time.Minute @@ -87,68 +85,3 @@ func (s *RedisSupplier) SetChainNext(next LayeredStoreSupplier) { func (s *RedisSupplier) Next() LayeredStoreSupplier { return s.next } - -func (s *RedisSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil { - l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) - } - return s.Next().ReactionSave(ctx, reaction, hints...) -} - -func (s *RedisSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil { - l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) - } - return s.Next().ReactionDelete(ctx, reaction, hints...) -} - -func (s *RedisSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - var resultdata []*model.Reaction - found, err := s.load("reactions:"+postId, &resultdata) - if found { - result := NewSupplierResult() - result.Data = resultdata - return result - } - if err != nil { - l4g.Error("Redis encountered an error on read: " + err.Error()) - } - - result := s.Next().ReactionGetForPost(ctx, postId, hints...) - - if err := s.save("reactions:"+postId, result.Data, REDIS_EXPIRY_TIME); err != nil { - l4g.Error("Redis encountered and error on write: " + err.Error()) - } - - return result -} - -func (s *RedisSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - // Ignoring this. It's probably OK to have the emoji slowly expire from Redis. - return s.Next().ReactionDeleteAllWithEmojiName(ctx, emojiName, hints...) -} - -func (s *RedisSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - // Ignoring this. It's probably OK to have the emoji slowly expire from Redis. - return s.Next().ReactionPermanentDeleteBatch(ctx, endTime, limit, hints...) -} - -func (s *RedisSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - // TODO: Redis Caching. - return s.Next().RoleSave(ctx, role, hints...) -} - -func (s *RedisSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - // TODO: Redis Caching. - return s.Next().RoleGet(ctx, roleId, hints...) -} - -func (s *RedisSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - // TODO: Redis Caching. - return s.Next().RoleGetByName(ctx, name, hints...) -} - -func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { - // TODO: Redis Caching. - return s.Next().RoleGetByNames(ctx, roleNames, hints...) -} diff --git a/store/redis_supplier_reactions.go b/store/redis_supplier_reactions.go new file mode 100644 index 000000000..cece8113d --- /dev/null +++ b/store/redis_supplier_reactions.go @@ -0,0 +1,57 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "context" + + l4g "github.com/alecthomas/log4go" + + "github.com/mattermost/mattermost-server/model" +) + +func (s *RedisSupplier) ReactionSave(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil { + l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) + } + return s.Next().ReactionSave(ctx, reaction, hints...) +} + +func (s *RedisSupplier) ReactionDelete(ctx context.Context, reaction *model.Reaction, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + if err := s.client.Del("reactions:" + reaction.PostId).Err(); err != nil { + l4g.Error("Redis failed to remove key reactions:" + reaction.PostId + " Error: " + err.Error()) + } + return s.Next().ReactionDelete(ctx, reaction, hints...) +} + +func (s *RedisSupplier) ReactionGetForPost(ctx context.Context, postId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + var resultdata []*model.Reaction + found, err := s.load("reactions:"+postId, &resultdata) + if found { + result := NewSupplierResult() + result.Data = resultdata + return result + } + if err != nil { + l4g.Error("Redis encountered an error on read: " + err.Error()) + } + + result := s.Next().ReactionGetForPost(ctx, postId, hints...) + + if err := s.save("reactions:"+postId, result.Data, REDIS_EXPIRY_TIME); err != nil { + l4g.Error("Redis encountered and error on write: " + err.Error()) + } + + return result +} + +func (s *RedisSupplier) ReactionDeleteAllWithEmojiName(ctx context.Context, emojiName string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // Ignoring this. It's probably OK to have the emoji slowly expire from Redis. + return s.Next().ReactionDeleteAllWithEmojiName(ctx, emojiName, hints...) +} + +func (s *RedisSupplier) ReactionPermanentDeleteBatch(ctx context.Context, endTime int64, limit int64, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // Ignoring this. It's probably OK to have the emoji slowly expire from Redis. + return s.Next().ReactionPermanentDeleteBatch(ctx, endTime, limit, hints...) +} diff --git a/store/redis_supplier_roles.go b/store/redis_supplier_roles.go new file mode 100644 index 000000000..170420f1f --- /dev/null +++ b/store/redis_supplier_roles.go @@ -0,0 +1,89 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "context" + "fmt" + + l4g "github.com/alecthomas/log4go" + + "github.com/mattermost/mattermost-server/model" +) + +func (s *RedisSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + key := buildRedisKeyForRoleName(role.Name) + + if err := s.client.Del(key).Err(); err != nil { + l4g.Error("Redis failed to remove key " + key + " Error: " + err.Error()) + } + + return s.Next().RoleSave(ctx, role, hints...) +} + +func (s *RedisSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + // Roles are cached by name, as that is most commonly how they are looked up. + // This means that no caching is supported on roles being looked up by ID. + return s.Next().RoleGet(ctx, roleId, hints...) +} + +func (s *RedisSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + key := buildRedisKeyForRoleName(name) + + var role *model.Role + found, err := s.load(key, &role) + if err != nil { + l4g.Error("Redis encountered an error on read: " + err.Error()) + } else if found { + result := NewSupplierResult() + result.Data = role + return result + } + + result := s.Next().RoleGetByName(ctx, name, hints...) + + if result.Err == nil { + if err := s.save(key, result.Data, REDIS_EXPIRY_TIME); err != nil { + l4g.Error("Redis encountered and error on write: " + err.Error()) + } + } + + return result +} + +func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) *LayeredStoreSupplierResult { + var foundRoles []*model.Role + var rolesToQuery []string + + for _, roleName := range roleNames { + var role *model.Role + found, err := s.load(buildRedisKeyForRoleName(roleName), &role) + if err == nil && found { + foundRoles = append(foundRoles, role) + } else { + rolesToQuery = append(rolesToQuery, roleName) + if err != nil { + l4g.Error("Redis encountered an error on read: " + err.Error()) + } + } + } + + result := s.Next().RoleGetByNames(ctx, rolesToQuery, hints...) + + if result.Err == nil { + rolesFound := result.Data.([]*model.Role) + for _, role := range rolesFound { + if err := s.save(buildRedisKeyForRoleName(role.Name), role, REDIS_EXPIRY_TIME); err != nil { + l4g.Error("Redis encountered and error on write: " + err.Error()) + } + } + result.Data = append(foundRoles, result.Data.([]*model.Role)...) + } + + return result +} + +func buildRedisKeyForRoleName(roleName string) string { + return fmt.Sprintf("roles:%s", roleName) +} -- cgit v1.2.3-1-g7c22 From fa5cba9cc79b3bdc48ad42f276652bc699795a39 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Thu, 8 Feb 2018 16:07:40 +0000 Subject: XYZ-76: Add license check to patchRoles endpoint. (#8224) --- api4/role.go | 26 ++++++++++++++++++++++++++ api4/role_test.go | 37 ++++++++++++++++++++++++++++++++++--- i18n/en.json | 4 ++++ model/role.go | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/api4/role.go b/api4/role.go index a401a8034..ac9364c35 100644 --- a/api4/role.go +++ b/api4/role.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitRole() { @@ -85,6 +86,31 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !utils.IsLicensed() && patch.Permissions != nil { + allowedPermissions := []string{ + model.PERMISSION_CREATE_TEAM.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OAUTH.Id, + model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + } + + changedPermissions := model.PermissionsChangedByPatch(oldRole, patch) + for _, permission := range changedPermissions { + allowed := false + for _, allowedPermission := range allowedPermissions { + if permission == allowedPermission { + allowed = true + } + } + + if !allowed { + c.Err = model.NewAppError("Api4.PatchRoles", "api.roles.patch_roles.license.error", nil, "", http.StatusNotImplemented) + return + } + } + } + if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return diff --git a/api4/role_test.go b/api4/role_test.go index 64b8303e2..a3e6d35be 100644 --- a/api4/role_test.go +++ b/api4/role_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" ) func TestGetRole(t *testing.T) { @@ -146,7 +147,7 @@ func TestPatchRole(t *testing.T) { Name: model.NewId(), DisplayName: model.NewId(), Description: model.NewId(), - Permissions: []string{"manage_system", "create_public_channel"}, + Permissions: []string{"manage_system", "create_public_channel", "manage_slash_commands"}, SchemeManaged: true, } @@ -156,7 +157,7 @@ func TestPatchRole(t *testing.T) { defer th.App.Srv.Store.Job().Delete(role.Id) patch := &model.RolePatch{ - Permissions: &[]string{"manage_system", "delete_public_channel"}, + Permissions: &[]string{"manage_system", "create_public_channel", "manage_webhooks"}, } received, resp := th.SystemAdminClient.PatchRole(role.Id, patch) @@ -166,7 +167,7 @@ func TestPatchRole(t *testing.T) { assert.Equal(t, received.Name, role.Name) assert.Equal(t, received.DisplayName, role.DisplayName) assert.Equal(t, received.Description, role.Description) - assert.EqualValues(t, received.Permissions, []string{"manage_system", "delete_public_channel"}) + assert.EqualValues(t, received.Permissions, []string{"manage_system", "create_public_channel", "manage_webhooks"}) assert.Equal(t, received.SchemeManaged, role.SchemeManaged) // Check a no-op patch succeeds. @@ -181,4 +182,34 @@ func TestPatchRole(t *testing.T) { received, resp = th.Client.PatchRole(role.Id, patch) CheckForbiddenStatus(t, resp) + + // Check a change that the license would not allow. + patch = &model.RolePatch{ + Permissions: &[]string{"manage_system", "manage_webhooks"}, + } + + received, resp = th.SystemAdminClient.PatchRole(role.Id, patch) + CheckNotImplementedStatus(t, resp) + + // Add a license. + isLicensed := utils.IsLicensed() + license := utils.License() + defer func() { + utils.SetIsLicensed(isLicensed) + utils.SetLicense(license) + }() + utils.SetIsLicensed(true) + utils.SetLicense(&model.License{Features: &model.Features{}}) + utils.License().Features.SetDefaults() + + // Try again, should succeed + received, resp = th.SystemAdminClient.PatchRole(role.Id, patch) + CheckNoError(t, resp) + + assert.Equal(t, received.Id, role.Id) + assert.Equal(t, received.Name, role.Name) + assert.Equal(t, received.DisplayName, role.DisplayName) + assert.Equal(t, received.Description, role.Description) + assert.EqualValues(t, received.Permissions, []string{"manage_system", "manage_webhooks"}) + assert.Equal(t, received.SchemeManaged, role.SchemeManaged) } diff --git a/i18n/en.json b/i18n/en.json index 4708f321e..76f3d7ea6 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -47,6 +47,10 @@ "id": "September", "translation": "September" }, + { + "id": "api.roles.patch_roles.license.error", + "translation": "Your current license does not support advanced permissions." + }, { "id": "api.admin.add_certificate.no_file.app_error", "translation": "No file under 'certificate' in request." diff --git a/model/role.go b/model/role.go index 254513ae0..eedf97d25 100644 --- a/model/role.go +++ b/model/role.go @@ -81,6 +81,41 @@ func (o *Role) Patch(patch *RolePatch) { } } +// Returns an array of permissions that are in either role.Permissions +// or patch.Permissions, but not both. +func PermissionsChangedByPatch(role *Role, patch *RolePatch) []string { + var result []string + + if patch.Permissions == nil { + return result + } + + roleMap := make(map[string]bool) + patchMap := make(map[string]bool) + + for _, permission := range role.Permissions { + roleMap[permission] = true + } + + for _, permission := range *patch.Permissions { + patchMap[permission] = true + } + + for _, permission := range role.Permissions { + if !patchMap[permission] { + result = append(result, permission) + } + } + + for _, permission := range *patch.Permissions { + if !roleMap[permission] { + result = append(result, permission) + } + } + + return result +} + func (role *Role) IsValid() bool { if len(role.Id) != 26 { return false -- cgit v1.2.3-1-g7c22 From 96ffde43dc5dccef7af106dc8200566ff16ba1dc Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Thu, 8 Feb 2018 16:08:47 +0000 Subject: XYZ-80: Add CreateAt/UpdateAt/DeleteAt fields to roles table. (#8223) --- model/role.go | 3 +++ store/sqlstore/role_supplier.go | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/model/role.go b/model/role.go index eedf97d25..281304801 100644 --- a/model/role.go +++ b/model/role.go @@ -34,6 +34,9 @@ type Role struct { Name string `json:"name"` DisplayName string `json:"display_name"` Description string `json:"description"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` Permissions []string `json:"permissions"` SchemeManaged bool `json:"scheme_managed"` } diff --git a/store/sqlstore/role_supplier.go b/store/sqlstore/role_supplier.go index 41eed85e0..f9ce53788 100644 --- a/store/sqlstore/role_supplier.go +++ b/store/sqlstore/role_supplier.go @@ -19,6 +19,9 @@ type Role struct { Name string DisplayName string Description string + CreateAt int64 + UpdateAt int64 + DeleteAt int64 Permissions string SchemeManaged bool } @@ -39,6 +42,9 @@ func NewRoleFromModel(role *model.Role) *Role { Name: role.Name, DisplayName: role.DisplayName, Description: role.Description, + CreateAt: role.CreateAt, + UpdateAt: role.UpdateAt, + DeleteAt: role.DeleteAt, Permissions: permissions, SchemeManaged: role.SchemeManaged, } @@ -50,6 +56,9 @@ func (role Role) ToModel() *model.Role { Name: role.Name, DisplayName: role.DisplayName, Description: role.Description, + CreateAt: role.CreateAt, + UpdateAt: role.UpdateAt, + DeleteAt: role.DeleteAt, Permissions: strings.Fields(role.Permissions), SchemeManaged: role.SchemeManaged, } @@ -77,10 +86,13 @@ func (s *SqlSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...s dbRole := NewRoleFromModel(role) if len(dbRole.Id) == 0 { dbRole.Id = model.NewId() + dbRole.CreateAt = model.GetMillis() + dbRole.UpdateAt = dbRole.CreateAt if err := s.GetMaster().Insert(dbRole); err != nil { result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.insert.app_error", nil, err.Error(), http.StatusInternalServerError) } } else { + dbRole.UpdateAt = model.GetMillis() if rowsChanged, err := s.GetMaster().Update(dbRole); err != nil { result.Err = model.NewAppError("SqlRoleStore.Save", "store.sql_role.save.update.app_error", nil, err.Error(), http.StatusInternalServerError) } else if rowsChanged != 1 { -- cgit v1.2.3-1-g7c22 From 0aa7ecd5e89f054ae927b246f2aec4bd6348d42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Fri, 9 Feb 2018 16:31:01 +0100 Subject: AllowEditPost and PostEditTimeLimit migration (#8208) * AllowEditPost and PostEditTimeLimit migration * Not set EDIT_POST permission to sysadmin_role if ALLOW_EDIT_POST is configured to NEVER * Remove a bit of code duplication --- api/post_test.go | 13 +++++++++---- api4/post_test.go | 7 ------- app/app_test.go | 8 ++++---- app/apptestlib.go | 1 + app/post.go | 9 +-------- app/post_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ config/default.json | 2 +- model/config.go | 2 +- model/role.go | 1 - utils/authorization.go | 23 +++++++++++++++++++++++ 10 files changed, 86 insertions(+), 26 deletions(-) diff --git a/api/post_test.go b/api/post_test.go index fe0c2a6b7..b88c733db 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -395,7 +395,13 @@ func TestUpdatePost(t *testing.T) { Client := th.BasicClient channel1 := th.BasicChannel - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + th.AddPermissionToRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID) post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} rpost1, err := Client.CreatePost(post1) @@ -474,8 +480,7 @@ func TestUpdatePost(t *testing.T) { utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_NEVER }) - + th.RemovePermissionFromRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID) post4 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} rpost4, err := Client.CreatePost(post4) if err != nil { @@ -487,7 +492,7 @@ func TestUpdatePost(t *testing.T) { t.Fatal("shouldn't have been able to update a message when not allowed") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_TIME_LIMIT }) + th.AddPermissionToRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.PostEditTimeLimit = 1 }) //seconds post5 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} diff --git a/api4/post_test.go b/api4/post_test.go index de627d8bc..52326c4b0 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -489,16 +489,13 @@ func TestUpdatePost(t *testing.T) { isLicensed := utils.IsLicensed() license := utils.License() - allowEditPost := *th.App.Config().ServiceSettings.AllowEditPost defer func() { utils.SetIsLicensed(isLicensed) utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) }() utils.SetIsLicensed(true) utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) post := &model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a"} rpost, resp := Client.CreatePost(post) @@ -571,18 +568,14 @@ func TestPatchPost(t *testing.T) { isLicensed := utils.IsLicensed() license := utils.License() - allowEditPost := *th.App.Config().ServiceSettings.AllowEditPost defer func() { utils.SetIsLicensed(isLicensed) utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) }() utils.SetIsLicensed(true) utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License().Features.SetDefaults() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - post := &model.Post{ ChannelId: channel.Id, IsPinned: true, diff --git a/app/app_test.go b/app/app_test.go index 09e002791..bbc012364 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -109,10 +109,10 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, - model.PERMISSION_EDIT_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_EDIT_POST.Id, }, "channel_admin": []string{ model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, @@ -203,7 +203,6 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, - model.PERMISSION_EDIT_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, model.PERMISSION_EDIT_OTHERS_POSTS.Id, model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, @@ -214,6 +213,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_EDIT_POST.Id, }, } @@ -274,10 +274,10 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, - model.PERMISSION_EDIT_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_EDIT_POST.Id, }, "channel_admin": []string{ model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, @@ -368,7 +368,6 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, - model.PERMISSION_EDIT_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, model.PERMISSION_EDIT_OTHERS_POSTS.Id, model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, @@ -379,6 +378,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_EDIT_POST.Id, }, } diff --git a/app/apptestlib.go b/app/apptestlib.go index 9aef50ce6..7a2d7157d 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -232,6 +232,7 @@ func (me *TestHelper) CreatePost(channel *model.Channel) *model.Post { UserId: me.BasicUser.Id, ChannelId: channel.Id, Message: "message_" + id, + CreateAt: model.GetMillis() - 10000, } utils.DisableDebugLogForTest() diff --git a/app/post.go b/app/post.go index 01abb21cf..843319082 100644 --- a/app/post.go +++ b/app/post.go @@ -332,13 +332,6 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model } else { oldPost = result.Data.(*model.PostList).Posts[post.Id] - if a.License() != nil { - if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER && post.Message != oldPost.Message { - err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden) - return nil, err - } - } - if oldPost == nil { err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) return nil, err @@ -355,7 +348,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model } if a.License() != nil { - if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { + if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) return nil, err } diff --git a/app/post_test.go b/app/post_test.go index 3f3783265..049d3ff92 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" ) func TestUpdatePostEditAt(t *testing.T) { @@ -43,6 +44,51 @@ func TestUpdatePostEditAt(t *testing.T) { } } +func TestUpdatePostTimeLimit(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + post := &model.Post{} + *post = *th.BasicPost + + isLicensed := utils.IsLicensed() + license := utils.License() + defer func() { + utils.SetIsLicensed(isLicensed) + utils.SetLicense(license) + }() + utils.SetIsLicensed(true) + utils.SetLicense(&model.License{Features: &model.Features{}}) + utils.License().Features.SetDefaults() + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.PostEditTimeLimit = -1 + }) + if _, err := th.App.UpdatePost(post, true); err != nil { + t.Fatal(err) + } + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.PostEditTimeLimit = 1000000000 + }) + post.Message = model.NewId() + if _, err := th.App.UpdatePost(post, true); err != nil { + t.Fatal("should allow you to edit the post") + } + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.PostEditTimeLimit = 1 + }) + post.Message = model.NewId() + if _, err := th.App.UpdatePost(post, true); err == nil { + t.Fatal("should fail on update old post") + } + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.PostEditTimeLimit = -1 + }) +} + func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) { // This test ensures that when replying to a root post made by a user who has since left the channel, the reply // post completes successfully. This is a regression test for PLT-6523. diff --git a/config/default.json b/config/default.json index 934635cb9..0fa8f235f 100644 --- a/config/default.json +++ b/config/default.json @@ -45,7 +45,7 @@ "RestrictCustomEmojiCreation": "all", "RestrictPostDelete": "all", "AllowEditPost": "always", - "PostEditTimeLimit": 300, + "PostEditTimeLimit": -1, "ExperimentalEnableAuthenticationTransfer": true, "TimeBetweenUserTypingUpdatesMilliseconds": 5000, "EnablePostSearch": true, diff --git a/model/config.go b/model/config.go index 9010eaeae..8fb11d403 100644 --- a/model/config.go +++ b/model/config.go @@ -419,7 +419,7 @@ func (s *ServiceSettings) SetDefaults() { } if s.PostEditTimeLimit == nil { - s.PostEditTimeLimit = NewInt(300) + s.PostEditTimeLimit = NewInt(-1) } if s.EnablePreviewFeatures == nil { diff --git a/model/role.go b/model/role.go index 281304801..5f7352f12 100644 --- a/model/role.go +++ b/model/role.go @@ -182,7 +182,6 @@ func MakeDefaultRoles() map[string]*Role { PERMISSION_UPLOAD_FILE.Id, PERMISSION_GET_PUBLIC_LINK.Id, PERMISSION_CREATE_POST.Id, - PERMISSION_EDIT_POST.Id, PERMISSION_USE_SLASH_COMMANDS.Id, }, SchemeManaged: true, diff --git a/utils/authorization.go b/utils/authorization.go index b18ece141..b17e94587 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -267,5 +267,28 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } + if IsLicensed() { + switch *cfg.ServiceSettings.AllowEditPost { + case model.ALLOW_EDIT_POST_ALWAYS, model.ALLOW_EDIT_POST_TIME_LIMIT: + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, + model.PERMISSION_EDIT_POST.Id, + ) + roles[model.SYSTEM_ADMIN_ROLE_ID].Permissions = append( + roles[model.SYSTEM_ADMIN_ROLE_ID].Permissions, + model.PERMISSION_EDIT_POST.Id, + ) + } + } else { + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, + model.PERMISSION_EDIT_POST.Id, + ) + roles[model.SYSTEM_ADMIN_ROLE_ID].Permissions = append( + roles[model.SYSTEM_ADMIN_ROLE_ID].Permissions, + model.PERMISSION_EDIT_POST.Id, + ) + } + return roles } -- cgit v1.2.3-1-g7c22 From 3b83cc7dd3fc8c6281bbd74b5b85a6a06efcbb6d Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Fri, 9 Feb 2018 10:57:07 -0500 Subject: XYZ-51: Unit tests for and changes to SetRolePermissionsFromConfig. (#8160) * XYZ-10: Role store. * XYZ-37: Update unit tests to work with database roles. * XYZ-51: Tests 'SetRolePermissionsFromConfig' against JSON from policy page. * XYZ-51: Adds permissions in non-licensed cases also. * XYZ-51: Removes some permissions from team_user role. * XYZ-51: Merge fix for change to default permissions from PR 8208. * XYZ-51: Removes unused function. --- app/app.go | 2 +- app/app_test.go | 5 +- utils/authorization.go | 54 ++-- utils/authorization_test.go | 125 ++++++++++ utils/policies-roles-mapping.json | 510 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 667 insertions(+), 29 deletions(-) create mode 100644 utils/authorization_test.go create mode 100644 utils/policies-roles-mapping.json diff --git a/app/app.go b/app/app.go index 4cc9ff7df..ffb903b57 100644 --- a/app/app.go +++ b/app/app.go @@ -460,7 +460,7 @@ func (a *App) DoAdvancedPermissionsMigration() { l4g.Info("Migrating roles to database.") roles := model.MakeDefaultRoles() - roles = utils.SetRolePermissionsFromConfig(roles, a.Config()) + roles = utils.SetRolePermissionsFromConfig(roles, a.Config(), utils.IsLicensed()) allSucceeded := true diff --git a/app/app_test.go b/app/app_test.go index bbc012364..180bd21ee 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -5,6 +5,7 @@ package app import ( "flag" + "fmt" "os" "testing" @@ -275,6 +276,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.PERMISSION_DELETE_POST.Id, model.PERMISSION_EDIT_POST.Id, @@ -389,7 +392,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { for name, permissions := range expected2 { role, err := th.App.GetRoleByName(name) assert.Nil(t, err) - assert.Equal(t, permissions, role.Permissions) + assert.Equal(t, permissions, role.Permissions, fmt.Sprintf("'%v' did not have expected permissions", name)) } // Remove the license. diff --git a/utils/authorization.go b/utils/authorization.go index b17e94587..e8556458a 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -7,8 +7,8 @@ import ( "github.com/mattermost/mattermost-server/model" ) -func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Config) map[string]*model.Role { - if IsLicensed() { +func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Config, isLicensed bool) map[string]*model.Role { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelCreation { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -28,11 +28,11 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelManagement { case model.PERMISSIONS_ALL: - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -51,17 +51,17 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelDeletion { case model.PERMISSIONS_ALL: - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -80,13 +80,13 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelCreation { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -106,11 +106,11 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManagement { case model.PERMISSIONS_ALL: - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -129,17 +129,17 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelDeletion { case model.PERMISSIONS_ALL: - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -158,14 +158,14 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.TEAM_USER_ROLE_ID].Permissions = append( - roles[model.TEAM_USER_ROLE_ID].Permissions, + roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( + roles[model.CHANNEL_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, ) } // Restrict permissions for Private Channel Manage Members - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManageMembers { case model.PERMISSIONS_ALL: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( @@ -207,7 +207,7 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi } // Grant permissions for inviting and adding users to a team. - if IsLicensed() { + if isLicensed { if *cfg.TeamSettings.RestrictTeamInvite == model.PERMISSIONS_TEAM_ADMIN { roles[model.TEAM_ADMIN_ROLE_ID].Permissions = append( roles[model.TEAM_ADMIN_ROLE_ID].Permissions, @@ -229,7 +229,7 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } - if IsLicensed() { + if isLicensed { switch *cfg.ServiceSettings.RestrictPostDelete { case model.PERMISSIONS_DELETE_POST_ALL: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( @@ -267,7 +267,7 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } - if IsLicensed() { + if isLicensed { switch *cfg.ServiceSettings.AllowEditPost { case model.ALLOW_EDIT_POST_ALWAYS, model.ALLOW_EDIT_POST_TIME_LIMIT: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( diff --git a/utils/authorization_test.go b/utils/authorization_test.go new file mode 100644 index 000000000..9e5b570bb --- /dev/null +++ b/utils/authorization_test.go @@ -0,0 +1,125 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" +) + +type RoleState struct { + RoleName string `json:"roleName"` + Permission string `json:"permission"` + ShouldHave bool `json:"shouldHave"` +} + +func mockConfig() *model.Config { + config := model.Config{} + config.SetDefaults() + return &config +} + +func mapping() (map[string]map[string][]RoleState, error) { + + policiesRolesMapping := make(map[string]map[string][]RoleState) + + raw, err := ioutil.ReadFile("./policies-roles-mapping.json") + if err != nil { + return policiesRolesMapping, err + } + + var f map[string]interface{} + err = json.Unmarshal(raw, &f) + if err != nil { + return policiesRolesMapping, err + } + + for policyName, value := range f { + + capitalizedName := fmt.Sprintf("%v%v", strings.ToUpper(policyName[:1]), policyName[1:]) + policiesRolesMapping[capitalizedName] = make(map[string][]RoleState) + + for policyValue, roleStatesMappings := range value.(map[string]interface{}) { + + var roleStates []RoleState + for _, roleStateMapping := range roleStatesMappings.([]interface{}) { + + roleStateMappingJSON, _ := json.Marshal(roleStateMapping) + var roleState RoleState + _ = json.Unmarshal(roleStateMappingJSON, &roleState) + + roleStates = append(roleStates, roleState) + + } + + policiesRolesMapping[capitalizedName][policyValue] = roleStates + + } + + } + + return policiesRolesMapping, nil +} + +func TestSetRolePermissionsFromConfig(t *testing.T) { + + mapping, err := mapping() + if err != nil { + require.NoError(t, err) + } + + for policyName, v := range mapping { + for policyValue, rolesMappings := range v { + + config := mockConfig() + updateConfig(config, policyName, policyValue) + roles := model.MakeDefaultRoles() + SetRolePermissionsFromConfig(roles, config, true) + + for _, roleMappingItem := range rolesMappings { + role := roles[roleMappingItem.RoleName] + + permission := roleMappingItem.Permission + hasPermission := roleHasPermission(role, permission) + + if (roleMappingItem.ShouldHave && !hasPermission) || (!roleMappingItem.ShouldHave && hasPermission) { + wording := "not to" + if roleMappingItem.ShouldHave { + wording = "to" + } + t.Errorf("Expected '%v' %v have '%v' permission when '%v' is set to '%v'.", role.Name, wording, permission, policyName, policyValue) + } + + } + + } + } +} + +func updateConfig(config *model.Config, key string, value string) { + v := reflect.ValueOf(config.ServiceSettings) + field := v.FieldByName(key) + if !field.IsValid() { + v = reflect.ValueOf(config.TeamSettings) + field = v.FieldByName(key) + } + field.Elem().SetString(value) +} + +func roleHasPermission(role *model.Role, permission string) bool { + for _, p := range role.Permissions { + if p == permission { + return true + } + } + return false +} diff --git a/utils/policies-roles-mapping.json b/utils/policies-roles-mapping.json new file mode 100644 index 000000000..f083a854a --- /dev/null +++ b/utils/policies-roles-mapping.json @@ -0,0 +1,510 @@ +{ + "restrictTeamInvite": { + "all": [ + { + "roleName": "team_user", + "permission": "invite_user", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "team_user", + "permission": "invite_user", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "invite_user", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "team_user", + "permission": "invite_user", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "invite_user", + "shouldHave": false + } + ] + }, + "restrictPublicChannelCreation": { + "all": [ + { + "roleName": "team_user", + "permission": "create_public_channel", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "team_user", + "permission": "create_public_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "create_public_channel", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "team_user", + "permission": "create_public_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "create_public_channel", + "shouldHave": false + } + ] + }, + "restrictPrivateChannelCreation": { + "all": [ + { + "roleName": "team_user", + "permission": "create_private_channel", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "team_user", + "permission": "create_private_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "create_private_channel", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "team_user", + "permission": "create_private_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "create_private_channel", + "shouldHave": false + } + ] + }, + "restrictPublicChannelManagement": { + "all": [ + { + "roleName": "channel_user", + "permission": "manage_public_channel_properties", + "shouldHave": true + } + ], + "channel_admin": [ + { + "roleName": "channel_user", + "permission": "manage_public_channel_properties", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_public_channel_properties", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "manage_public_channel_properties", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "channel_user", + "permission": "manage_public_channel_properties", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_public_channel_properties", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "manage_public_channel_properties", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "channel_user", + "permission": "manage_public_channel_properties", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_public_channel_properties", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "manage_public_channel_properties", + "shouldHave": false + } + ] + }, + "restrictPublicChannelDeletion": { + "all": [ + { + "roleName": "channel_user", + "permission": "delete_public_channel", + "shouldHave": true + } + ], + "channel_admin": [ + { + "roleName": "channel_user", + "permission": "delete_public_channel", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_public_channel", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "delete_public_channel", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "channel_user", + "permission": "delete_public_channel", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_public_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_public_channel", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "channel_user", + "permission": "delete_public_channel", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_public_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_public_channel", + "shouldHave": false + } + ] + }, + "restrictPrivateChannelManagement": { + "all": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_properties", + "shouldHave": true + } + ], + "channel_admin": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_properties", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_private_channel_properties", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "manage_private_channel_properties", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_properties", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_private_channel_properties", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "manage_private_channel_properties", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_properties", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_private_channel_properties", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "manage_private_channel_properties", + "shouldHave": false + } + ] + }, + "restrictPrivateChannelManageMembers": { + "all": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_members", + "shouldHave": true + } + ], + "channel_admin": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_members", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_private_channel_members", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "manage_private_channel_members", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_members", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_private_channel_members", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "manage_private_channel_members", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "channel_user", + "permission": "manage_private_channel_members", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "manage_private_channel_members", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "manage_private_channel_members", + "shouldHave": false + } + ] + }, + "restrictPrivateChannelDeletion": { + "all": [ + { + "roleName": "channel_user", + "permission": "delete_private_channel", + "shouldHave": true + } + ], + "channel_admin": [ + { + "roleName": "channel_user", + "permission": "delete_private_channel", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_private_channel", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "delete_private_channel", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "channel_user", + "permission": "delete_private_channel", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_private_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_private_channel", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "channel_user", + "permission": "delete_private_channel", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_private_channel", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_private_channel", + "shouldHave": false + } + ] + }, + "allowEditPost": { + "always": [ + { + "roleName": "channel_user", + "permission": "edit_post", + "shouldHave": true + }, + { + "roleName": "system_admin", + "permission": "edit_post", + "shouldHave": true + } + ], + "never": [ + { + "roleName": "channel_user", + "permission": "edit_post", + "shouldHave": false + }, + { + "roleName": "system_admin", + "permission": "edit_post", + "shouldHave": false + } + ] + }, + "restrictPostDelete": { + "all": [ + { + "roleName": "channel_user", + "permission": "delete_post", + "shouldHave": true + }, + { + "roleName": "channel_admin", + "permission": "delete_post", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_others_posts", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_post", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "delete_others_posts", + "shouldHave": true + } + ], + "team_admin": [ + { + "roleName": "channel_user", + "permission": "delete_post", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_post", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_others_posts", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_post", + "shouldHave": true + }, + { + "roleName": "team_admin", + "permission": "delete_others_posts", + "shouldHave": true + } + ], + "system_admin": [ + { + "roleName": "channel_user", + "permission": "delete_post", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_post", + "shouldHave": false + }, + { + "roleName": "channel_admin", + "permission": "delete_others_posts", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_post", + "shouldHave": false + }, + { + "roleName": "team_admin", + "permission": "delete_others_posts", + "shouldHave": false + } + ] + } +} \ No newline at end of file -- cgit v1.2.3-1-g7c22 From 1edcabbc9bd2e571dd72a2df699bc2979274add2 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Mon, 12 Feb 2018 13:52:42 +0000 Subject: Fix tests. (#8244) --- api/channel_test.go | 8 ++++---- api4/channel_test.go | 8 -------- app/app_test.go | 10 ++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/api/channel_test.go b/api/channel_test.go index 37dde24bd..c68ace31e 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -1108,8 +1108,8 @@ func TestDeleteChannel(t *testing.T) { th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) th.LoginSystemAdmin() th.LinkUserToTeam(th.BasicUser, team) @@ -1131,8 +1131,8 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) diff --git a/api4/channel_test.go b/api4/channel_test.go index 4deceb4c4..d85c939b2 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -770,12 +770,6 @@ func TestDeleteChannel(t *testing.T) { _, resp = Client.DeleteChannel(publicChannel3.Id) CheckNoError(t, resp) - // successful delete by TeamAdmin of channel created by user - publicChannel4 := th.CreatePublicChannel() - th.LoginTeamAdmin() - _, resp = Client.DeleteChannel(publicChannel4.Id) - CheckNoError(t, resp) - // default channel cannot be deleted. defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, team.Id) pass, resp = Client.DeleteChannel(defaultChannel.Id) @@ -837,8 +831,6 @@ func TestDeleteChannel(t *testing.T) { th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) Client = th.Client team = th.BasicTeam diff --git a/app/app_test.go b/app/app_test.go index 180bd21ee..4a4238197 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -111,6 +111,10 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.PERMISSION_DELETE_POST.Id, model.PERMISSION_EDIT_POST.Id, @@ -124,11 +128,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_READ_PUBLIC_CHANNEL.Id, model.PERMISSION_VIEW_TEAM.Id, model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, - model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, - model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, - model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, - model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_INVITE_USER.Id, model.PERMISSION_ADD_USER_TO_TEAM.Id, }, @@ -291,9 +291,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_READ_PUBLIC_CHANNEL.Id, model.PERMISSION_VIEW_TEAM.Id, model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, - model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, - model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_INVITE_USER.Id, model.PERMISSION_ADD_USER_TO_TEAM.Id, }, -- cgit v1.2.3-1-g7c22 From 93254308599357fac665fce604f3506ee8a845fa Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Mon, 12 Feb 2018 12:49:29 -0500 Subject: XYZ-73: Removes EnableOnlyAdminIntegrations uses. (#8245) --- api/command_test.go | 3 --- api4/post_test.go | 3 --- 2 files changed, 6 deletions(-) diff --git a/api/command_test.go b/api/command_test.go index 7eadca124..7f9c6dcff 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -202,13 +202,10 @@ func TestDeleteCommand(t *testing.T) { Client := th.SystemAdminClient enableCommands := *th.App.Config().ServiceSettings.EnableCommands - onlyAdminIntegration := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations defer func() { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = enableCommands }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = onlyAdminIntegration }) }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) cmd := &model.Command{URL: "http://nowhere.com", Method: model.COMMAND_METHOD_POST, Trigger: "trigger"} cmd = Client.Must(Client.CreateCommand(cmd)).Data.(*model.Command) diff --git a/api4/post_test.go b/api4/post_test.go index 52326c4b0..dba3a1c2d 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -131,17 +131,14 @@ func testCreatePostWithOutgoingHook( channel := th.BasicChannel enableOutgoingHooks := th.App.Config().ServiceSettings.EnableOutgoingWebhooks - enableAdminOnlyHooks := th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections }) }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" }) -- cgit v1.2.3-1-g7c22 From b7fc3d7d35ca4dd16097715a66463392a1dfaf0a Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Tue, 13 Feb 2018 06:08:21 -0500 Subject: Updates migration tests to reflect front-end mapping changes. (#8237) --- app/diagnostics.go | 2 +- model/config.go | 6 +++- utils/authorization.go | 2 +- utils/authorization_test.go | 10 +++++- utils/config.go | 2 +- utils/policies-roles-mapping.json | 74 +++++++++++++++++++++++++-------------- 6 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/diagnostics.go b/app/diagnostics.go index 6d83d3a89..bbc72e63e 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -247,7 +247,7 @@ func (a *App) trackConfig() { a.SendDiagnostic(TRACK_CONFIG_TEAM, map[string]interface{}{ "enable_user_creation": cfg.TeamSettings.EnableUserCreation, - "enable_team_creation": cfg.TeamSettings.EnableTeamCreation, + "enable_team_creation": *cfg.TeamSettings.EnableTeamCreation, "restrict_team_invite": *cfg.TeamSettings.RestrictTeamInvite, "restrict_public_channel_creation": *cfg.TeamSettings.RestrictPublicChannelCreation, "restrict_private_channel_creation": *cfg.TeamSettings.RestrictPrivateChannelCreation, diff --git a/model/config.go b/model/config.go index 8fb11d403..f20a11ab8 100644 --- a/model/config.go +++ b/model/config.go @@ -962,7 +962,7 @@ func (s *ThemeSettings) SetDefaults() { type TeamSettings struct { SiteName string MaxUsersPerTeam *int - EnableTeamCreation bool + EnableTeamCreation *bool EnableUserCreation bool EnableOpenServer *bool RestrictCreationToDomains string @@ -1085,6 +1085,10 @@ func (s *TeamSettings) SetDefaults() { if s.ExperimentalPrimaryTeam == nil { s.ExperimentalPrimaryTeam = NewString("") } + + if s.EnableTeamCreation == nil { + s.EnableTeamCreation = NewBool(true) + } } type ClientRequirements struct { diff --git a/utils/authorization.go b/utils/authorization.go index e8556458a..bc71404ef 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -260,7 +260,7 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } - if cfg.TeamSettings.EnableTeamCreation { + if *cfg.TeamSettings.EnableTeamCreation { roles[model.SYSTEM_USER_ROLE_ID].Permissions = append( roles[model.SYSTEM_USER_ROLE_ID].Permissions, model.PERMISSION_CREATE_TEAM.Id, diff --git a/utils/authorization_test.go b/utils/authorization_test.go index 9e5b570bb..8c78dcbda 100644 --- a/utils/authorization_test.go +++ b/utils/authorization_test.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "reflect" + "strconv" "strings" "testing" @@ -112,7 +113,14 @@ func updateConfig(config *model.Config, key string, value string) { v = reflect.ValueOf(config.TeamSettings) field = v.FieldByName(key) } - field.Elem().SetString(value) + + switch value { + case "true", "false": + b, _ := strconv.ParseBool(value) + field.Elem().SetBool(b) + default: + field.Elem().SetString(value) + } } func roleHasPermission(role *model.Role, permission string) bool { diff --git a/utils/config.go b/utils/config.go index 9e962eef4..be20e0367 100644 --- a/utils/config.go +++ b/utils/config.go @@ -354,7 +354,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["SiteURL"] = strings.TrimRight(*c.ServiceSettings.SiteURL, "/") props["SiteName"] = c.TeamSettings.SiteName - props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation) + props["EnableTeamCreation"] = strconv.FormatBool(*c.TeamSettings.EnableTeamCreation) props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation) props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer) props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage diff --git a/utils/policies-roles-mapping.json b/utils/policies-roles-mapping.json index f083a854a..1b2acdfcb 100644 --- a/utils/policies-roles-mapping.json +++ b/utils/policies-roles-mapping.json @@ -431,16 +431,6 @@ "permission": "delete_post", "shouldHave": true }, - { - "roleName": "channel_admin", - "permission": "delete_post", - "shouldHave": false - }, - { - "roleName": "channel_admin", - "permission": "delete_others_posts", - "shouldHave": false - }, { "roleName": "team_admin", "permission": "delete_post", @@ -458,16 +448,6 @@ "permission": "delete_post", "shouldHave": false }, - { - "roleName": "channel_admin", - "permission": "delete_post", - "shouldHave": false - }, - { - "roleName": "channel_admin", - "permission": "delete_others_posts", - "shouldHave": false - }, { "roleName": "team_admin", "permission": "delete_post", @@ -486,25 +466,67 @@ "shouldHave": false }, { - "roleName": "channel_admin", + "roleName": "team_admin", "permission": "delete_post", "shouldHave": false }, { - "roleName": "channel_admin", + "roleName": "team_admin", "permission": "delete_others_posts", "shouldHave": false + } + ] + }, + "enableTeamCreation": { + "true": [ + { + "roleName": "system_user", + "permission": "create_team", + "shouldHave": true + } + ], + "false": [ + { + "roleName": "system_user", + "permission": "create_team", + "shouldHave": false + } + ] + }, + "enableOnlyAdminIntegrations": { + "true": [ + { + "roleName": "team_user", + "permission": "manage_webhooks", + "shouldHave": false }, { - "roleName": "team_admin", - "permission": "delete_post", + "roleName": "team_user", + "permission": "manage_slash_commands", "shouldHave": false }, { - "roleName": "team_admin", - "permission": "delete_others_posts", + "roleName": "system_user", + "permission": "manage_oauth", "shouldHave": false } + ], + "false": [ + { + "roleName": "team_user", + "permission": "manage_webhooks", + "shouldHave": true + }, + { + "roleName": "team_user", + "permission": "manage_slash_commands", + "shouldHave": true + }, + { + "roleName": "system_user", + "permission": "manage_oauth", + "shouldHave": true + } ] } } \ No newline at end of file -- cgit v1.2.3-1-g7c22 From 0663f5f88d8a2945178c521884a5323d6fac14ee Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Tue, 13 Feb 2018 18:03:09 +0000 Subject: XYZ-78: Add note to upgrade code about advanced permissions migration. (#8267) --- store/sqlstore/upgrade.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 56fdf9d6c..265696288 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -351,6 +351,10 @@ func UpgradeDatabaseToVersion47(sqlStore SqlStore) { } func UpgradeDatabaseToVersion48(sqlStore SqlStore) { + // This version of Mattermost includes an App-Layer migration which migrates from hard-coded roles configured by + // a number of parameters in `config.json` to a `Roles` table in the database. The migration code can be seen + // in the file `app/app.go` in the function `DoAdvancedPermissionsMigration()`. + //TODO: Uncomment the following condition when version 4.8.0 is released //if shouldPerformUpgrade(sqlStore, VERSION_4_7_0, VERSION_4_8_0) { // saveSchemaVersion(sqlStore, VERSION_4_8_0) -- cgit v1.2.3-1-g7c22 From a43928cca82c718dd378961102a3766b3e354ac8 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 13 Feb 2018 11:08:49 -0500 Subject: ABC-176 Prevent changing PluginSettings.EnableUploads through the API (#8249) * Prevent changing PluginSettings.EnableUploads through the API * Contain api4 test case in it's own test --- api/admin.go | 3 +++ api/admin_test.go | 13 +++++++++++++ api4/system.go | 3 +++ api4/system_test.go | 22 ++++++++++++++++++++-- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/api/admin.go b/api/admin.go index b3b74d5ea..3b58650cc 100644 --- a/api/admin.go +++ b/api/admin.go @@ -108,6 +108,9 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Do not allow plugin uploads to be toggled through the API + cfg.PluginSettings.EnableUploads = c.App.GetConfig().PluginSettings.EnableUploads + err := c.App.SaveConfig(cfg, true) if err != nil { c.Err = err diff --git a/api/admin_test.go b/api/admin_test.go index d916e8c4b..00e5b3c7f 100644 --- a/api/admin_test.go +++ b/api/admin_test.go @@ -10,6 +10,7 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" + "github.com/stretchr/testify/assert" ) func TestGetLogs(t *testing.T) { @@ -149,6 +150,18 @@ func TestSaveConfig(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) + + // Should not be able to modify PluginSettings.EnableUploads + oldEnableUploads := *th.App.GetConfig().PluginSettings.EnableUploads + cfg := &model.Config{} + cfg.SetDefaults() + *cfg.PluginSettings.EnableUploads = !oldEnableUploads + + if _, err := th.SystemAdminClient.SaveConfig(cfg); err != nil { + t.Fatal(err) + } + + assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads) } func TestRecycleDatabaseConnection(t *testing.T) { diff --git a/api4/system.go b/api4/system.go index 061ffe094..2355cb476 100644 --- a/api4/system.go +++ b/api4/system.go @@ -121,6 +121,9 @@ func updateConfig(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Do not allow plugin uploads to be toggled through the API + cfg.PluginSettings.EnableUploads = c.App.GetConfig().PluginSettings.EnableUploads + err := c.App.SaveConfig(cfg, true) if err != nil { c.Err = err diff --git a/api4/system_test.go b/api4/system_test.go index 1b2bb5d99..01b4934ae 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -7,6 +7,7 @@ import ( l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/model" + "github.com/stretchr/testify/assert" ) func TestGetPing(t *testing.T) { @@ -106,9 +107,10 @@ func TestUpdateConfig(t *testing.T) { defer th.TearDown() Client := th.Client - cfg := th.App.GetConfig() + cfg, resp := th.SystemAdminClient.GetConfig() + CheckNoError(t, resp) - _, resp := Client.UpdateConfig(cfg) + _, resp = Client.UpdateConfig(cfg) CheckForbiddenStatus(t, resp) SiteName := th.App.Config().TeamSettings.SiteName @@ -139,6 +141,22 @@ func TestUpdateConfig(t *testing.T) { t.Fatal() } } + + t.Run("Should not be able to modify PluginSettings.EnableUploads", func(t *testing.T) { + oldEnableUploads := *th.App.GetConfig().PluginSettings.EnableUploads + *cfg.PluginSettings.EnableUploads = !oldEnableUploads + + cfg, resp = th.SystemAdminClient.UpdateConfig(cfg) + CheckNoError(t, resp) + assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads) + assert.Equal(t, oldEnableUploads, *th.App.GetConfig().PluginSettings.EnableUploads) + + cfg.PluginSettings.EnableUploads = nil + cfg, resp = th.SystemAdminClient.UpdateConfig(cfg) + CheckNoError(t, resp) + assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads) + assert.Equal(t, oldEnableUploads, *th.App.GetConfig().PluginSettings.EnableUploads) + }) } func TestGetOldClientConfig(t *testing.T) { -- cgit v1.2.3-1-g7c22 From 0e718a632a616bcfec4378f512182245b68f4fd8 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Mon, 19 Feb 2018 10:16:45 +0000 Subject: MM-9618: Don't change default role permissions for policy. (#8303) --- api/channel_test.go | 8 ++++---- api4/channel_test.go | 8 ++++---- app/app_test.go | 12 ++++++------ utils/authorization.go | 32 ++++++++++++++++---------------- utils/policies-roles-mapping.json | 34 +++++++++++++++++----------------- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/api/channel_test.go b/api/channel_test.go index c68ace31e..37dde24bd 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -1108,8 +1108,8 @@ func TestDeleteChannel(t *testing.T) { th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) th.LoginSystemAdmin() th.LinkUserToTeam(th.BasicUser, team) @@ -1131,8 +1131,8 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) diff --git a/api4/channel_test.go b/api4/channel_test.go index d85c939b2..1b74ea880 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -829,8 +829,8 @@ func TestDeleteChannel(t *testing.T) { th.RestoreDefaultRolePermissions(defaultRolePermissions) }() - th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) - th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) Client = th.Client team = th.BasicTeam @@ -852,8 +852,8 @@ func TestDeleteChannel(t *testing.T) { CheckNoError(t, resp) // Restrict permissions to Channel Admins - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) - th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.TEAM_USER_ROLE_ID) + th.RemovePermissionFromRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.TEAM_USER_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) th.AddPermissionToRole(model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.CHANNEL_ADMIN_ROLE_ID) diff --git a/app/app_test.go b/app/app_test.go index f31e0332c..3690d916f 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -111,10 +111,6 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, - model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, - model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, - model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, - model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.PERMISSION_DELETE_POST.Id, model.PERMISSION_EDIT_POST.Id, @@ -128,7 +124,11 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_READ_PUBLIC_CHANNEL.Id, model.PERMISSION_VIEW_TEAM.Id, model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_INVITE_USER.Id, model.PERMISSION_ADD_USER_TO_TEAM.Id, }, @@ -270,8 +270,6 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, model.PERMISSION_USE_SLASH_COMMANDS.Id, - model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, - model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, model.PERMISSION_DELETE_POST.Id, model.PERMISSION_EDIT_POST.Id, @@ -285,7 +283,9 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_READ_PUBLIC_CHANNEL.Id, model.PERMISSION_VIEW_TEAM.Id, model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, model.PERMISSION_INVITE_USER.Id, model.PERMISSION_ADD_USER_TO_TEAM.Id, }, diff --git a/utils/authorization.go b/utils/authorization.go index bc71404ef..16f33bc1a 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -31,8 +31,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelManagement { case model.PERMISSIONS_ALL: - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -51,8 +51,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, ) } @@ -60,8 +60,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelDeletion { case model.PERMISSIONS_ALL: - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -80,8 +80,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, ) } @@ -109,8 +109,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManagement { case model.PERMISSIONS_ALL: - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -129,8 +129,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, ) } @@ -138,8 +138,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelDeletion { case model.PERMISSIONS_ALL: - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, ) case model.PERMISSIONS_CHANNEL_ADMIN: @@ -158,8 +158,8 @@ func SetRolePermissionsFromConfig(roles map[string]*model.Role, cfg *model.Confi ) } } else { - roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( - roles[model.CHANNEL_USER_ROLE_ID].Permissions, + roles[model.TEAM_USER_ROLE_ID].Permissions = append( + roles[model.TEAM_USER_ROLE_ID].Permissions, model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, ) } diff --git a/utils/policies-roles-mapping.json b/utils/policies-roles-mapping.json index 1b2acdfcb..6b09c6c72 100644 --- a/utils/policies-roles-mapping.json +++ b/utils/policies-roles-mapping.json @@ -101,14 +101,14 @@ "restrictPublicChannelManagement": { "all": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_public_channel_properties", "shouldHave": true } ], "channel_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_public_channel_properties", "shouldHave": false }, @@ -125,7 +125,7 @@ ], "team_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_public_channel_properties", "shouldHave": false }, @@ -142,7 +142,7 @@ ], "system_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_public_channel_properties", "shouldHave": false }, @@ -161,14 +161,14 @@ "restrictPublicChannelDeletion": { "all": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_public_channel", "shouldHave": true } ], "channel_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_public_channel", "shouldHave": false }, @@ -185,7 +185,7 @@ ], "team_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_public_channel", "shouldHave": false }, @@ -202,7 +202,7 @@ ], "system_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_public_channel", "shouldHave": false }, @@ -221,14 +221,14 @@ "restrictPrivateChannelManagement": { "all": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_private_channel_properties", "shouldHave": true } ], "channel_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_private_channel_properties", "shouldHave": false }, @@ -245,7 +245,7 @@ ], "team_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_private_channel_properties", "shouldHave": false }, @@ -262,7 +262,7 @@ ], "system_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "manage_private_channel_properties", "shouldHave": false }, @@ -341,14 +341,14 @@ "restrictPrivateChannelDeletion": { "all": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_private_channel", "shouldHave": true } ], "channel_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_private_channel", "shouldHave": false }, @@ -365,7 +365,7 @@ ], "team_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_private_channel", "shouldHave": false }, @@ -382,7 +382,7 @@ ], "system_admin": [ { - "roleName": "channel_user", + "roleName": "team_user", "permission": "delete_private_channel", "shouldHave": false }, @@ -529,4 +529,4 @@ } ] } -} \ No newline at end of file +} -- cgit v1.2.3-1-g7c22 From 8891fa2a5e9e08eb9fa99ec163c47a6e9761a816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Mon, 19 Feb 2018 12:13:29 +0100 Subject: MM-8827: Add ADD_REACTION, REMOVE_REACTION and REMOVE_OTHERS_REACTIONS permissions (#8300) --- api/reaction.go | 8 +- api4/reaction.go | 12 +- api4/reaction_test.go | 464 +++++++++++++++++++++++++++++++------------------- app/app_test.go | 10 ++ model/permission.go | 24 +++ model/role.go | 3 + 6 files changed, 333 insertions(+), 188 deletions(-) diff --git a/api/reaction.go b/api/reaction.go index 991044cda..812c9f582 100644 --- a/api/reaction.go +++ b/api/reaction.go @@ -36,8 +36,8 @@ func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) + if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_ADD_REACTION) { + c.SetPermissionError(model.PERMISSION_ADD_REACTION) return } @@ -87,8 +87,8 @@ func deleteReaction(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) + if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_REMOVE_REACTION) { + c.SetPermissionError(model.PERMISSION_REMOVE_REACTION) return } diff --git a/api4/reaction.go b/api4/reaction.go index af637bf91..337b49751 100644 --- a/api4/reaction.go +++ b/api4/reaction.go @@ -32,8 +32,8 @@ func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(c.Session, reaction.PostId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) + if !c.App.SessionHasPermissionToChannelByPost(c.Session, reaction.PostId, model.PERMISSION_ADD_REACTION) { + c.SetPermissionError(model.PERMISSION_ADD_REACTION) return } @@ -82,13 +82,13 @@ func deleteReaction(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_READ_CHANNEL) { - c.SetPermissionError(model.PERMISSION_READ_CHANNEL) + if !c.App.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_REMOVE_REACTION) { + c.SetPermissionError(model.PERMISSION_REMOVE_REACTION) return } - if c.Params.UserId != c.Session.UserId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + if c.Params.UserId != c.Session.UserId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_REMOVE_OTHERS_REACTIONS) { + c.SetPermissionError(model.PERMISSION_REMOVE_OTHERS_REACTIONS) return } diff --git a/api4/reaction_test.go b/api4/reaction_test.go index 93cd754c9..ac1a49671 100644 --- a/api4/reaction_test.go +++ b/api4/reaction_test.go @@ -19,116 +19,159 @@ func TestSaveReaction(t *testing.T) { userId := th.BasicUser.Id postId := th.BasicPost.Id + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + reaction := &model.Reaction{ UserId: userId, PostId: postId, EmojiName: "smile", } - rr, resp := Client.SaveReaction(reaction) - CheckNoError(t, resp) + t.Run("successful-reaction", func(t *testing.T) { + rr, resp := Client.SaveReaction(reaction) + CheckNoError(t, resp) - if rr.UserId != reaction.UserId { - t.Fatal("UserId did not match") - } + if rr.UserId != reaction.UserId { + t.Fatal("UserId did not match") + } - if rr.PostId != reaction.PostId { - t.Fatal("PostId did not match") - } + if rr.PostId != reaction.PostId { + t.Fatal("PostId did not match") + } - if rr.EmojiName != reaction.EmojiName { - t.Fatal("EmojiName did not match") - } + if rr.EmojiName != reaction.EmojiName { + t.Fatal("EmojiName did not match") + } - if rr.CreateAt == 0 { - t.Fatal("CreateAt should exist") - } + if rr.CreateAt == 0 { + t.Fatal("CreateAt should exist") + } - if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 1 { - t.Fatal("didn't save reaction correctly") - } + if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 1 { + t.Fatal("didn't save reaction correctly") + } + }) - // saving a duplicate reaction - rr, resp = Client.SaveReaction(reaction) - CheckNoError(t, resp) + t.Run("duplicated-reaction", func(t *testing.T) { + _, resp := Client.SaveReaction(reaction) + CheckNoError(t, resp) - if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 1 { - t.Fatal("should have not save duplicated reaction") - } + if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 1 { + t.Fatal("should have not save duplicated reaction") + } + }) - reaction.EmojiName = "sad" + t.Run("save-second-reaction", func(t *testing.T) { + reaction.EmojiName = "sad" - rr, resp = Client.SaveReaction(reaction) - CheckNoError(t, resp) + rr, resp := Client.SaveReaction(reaction) + CheckNoError(t, resp) - if rr.EmojiName != reaction.EmojiName { - t.Fatal("EmojiName did not match") - } + if rr.EmojiName != reaction.EmojiName { + t.Fatal("EmojiName did not match") + } - if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 2 { - t.Fatal("should have save multiple reactions") - } + if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 2 { + t.Fatal("should have save multiple reactions") + } + }) - // saving special case - reaction.EmojiName = "+1" + t.Run("saving-special-case", func(t *testing.T) { + reaction.EmojiName = "+1" - rr, resp = Client.SaveReaction(reaction) - CheckNoError(t, resp) + rr, resp := Client.SaveReaction(reaction) + CheckNoError(t, resp) - if rr.EmojiName != reaction.EmojiName { - t.Fatal("EmojiName did not match") - } + if rr.EmojiName != reaction.EmojiName { + t.Fatal("EmojiName did not match") + } - if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 3 { - t.Fatal("should have save multiple reactions") - } + if reactions, err := th.App.GetReactionsForPost(postId); err != nil && len(reactions) != 3 { + t.Fatal("should have save multiple reactions") + } + }) + + t.Run("react-to-not-existing-post-id", func(t *testing.T) { + reaction.PostId = GenerateTestId() + + _, resp := Client.SaveReaction(reaction) + CheckForbiddenStatus(t, resp) + }) - reaction.PostId = GenerateTestId() + t.Run("react-to-not-valid-post-id", func(t *testing.T) { + reaction.PostId = "junk" - _, resp = Client.SaveReaction(reaction) - CheckForbiddenStatus(t, resp) + _, resp := Client.SaveReaction(reaction) + CheckBadRequestStatus(t, resp) + }) - reaction.PostId = "junk" + t.Run("react-as-not-existing-user-id", func(t *testing.T) { + reaction.PostId = postId + reaction.UserId = GenerateTestId() - _, resp = Client.SaveReaction(reaction) - CheckBadRequestStatus(t, resp) + _, resp := Client.SaveReaction(reaction) + CheckForbiddenStatus(t, resp) + }) - reaction.PostId = postId - reaction.UserId = GenerateTestId() + t.Run("react-as-not-valid-user-id", func(t *testing.T) { + reaction.UserId = "junk" - _, resp = Client.SaveReaction(reaction) - CheckForbiddenStatus(t, resp) + _, resp := Client.SaveReaction(reaction) + CheckBadRequestStatus(t, resp) + }) - reaction.UserId = "junk" + t.Run("react-as-empty-emoji-name", func(t *testing.T) { + reaction.UserId = userId + reaction.EmojiName = "" - _, resp = Client.SaveReaction(reaction) - CheckBadRequestStatus(t, resp) + _, resp := Client.SaveReaction(reaction) + CheckBadRequestStatus(t, resp) + }) - reaction.UserId = userId - reaction.EmojiName = "" + t.Run("react-as-not-valid-emoji-name", func(t *testing.T) { + reaction.EmojiName = strings.Repeat("a", 65) - _, resp = Client.SaveReaction(reaction) - CheckBadRequestStatus(t, resp) + _, resp := Client.SaveReaction(reaction) + CheckBadRequestStatus(t, resp) + }) - reaction.EmojiName = strings.Repeat("a", 65) + t.Run("react-as-other-user", func(t *testing.T) { + reaction.EmojiName = "smile" + otherUser := th.CreateUser() + Client.Logout() + Client.Login(otherUser.Email, otherUser.Password) - _, resp = Client.SaveReaction(reaction) - CheckBadRequestStatus(t, resp) + _, resp := Client.SaveReaction(reaction) + CheckForbiddenStatus(t, resp) + }) - reaction.EmojiName = "smile" - otherUser := th.CreateUser() - Client.Logout() - Client.Login(otherUser.Email, otherUser.Password) + t.Run("react-being-not-logged-in", func(t *testing.T) { + Client.Logout() + _, resp := Client.SaveReaction(reaction) + CheckUnauthorizedStatus(t, resp) + }) - _, resp = Client.SaveReaction(reaction) - CheckForbiddenStatus(t, resp) + t.Run("react-as-other-user-being-system-admin", func(t *testing.T) { + _, resp := th.SystemAdminClient.SaveReaction(reaction) + CheckForbiddenStatus(t, resp) + }) - Client.Logout() - _, resp = Client.SaveReaction(reaction) - CheckUnauthorizedStatus(t, resp) + t.Run("unable-to-create-reaction-without-permissions", func(t *testing.T) { + th.LoginBasic() - _, resp = th.SystemAdminClient.SaveReaction(reaction) - CheckForbiddenStatus(t, resp) + th.RemovePermissionFromRole(model.PERMISSION_ADD_REACTION.Id, model.CHANNEL_USER_ROLE_ID) + _, resp := Client.SaveReaction(reaction) + CheckForbiddenStatus(t, resp) + + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 3 { + t.Fatal("should have not created a reactions") + } + th.AddPermissionToRole(model.PERMISSION_ADD_REACTION.Id, model.CHANNEL_USER_ROLE_ID) + }) } func TestGetReactions(t *testing.T) { @@ -177,29 +220,39 @@ func TestGetReactions(t *testing.T) { } } - rr, resp := Client.GetReactions(postId) - CheckNoError(t, resp) + t.Run("get-reactions", func(t *testing.T) { + rr, resp := Client.GetReactions(postId) + CheckNoError(t, resp) - assert.Len(t, rr, 5) - for _, r := range reactions { - assert.Contains(t, reactions, r) - } + assert.Len(t, rr, 5) + for _, r := range reactions { + assert.Contains(t, reactions, r) + } + }) - rr, resp = Client.GetReactions("junk") - CheckBadRequestStatus(t, resp) + t.Run("get-reactions-of-invalid-post-id", func(t *testing.T) { + rr, resp := Client.GetReactions("junk") + CheckBadRequestStatus(t, resp) - assert.Empty(t, rr) + assert.Empty(t, rr) + }) - _, resp = Client.GetReactions(GenerateTestId()) - CheckForbiddenStatus(t, resp) + t.Run("get-reactions-of-not-existing-post-id", func(t *testing.T) { + _, resp := Client.GetReactions(GenerateTestId()) + CheckForbiddenStatus(t, resp) + }) - Client.Logout() + t.Run("get-reactions-as-anonymous-user", func(t *testing.T) { + Client.Logout() - _, resp = Client.GetReactions(postId) - CheckUnauthorizedStatus(t, resp) + _, resp := Client.GetReactions(postId) + CheckUnauthorizedStatus(t, resp) + }) - _, resp = th.SystemAdminClient.GetReactions(postId) - CheckNoError(t, resp) + t.Run("get-reactions-as-system-admin", func(t *testing.T) { + _, resp := th.SystemAdminClient.GetReactions(postId) + CheckNoError(t, resp) + }) } func TestDeleteReaction(t *testing.T) { @@ -216,131 +269,186 @@ func TestDeleteReaction(t *testing.T) { EmojiName: "smile", } - th.App.SaveReactionForPost(r1) - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { - t.Fatal("didn't save reaction correctly") - } - - ok, resp := Client.DeleteReaction(r1) - CheckNoError(t, resp) - - if !ok { - t.Fatal("should have returned true") - } - - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { - t.Fatal("should have deleted reaction") - } - - // deleting one reaction when a post has multiple reactions r2 := &model.Reaction{ UserId: userId, PostId: postId, EmojiName: "smile-", } - th.App.SaveReactionForPost(r1) - th.App.SaveReactionForPost(r2) - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { - t.Fatal("didn't save reactions correctly") - } - - _, resp = Client.DeleteReaction(r2) - CheckNoError(t, resp) - - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { - t.Fatal("should have deleted 1 reaction only") - } - - // deleting one reaction of name +1 r3 := &model.Reaction{ UserId: userId, PostId: postId, EmojiName: "+1", } - th.App.SaveReactionForPost(r3) - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { - t.Fatal("didn't save reactions correctly") - } - - _, resp = Client.DeleteReaction(r3) - CheckNoError(t, resp) - - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { - t.Fatal("should have deleted 1 reaction only") - } - - // deleting a reaction made by another user r4 := &model.Reaction{ UserId: user2Id, PostId: postId, EmojiName: "smile_", } - th.LoginBasic2() - th.App.SaveReactionForPost(r4) - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { - t.Fatal("didn't save reaction correctly") - } + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - th.LoginBasic() + t.Run("delete-reaction", func(t *testing.T) { + th.App.SaveReactionForPost(r1) + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { + t.Fatal("didn't save reaction correctly") + } - ok, resp = Client.DeleteReaction(r4) - CheckForbiddenStatus(t, resp) + ok, resp := Client.DeleteReaction(r1) + CheckNoError(t, resp) - if ok { - t.Fatal("should have returned false") - } + if !ok { + t.Fatal("should have returned true") + } - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { - t.Fatal("should have not deleted a reaction") - } + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { + t.Fatal("should have deleted reaction") + } + }) - r1.PostId = GenerateTestId() - _, resp = Client.DeleteReaction(r1) - CheckForbiddenStatus(t, resp) + t.Run("delete-reaction-when-post-has-multiple-reactions", func(t *testing.T) { + th.App.SaveReactionForPost(r1) + th.App.SaveReactionForPost(r2) + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("didn't save reactions correctly") + } + + _, resp := Client.DeleteReaction(r2) + CheckNoError(t, resp) - r1.PostId = "junk" + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { + t.Fatal("should have deleted 1 reaction only") + } + }) + + t.Run("delete-reaction-when-plus-one-reaction-name", func(t *testing.T) { + th.App.SaveReactionForPost(r3) + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("didn't save reactions correctly") + } - _, resp = Client.DeleteReaction(r1) - CheckBadRequestStatus(t, resp) + _, resp := Client.DeleteReaction(r3) + CheckNoError(t, resp) - r1.PostId = postId - r1.UserId = GenerateTestId() + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { + t.Fatal("should have deleted 1 reaction only") + } + }) - _, resp = Client.DeleteReaction(r1) - CheckForbiddenStatus(t, resp) + t.Run("delete-reaction-made-by-another-user", func(t *testing.T) { + th.LoginBasic2() + th.App.SaveReactionForPost(r4) + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("didn't save reaction correctly") + } - r1.UserId = "junk" + th.LoginBasic() - _, resp = Client.DeleteReaction(r1) - CheckBadRequestStatus(t, resp) + ok, resp := Client.DeleteReaction(r4) + CheckForbiddenStatus(t, resp) - r1.UserId = userId - r1.EmojiName = "" + if ok { + t.Fatal("should have returned false") + } - _, resp = Client.DeleteReaction(r1) - CheckNotFoundStatus(t, resp) + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("should have not deleted a reaction") + } + }) - r1.EmojiName = strings.Repeat("a", 65) + t.Run("delete-reaction-from-not-existing-post-id", func(t *testing.T) { + r1.PostId = GenerateTestId() + _, resp := Client.DeleteReaction(r1) + CheckForbiddenStatus(t, resp) + }) - _, resp = Client.DeleteReaction(r1) - CheckBadRequestStatus(t, resp) + t.Run("delete-reaction-from-not-valid-post-id", func(t *testing.T) { + r1.PostId = "junk" - Client.Logout() - r1.EmojiName = "smile" + _, resp := Client.DeleteReaction(r1) + CheckBadRequestStatus(t, resp) + }) - _, resp = Client.DeleteReaction(r1) - CheckUnauthorizedStatus(t, resp) + t.Run("delete-reaction-from-not-existing-user-id", func(t *testing.T) { + r1.PostId = postId + r1.UserId = GenerateTestId() - _, resp = th.SystemAdminClient.DeleteReaction(r1) - CheckNoError(t, resp) + _, resp := Client.DeleteReaction(r1) + CheckForbiddenStatus(t, resp) + }) - _, resp = th.SystemAdminClient.DeleteReaction(r4) - CheckNoError(t, resp) + t.Run("delete-reaction-from-not-valid-user-id", func(t *testing.T) { + r1.UserId = "junk" - if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { - t.Fatal("should have deleted both reactions") - } + _, resp := Client.DeleteReaction(r1) + CheckBadRequestStatus(t, resp) + }) + + t.Run("delete-reaction-with-empty-name", func(t *testing.T) { + r1.UserId = userId + r1.EmojiName = "" + + _, resp := Client.DeleteReaction(r1) + CheckNotFoundStatus(t, resp) + }) + + t.Run("delete-reaction-with-not-existing-name", func(t *testing.T) { + r1.EmojiName = strings.Repeat("a", 65) + + _, resp := Client.DeleteReaction(r1) + CheckBadRequestStatus(t, resp) + }) + + t.Run("delete-reaction-as-anonymous-user", func(t *testing.T) { + Client.Logout() + r1.EmojiName = "smile" + + _, resp := Client.DeleteReaction(r1) + CheckUnauthorizedStatus(t, resp) + }) + + t.Run("delete-reaction-as-system-admin", func(t *testing.T) { + _, resp := th.SystemAdminClient.DeleteReaction(r1) + CheckNoError(t, resp) + + _, resp = th.SystemAdminClient.DeleteReaction(r4) + CheckNoError(t, resp) + + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { + t.Fatal("should have deleted both reactions") + } + }) + + t.Run("unable-to-delete-reaction-without-permissions", func(t *testing.T) { + th.LoginBasic() + + th.RemovePermissionFromRole(model.PERMISSION_REMOVE_REACTION.Id, model.CHANNEL_USER_ROLE_ID) + th.App.SaveReactionForPost(r1) + + _, resp := Client.DeleteReaction(r1) + CheckForbiddenStatus(t, resp) + + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { + t.Fatal("should have not deleted a reactions") + } + th.AddPermissionToRole(model.PERMISSION_REMOVE_REACTION.Id, model.CHANNEL_USER_ROLE_ID) + }) + + t.Run("unable-to-delete-others-reactions-without-permissions", func(t *testing.T) { + th.RemovePermissionFromRole(model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, model.SYSTEM_ADMIN_ROLE_ID) + th.App.SaveReactionForPost(r1) + + _, resp := th.SystemAdminClient.DeleteReaction(r1) + CheckForbiddenStatus(t, resp) + + if reactions, err := th.App.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { + t.Fatal("should have not deleted a reactions") + } + th.AddPermissionToRole(model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, model.SYSTEM_ADMIN_ROLE_ID) + }) } diff --git a/app/app_test.go b/app/app_test.go index 3690d916f..c2841ec53 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -106,6 +106,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { expected1 := map[string][]string{ "channel_user": []string{ model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, @@ -196,11 +198,14 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, model.PERMISSION_LIST_TEAM_CHANNELS.Id, model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, model.PERMISSION_READ_PUBLIC_CHANNEL.Id, model.PERMISSION_VIEW_TEAM.Id, model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, @@ -265,6 +270,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { expected2 := map[string][]string{ "channel_user": []string{ model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, @@ -355,11 +362,14 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, model.PERMISSION_LIST_TEAM_CHANNELS.Id, model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, model.PERMISSION_READ_PUBLIC_CHANNEL.Id, model.PERMISSION_VIEW_TEAM.Id, model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, model.PERMISSION_UPLOAD_FILE.Id, model.PERMISSION_GET_PUBLIC_LINK.Id, model.PERMISSION_CREATE_POST.Id, diff --git a/model/permission.go b/model/permission.go index 0dc09f49a..ddbf1030e 100644 --- a/model/permission.go +++ b/model/permission.go @@ -40,6 +40,9 @@ var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission var PERMISSION_EDIT_OTHER_USERS *Permission var PERMISSION_READ_CHANNEL *Permission var PERMISSION_READ_PUBLIC_CHANNEL *Permission +var PERMISSION_ADD_REACTION *Permission +var PERMISSION_REMOVE_REACTION *Permission +var PERMISSION_REMOVE_OTHERS_REACTIONS *Permission var PERMISSION_PERMANENT_DELETE_USER *Permission var PERMISSION_UPLOAD_FILE *Permission var PERMISSION_GET_PUBLIC_LINK *Permission @@ -222,6 +225,24 @@ func initializePermissions() { "authentication.permissions.read_public_channel.description", PERMISSION_SCOPE_TEAM, } + PERMISSION_ADD_REACTION = &Permission{ + "add_reaction", + "authentication.permissions.add_reaction.name", + "authentication.permissions.add_reaction.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_REMOVE_REACTION = &Permission{ + "remove_reaction", + "authentication.permissions.remove_reaction.name", + "authentication.permissions.remove_reaction.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_REMOVE_OTHERS_REACTIONS = &Permission{ + "remove_others_reactions", + "authentication.permissions.remove_others_reactions.name", + "authentication.permissions.remove_others_reactions.description", + PERMISSION_SCOPE_CHANNEL, + } PERMISSION_PERMANENT_DELETE_USER = &Permission{ "permanent_delete_user", "authentication.permissions.permanent_delete_user.name", @@ -386,6 +407,9 @@ func initializePermissions() { PERMISSION_EDIT_OTHER_USERS, PERMISSION_READ_CHANNEL, PERMISSION_READ_PUBLIC_CHANNEL, + PERMISSION_ADD_REACTION, + PERMISSION_REMOVE_REACTION, + PERMISSION_REMOVE_OTHERS_REACTIONS, PERMISSION_PERMANENT_DELETE_USER, PERMISSION_UPLOAD_FILE, PERMISSION_GET_PUBLIC_LINK, diff --git a/model/role.go b/model/role.go index 5f7352f12..5c2cf8f5b 100644 --- a/model/role.go +++ b/model/role.go @@ -178,6 +178,8 @@ func MakeDefaultRoles() map[string]*Role { Description: "authentication.roles.channel_user.description", Permissions: []string{ PERMISSION_READ_CHANNEL.Id, + PERMISSION_ADD_REACTION.Id, + PERMISSION_REMOVE_REACTION.Id, PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, PERMISSION_UPLOAD_FILE.Id, PERMISSION_GET_PUBLIC_LINK.Id, @@ -331,6 +333,7 @@ func MakeDefaultRoles() map[string]*Role { PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, PERMISSION_READ_USER_ACCESS_TOKEN.Id, PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + PERMISSION_REMOVE_OTHERS_REACTIONS.Id, }, roles[TEAM_USER_ROLE_ID].Permissions..., ), -- cgit v1.2.3-1-g7c22 From 21afaf4bedcad578d4f876bb315d1072ccd296e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 20 Feb 2018 16:19:35 +0100 Subject: MM-8825: Make consistent INVITE_USER and ADD_USER_TO_TEAM permissions checking (#8326) --- api/team.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/team.go b/api/team.go index e89f368ec..b1d8086d3 100644 --- a/api/team.go +++ b/api/team.go @@ -121,6 +121,11 @@ func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { return } + if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_ADD_USER_TO_TEAM) { + c.SetPermissionError(model.PERMISSION_INVITE_USER) + return + } + if err := c.App.InviteNewUsersToTeam(invites.ToEmailList(), c.TeamId, c.Session.UserId); err != nil { c.Err = err return -- cgit v1.2.3-1-g7c22 From b66e4bc932ed76c1cfd2b5f4ec0cfce70cd9fbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 7 Mar 2018 14:54:47 +0000 Subject: MM-8830 Consistent Incomming/Outgoing webhooks permissions (#8335) --- api4/webhook.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/api4/webhook.go b/api4/webhook.go index e19f14704..dcbf6c2af 100644 --- a/api4/webhook.go +++ b/api4/webhook.go @@ -194,10 +194,16 @@ func getIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("fail - bad permissions") c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) return - } else { - w.Write([]byte(hook.ToJson())) + } + + if c.Session.UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { + c.LogAudit("fail - inappropriate permissions") + c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) return } + + w.Write([]byte(hook.ToJson())) + return } } @@ -228,14 +234,20 @@ func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("fail - bad permissions") c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS) return - } else { - if err = c.App.DeleteIncomingWebhook(hookId); err != nil { - c.Err = err - return - } + } - ReturnStatusOK(w) + if c.Session.UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) { + c.LogAudit("fail - inappropriate permissions") + c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) + return } + + if err = c.App.DeleteIncomingWebhook(hookId); err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) } } -- cgit v1.2.3-1-g7c22 From ca5198c7b64f76027bf7b7cc4592c62b42fee623 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Mon, 26 Mar 2018 12:56:57 +0100 Subject: Ignore blank role names in getRolesByName call. (#8507) --- api4/role.go | 10 +++++++++- api4/role_test.go | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/api4/role.go b/api4/role.go index e7654011d..c4203137b 100644 --- a/api4/role.go +++ b/api4/role.go @@ -5,6 +5,7 @@ package api4 import ( "net/http" + "strings" "github.com/mattermost/mattermost-server/model" ) @@ -52,14 +53,21 @@ func getRolesByNames(c *Context, w http.ResponseWriter, r *http.Request) { return } + var cleanedRoleNames []string for _, rolename := range rolenames { + if strings.TrimSpace(rolename) == "" { + continue + } + if !model.IsValidRoleName(rolename) { c.SetInvalidParam("rolename") return } + + cleanedRoleNames = append(cleanedRoleNames, rolename) } - if roles, err := c.App.GetRolesByNames(rolenames); err != nil { + if roles, err := c.App.GetRolesByNames(cleanedRoleNames); err != nil { c.Err = err return } else { diff --git a/api4/role_test.go b/api4/role_test.go index 3fbf6808d..c5d8e303e 100644 --- a/api4/role_test.go +++ b/api4/role_test.go @@ -129,13 +129,21 @@ func TestGetRolesByNames(t *testing.T) { assert.Contains(t, received, role2) assert.Contains(t, received, role3) - // Check a list of invalid roles. - // TODO: Confirm whether no error for invalid role names is intended. + // Check a list of non-existant roles. received, resp = th.Client.GetRolesByNames([]string{model.NewId(), model.NewId()}) CheckNoError(t, resp) + // Empty list should error. _, resp = th.SystemAdminClient.GetRolesByNames([]string{}) CheckBadRequestStatus(t, resp) + + // Invalid role name should error. + received, resp = th.Client.GetRolesByNames([]string{model.NewId(), model.NewId(), "!!!!!!"}) + CheckBadRequestStatus(t, resp) + + // Empty/whitespace rolenames should be ignored. + received, resp = th.Client.GetRolesByNames([]string{model.NewId(), model.NewId(), "", " "}) + CheckNoError(t, resp) } func TestPatchRole(t *testing.T) { -- cgit v1.2.3-1-g7c22 From d8b42070186c12f6320fe54ea1c405149846404c Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Tue, 27 Mar 2018 13:42:44 +0100 Subject: Fix typo in MANAGE_SYSTEM_WIDE_OAUTH permission. (#8516) --- model/permission.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/model/permission.go b/model/permission.go index ddbf1030e..703f6c71e 100644 --- a/model/permission.go +++ b/model/permission.go @@ -280,9 +280,9 @@ func initializePermissions() { PERMISSION_SCOPE_SYSTEM, } PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ - "manage_sytem_wide_oauth", - "authentication.permissions.manage_sytem_wide_oauth.name", - "authentication.permissions.manage_sytem_wide_oauth.description", + "manage_system_wide_oauth", + "authentication.permissions.manage_system_wide_oauth.name", + "authentication.permissions.manage_system_wide_oauth.description", PERMISSION_SCOPE_SYSTEM, } PERMISSION_CREATE_POST = &Permission{ -- cgit v1.2.3-1-g7c22