diff options
71 files changed, 3988 insertions, 1333 deletions
diff --git a/api/apitestlib.go b/api/apitestlib.go index 47691b2dd..6e2b8c045 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -115,6 +115,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 }) @@ -394,3 +395,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 9268d9071..c68ace31e 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -19,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) @@ -96,9 +95,14 @@ func TestCreateChannel(t *testing.T) { t.Fatal("Should have errored out on direct channel type") } - 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.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + 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} @@ -109,12 +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.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" @@ -137,46 +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 - }) - - 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. - th.App.SetLicense(nil) - - 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 }) } func TestCreateDirectChannel(t *testing.T) { @@ -347,9 +309,17 @@ func TestUpdateChannel(t *testing.T) { t.Fatal("should have failed - channel deleted") } - 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.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + 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) @@ -368,10 +338,11 @@ func TestUpdateChannel(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) + 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() @@ -383,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() @@ -404,62 +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 - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.SetLicense(model.NewTestLicense()) - - 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 - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - - 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. - th.App.SetLicense(nil) - - 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) { @@ -495,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} @@ -565,9 +468,17 @@ func TestUpdateChannelHeader(t *testing.T) { t.Fatal("should have errored non-channel member trying to update header") } - 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.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + 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) @@ -590,10 +501,11 @@ func TestUpdateChannelHeader(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) + 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() @@ -615,64 +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 - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - - 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 - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - - 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. - th.App.SetLicense(nil) - - 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) { @@ -680,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} @@ -736,9 +589,17 @@ func TestUpdateChannelPurpose(t *testing.T) { t.Fatal("should have errored non-channel member trying to update purpose") } - 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.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + 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) @@ -761,10 +622,11 @@ func TestUpdateChannelPurpose(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) + 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() @@ -786,63 +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 - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - - 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 - *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - - 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. - th.App.SetLicense(nil) - 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) { @@ -1297,9 +1102,14 @@ func TestDeleteChannel(t *testing.T) { t.Fatal("should have failed - channel already deleted") } - 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.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + 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.LoginSystemAdmin() th.LinkUserToTeam(th.BasicUser, team) @@ -1321,10 +1131,10 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) + 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) th.LoginSystemAdmin() @@ -1357,67 +1167,6 @@ func TestDeleteChannel(t *testing.T) { 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() - - th.App.SetLicense(model.NewTestLicense()) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - - 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) - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - - 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)) @@ -1442,23 +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. - th.App.SetLicense(nil) - - 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) - } } func TestGetChannelStats(t *testing.T) { @@ -1534,21 +1266,14 @@ func TestAddChannelMember(t *testing.T) { t.Fatal("Should have errored, user not on team") } - // Test policy does not apply to TE. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - 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 - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - th.App.SetLicense(model.NewTestLicense()) + 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} @@ -1558,10 +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 - }) + 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) @@ -1572,47 +1297,10 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - th.App.SetLicense(model.NewTestLicense()) 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 - }) - - 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() - th.App.SetLicense(model.NewTestLicense()) - - 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 - }) - - 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) { @@ -1684,22 +1372,14 @@ func TestRemoveChannelMember(t *testing.T) { th.LoginBasic() - // Test policy does not apply to TE. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) - - 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) - } + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - // Add a license - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - th.App.SetLicense(model.NewTestLicense()) + 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} @@ -1710,10 +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 - }) + 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) @@ -1725,49 +1405,10 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - th.App.SetLicense(model.NewTestLicense()) 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 - }) - - 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() - th.App.SetLicense(model.NewTestLicense()) - - 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 - }) - - 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/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/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 2fc79d9b1..7a2367312 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -385,7 +385,14 @@ 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.App.SetLicense(model.NewTestLicense()) + + 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) @@ -454,8 +461,7 @@ func TestUpdatePost(t *testing.T) { } // Test licensed policy controls for edit post - th.App.SetLicense(model.NewTestLicense()) - 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) @@ -468,7 +474,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} @@ -944,8 +950,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 }) - 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) @@ -998,8 +1002,11 @@ func TestDeletePosts(t *testing.T) { t.Fatal(err) } - // Test licensed policy controls for delete post - th.App.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) @@ -1011,9 +1018,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.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() @@ -1034,40 +1040,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.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. - th.App.SetLicense(nil) - - 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 f590b8e8c..e89f368ec 100644 --- a/api/team.go +++ b/api/team.go @@ -116,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 c.App.License() != nil && !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 696cf31bb..3db454b62 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -139,17 +139,17 @@ func TestAddUserToTeam(t *testing.T) { t.Fatal(err) } - // 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 }) - - // 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) - } + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - // Add an EE license. - th.App.SetLicense(model.NewTestLicense()) + // 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) // Check that a regular user can't add someone to the team. user4 := th.CreateUser(th.BasicClient) @@ -160,28 +160,17 @@ func TestAddUserToTeam(t *testing.T) { // Should work as team admin. th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() - th.App.SetLicense(model.NewTestLicense()) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) + + // 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 }) - - // 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) { @@ -515,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) @@ -546,17 +534,21 @@ func TestInviteMembers(t *testing.T) { t.Fatal("Should have errored out on no invites to send") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // 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) - } - - th.App.SetLicense(model.NewTestLicense()) - if _, err := Client.InviteMembers(invites); err == nil { t.Fatal("should have errored not team admin and licensed") } @@ -569,18 +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 }) - - 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 f4d46496b..0b3073f83 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -24,8 +24,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} @@ -91,7 +99,8 @@ func TestCreateIncomingHook(t *testing.T) { t.Fatal(err) } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + // 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) @@ -130,7 +139,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 }) + + 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) @@ -213,7 +227,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 }) + + // 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} @@ -235,7 +251,8 @@ func TestUpdateIncomingHook(t *testing.T) { }) }) - 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) Client.Logout() th.UpdateUserToTeamAdmin(user2, team) @@ -318,7 +335,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 }) + + 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) @@ -344,7 +369,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 }) + // 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) @@ -368,7 +394,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 }) + + 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) @@ -401,7 +435,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 }) + // 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") @@ -437,7 +472,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 }) + + 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"}} @@ -507,7 +550,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 }) + // 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) @@ -539,7 +583,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 }) + + 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) @@ -565,7 +617,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 }) + // 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) @@ -595,7 +648,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 }) + + 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) @@ -668,14 +729,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 }) + // 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 }) + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) Client.Logout() th.LinkUserToTeam(user3, team) @@ -762,7 +826,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 }) + + 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) @@ -795,7 +867,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 }) + // 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") @@ -829,7 +902,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 }) + + 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) @@ -863,7 +944,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 }) + // 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) diff --git a/api4/api.go b/api4/api.go index 871dca0ac..88526e4d3 100644 --- a/api4/api.go +++ b/api4/api.go @@ -98,6 +98,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_-\.]+}' @@ -196,6 +198,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.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter() api.InitUser() @@ -223,6 +227,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.InitWebrtc() api.InitOpenGraph() api.InitPlugin() + api.InitRole() api.InitImage() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index db43a6512..ac89a1f71 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -122,6 +122,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 }) @@ -803,3 +804,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 29dff883f..1670770c6 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 e65918707..d85c939b2 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -78,12 +78,16 @@ func TestCreateChannel(t *testing.T) { _, resp = Client.CreateChannel(private) CheckForbiddenStatus(t, resp) - th.LoginBasic() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + 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) - // Check permissions with policy config changes - 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.SetLicense(model.NewTestLicense()) + th.LoginBasic() channel.Name = GenerateTestChannelName() _, resp = Client.CreateChannel(channel) @@ -93,10 +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 - *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) + 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) @@ -122,46 +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 - *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - - 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. - th.App.SetLicense(nil) - - 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 { @@ -805,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) @@ -864,9 +823,14 @@ func TestDeleteChannel(t *testing.T) { th.InitBasic().InitSystemAdmin() - 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.SetLicense(model.NewTestLicense()) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + 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) Client = th.Client team = th.BasicTeam @@ -887,10 +851,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 - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) + // 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) @@ -917,115 +882,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() - th.App.SetLicense(model.NewTestLicense()) - - _, 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 - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.UpdateUserToNonTeamAdmin(user, team) - th.App.InvalidateAllCaches() - th.App.SetLicense(model.NewTestLicense()) - - // 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() - th.App.SetLicense(model.NewTestLicense()) - - _, 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 - *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - - // 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() - th.App.SetLicense(model.NewTestLicense()) - - _, 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) @@ -1768,25 +1627,13 @@ func TestAddChannelMember(t *testing.T) { _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user2.Id) CheckNoError(t, resp) - // Test policy does not apply to TE. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - 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 - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - th.App.SetLicense(model.NewTestLicense()) + 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) @@ -1800,10 +1647,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 - }) + // 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() @@ -1818,56 +1664,11 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user, privateChannel) th.App.InvalidateAllCaches() - th.App.SetLicense(model.NewTestLicense()) - - 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 - }) - - 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() - th.App.SetLicense(model.NewTestLicense()) 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 - }) - - 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) { @@ -1929,26 +1730,16 @@ func TestRemoveChannelMember(t *testing.T) { th.UpdateUserToNonTeamAdmin(user1, team) th.App.InvalidateAllCaches() - // Test policy does not apply to TE. - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN - }) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - 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 - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - th.App.SetLicense(model.NewTestLicense()) + 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) @@ -1957,10 +1748,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 - }) + // 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) @@ -1973,46 +1763,7 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, privateChannel) th.App.InvalidateAllCaches() - th.App.SetLicense(model.NewTestLicense()) _, 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 - }) - - 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() - th.App.SetLicense(model.NewTestLicense()) - - _, 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 - }) - - 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 82c8d9e6c..19778dda3 100644 --- a/api4/context.go +++ b/api4/context.go @@ -622,3 +622,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 c871dafff..0862f13f5 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -18,6 +18,14 @@ func TestCreateOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true} @@ -34,11 +42,15 @@ func TestCreateOAuthApp(t *testing.T) { t.Fatal("trusted did no match") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + // 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 }) + // 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) @@ -77,6 +89,13 @@ func TestUpdateOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) oapp := &model.OAuthApp{ @@ -155,7 +174,9 @@ func TestUpdateOAuthApp(t *testing.T) { th.LoginBasic() - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) + // Revoke permission from regular users. + th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + _, resp = Client.UpdateOAuthApp(oapp) CheckForbiddenStatus(t, resp) @@ -164,6 +185,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) @@ -182,8 +204,14 @@ func TestGetOAuthApps(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -226,7 +254,8 @@ func TestGetOAuthApps(t *testing.T) { t.Fatal("wrong apps returned") } - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) + // 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) @@ -247,8 +276,14 @@ func TestGetOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -287,7 +322,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 }) + // 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) @@ -314,8 +350,14 @@ func TestGetOAuthAppInfo(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -354,7 +396,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 }) + // 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) @@ -381,8 +424,14 @@ func TestDeleteOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -416,7 +465,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 }) + // 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) @@ -441,8 +492,14 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + // Grant permission to regular users. + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -480,7 +537,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 }) + // 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) 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 257918525..1b682e38b 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -130,7 +130,6 @@ func testCreatePostWithOutgoingHook( channel := th.BasicChannel 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" }) @@ -477,7 +476,6 @@ func TestUpdatePost(t *testing.T) { channel := th.BasicChannel th.App.SetLicense(model.NewTestLicense()) - 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) @@ -549,7 +547,6 @@ func TestPatchPost(t *testing.T) { channel := th.BasicChannel th.App.SetLicense(model.NewTestLicense()) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) post := &model.Post{ ChannelId: channel.Id, diff --git a/api4/role.go b/api4/role.go new file mode 100644 index 000000000..e7654011d --- /dev/null +++ b/api4/role.go @@ -0,0 +1,125 @@ +// 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.License() == nil && 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 + } + + 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..3fbf6808d --- /dev/null +++ b/api4/role_test.go @@ -0,0 +1,206 @@ +// 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", "manage_slash_commands"}, + 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", "create_public_channel", "manage_webhooks"}, + } + + 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", "create_public_channel", "manage_webhooks"}) + 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) + + // 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. + th.App.SetLicense(model.NewTestLicense()) + + // 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/api4/team_test.go b/api4/team_test.go index faa90e511..fa139faae 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -67,8 +67,14 @@ func TestCreateTeam(t *testing.T) { _, resp = Client.CreateTeam(rteam) CheckUnauthorizedStatus(t, resp) - // Update permission - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = false }) + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + + 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) @@ -1286,16 +1292,18 @@ func TestAddTeamMember(t *testing.T) { Client.Logout() - // 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.LoginBasic() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - // Test without the EE license to see that the permission restriction is ignored. - _, resp = Client.AddTeamMember(team.Id, otherUser.Id) - CheckNoError(t, resp) + // 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) - // Add an EE license. - th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1305,38 +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 }) - th.App.SetLicense(model.NewTestLicense()) 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 }) + // 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 }) - th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a regular user. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) CheckNoError(t, resp) - th.LoginBasic() - // by hash and data Client.Login(otherUser.Email, otherUser.Password) @@ -1474,16 +1470,18 @@ func TestAddTeamMembers(t *testing.T) { Client.Logout() - // 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.LoginBasic() + // Check the appropriate permissions are enforced. + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() - // Test without the EE license to see that the permission restriction is ignored. - _, resp = Client.AddTeamMembers(team.Id, userList) - CheckNoError(t, resp) + // 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) - // Add an EE license. - th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1493,30 +1491,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 }) - th.App.SetLicense(model.NewTestLicense()) 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 }) - - // 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 }) - th.App.SetLicense(model.NewTestLicense()) 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 636f0a428..9d44c358c 100644 --- a/app/app.go +++ b/app/app.go @@ -8,6 +8,7 @@ import ( "html/template" "net" "net/http" + "reflect" "strings" "sync" "sync/atomic" @@ -26,6 +27,8 @@ import ( "github.com/mattermost/mattermost-server/utils" ) +const ADVANCED_PERMISSIONS_MIGRATION_KEY = "AdvancedPermissionsMigrationComplete" + type App struct { goroutineCount int32 goroutineExitSignal chan struct{} @@ -67,7 +70,6 @@ type App struct { htmlTemplateWatcher *utils.HTMLTemplateWatcher sessionCache *utils.Cache - roles map[string]*model.Role configListenerId string licenseListenerId string disableConfigWatch bool @@ -132,7 +134,6 @@ func New(options ...Option) (outApp *App, outErr error) { }) app.licenseListenerId = app.AddLicenseListener(app.configOrLicenseListener) app.regenerateClientConfig() - app.setDefaultRolesBasedOnConfig() l4g.Info(utils.T("api.server.new_server.init.info")) @@ -173,7 +174,6 @@ func New(options ...Option) (outApp *App, outErr error) { func (a *App) configOrLicenseListener() { a.regenerateClientConfig() - a.setDefaultRolesBasedOnConfig() } func (a *App) Shutdown() { @@ -468,3 +468,57 @@ func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) } + +// 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(), a.License() != nil) + + 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 09f8725d7..f31e0332c 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -5,11 +5,11 @@ package app import ( "flag" + "fmt" "os" "testing" l4g "github.com/alecthomas/log4go" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -72,3 +72,336 @@ 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_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, + }, + "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_CREATE_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_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, + model.PERMISSION_EDIT_POST.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. + 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 }) + }() + + 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.SetLicense(model.NewTestLicense()) + + // 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_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, + }, + "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_CREATE_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_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, + model.PERMISSION_EDIT_POST.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, fmt.Sprintf("'%v' did not have expected permissions", name)) + } + + // Remove the license. + th.App.SetLicense(nil) + + // 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 c7846c9b5..3402f1f79 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() { @@ -102,6 +107,9 @@ func setupTestHelper(enterprise bool) *TestHelper { } 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 }) @@ -225,6 +233,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() @@ -318,3 +327,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 8ac1f421c..af36774de 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/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/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/license.go b/app/license.go index efb725a20..6a2206747 100644 --- a/app/license.go +++ b/app/license.go @@ -113,7 +113,6 @@ func (a *App) License() *model.License { func (a *App) SetLicense(license *model.License) bool { defer func() { - a.setDefaultRolesBasedOnConfig() for _, listener := range a.licenseListeners { listener() } diff --git a/app/post.go b/app/post.go index be9374e10..3f6252faa 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 a.License() != nil && *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) } @@ -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 409bc043d..207935d58 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -43,6 +43,43 @@ func TestUpdatePostEditAt(t *testing.T) { } } +func TestUpdatePostTimeLimit(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + post := &model.Post{} + *post = *th.BasicPost + + th.App.SetLicense(model.NewTestLicense()) + + 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/app/role.go b/app/role.go index 9f271ea7a..c99d8365b 100644 --- a/app/role.go +++ b/app/role.go @@ -1,17 +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 } -func (a *App) setDefaultRolesBasedOnConfig() { - a.roles = utils.DefaultRolesBasedOnConfig(a.Config(), a.License() != nil) +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 8e8c29e2a..dc10760f8 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 69c6d072b..70ed83c0b 100644 --- a/app/user.go +++ b/app/user.go @@ -1231,6 +1231,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 1b411cf20..c3001a77d 100644 --- a/cmd/platform/server.go +++ b/cmd/platform/server.go @@ -79,6 +79,8 @@ func runServer(configFileLocation string, disableConfigWatch bool, interruptChan 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/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/i18n/en.json b/i18n/en.json index de910fca8..5befd9fc5 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -48,6 +48,10 @@ "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." }, @@ -2335,10 +2339,18 @@ "translation": "Your email address for {{.TeamDisplayName}} has been changed to {{.NewEmail}}.<br>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" }, @@ -6439,6 +6451,26 @@ "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/client4.go b/model/client4.go index 962b816bb..4b50aa05f 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 f060c4ac8..cf9e3f9f2 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/config.go b/model/config.go index 9010eaeae..f20a11ab8 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 { @@ -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/model/authorization.go b/model/permission.go index 9f4e36eab..0dc09f49a 100644 --- a/model/authorization.go +++ b/model/permission.go @@ -3,17 +3,17 @@ 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"` -} - -type Role struct { - Id string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Permissions []string `json:"permissions"` + Scope string `json:"scope"` } var PERMISSION_INVITE_USER *Permission @@ -64,459 +64,355 @@ 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 +// 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 -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" -) +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__publicchannel_properties", + "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", - } -} - -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, - }, + PERMISSION_SCOPE_SYSTEM, } - 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, - }, + 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, } - - 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 "[<NO ROLES>]" - } - - return output[:len(output)-1] } func init() { initializePermissions() - initializeDefaultRoles() } 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..5f7352f12 --- /dev/null +++ b/model/role.go @@ -0,0 +1,347 @@ +// 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"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + 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 + } +} + +// 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 + } + + 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_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 1e1d49f7d..d915307ad 100644 --- a/model/user.go +++ b/model/user.go @@ -428,7 +428,7 @@ func IsValidUserRoles(userRoles string) bool { roles := strings.Fields(userRoles) for _, r := range roles { - if !isValidRole(r) { + if !IsValidRoleName(r) { return false } } @@ -441,11 +441,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 2bf8b2a65..72ad6a92b 100644 --- a/model/user_test.go +++ b/model/user_test.go @@ -287,11 +287,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 0256e400f..a1427e196 100644 --- a/model/websocket_message.go +++ b/model/websocket_message.go @@ -43,6 +43,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..8cbde0a23 --- /dev/null +++ b/store/local_cache_supplier_roles.go @@ -0,0 +1,70 @@ +// 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 { + // 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 *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..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,48 +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...) -} 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) +} 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..f9ce53788 --- /dev/null +++ b/store/sqlstore/role_supplier.go @@ -0,0 +1,175 @@ +// 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 + CreateAt int64 + UpdateAt int64 + DeleteAt int64 + 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, + CreateAt: role.CreateAt, + UpdateAt: role.UpdateAt, + DeleteAt: role.DeleteAt, + 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, + CreateAt: role.CreateAt, + UpdateAt: role.UpdateAt, + DeleteAt: role.DeleteAt, + 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() + 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 { + 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/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) diff --git a/store/store.go b/store/store.go index 85f215ab9..9364218c8 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 @@ -462,3 +463,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 42815b807..bc71404ef 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, isLicensed bool) 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, isLicensed bool) map[string]*model.Role { if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelCreation { case model.PERMISSIONS_ALL: @@ -38,8 +31,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m 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: @@ -58,8 +51,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m ) } } 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, ) } @@ -67,8 +60,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m 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: @@ -87,8 +80,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m ) } } 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, ) } @@ -116,8 +109,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m 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: @@ -136,8 +129,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m ) } } 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, ) } @@ -145,8 +138,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m 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: @@ -165,8 +158,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m ) } } 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, ) } @@ -222,8 +215,8 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m 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, isLicensed bool) map[string]*m 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, @@ -272,12 +260,35 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m ) } - 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, ) } + 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 } diff --git a/utils/authorization_test.go b/utils/authorization_test.go new file mode 100644 index 000000000..8c78dcbda --- /dev/null +++ b/utils/authorization_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "reflect" + "strconv" + "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) + } + + 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 { + for _, p := range role.Permissions { + if p == permission { + return true + } + } + return false +} diff --git a/utils/config.go b/utils/config.go index a855733a7..10ada1728 100644 --- a/utils/config.go +++ b/utils/config.go @@ -354,7 +354,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L 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 new file mode 100644 index 000000000..1b2acdfcb --- /dev/null +++ b/utils/policies-roles-mapping.json @@ -0,0 +1,532 @@ +{ + "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": "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": "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": "team_admin", + "permission": "delete_post", + "shouldHave": false + }, + { + "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_user", + "permission": "manage_slash_commands", + "shouldHave": false + }, + { + "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 diff --git a/web/web_test.go b/web/web_test.go index c8d64c61d..e4b9d820f 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -55,6 +55,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) { |