diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/app.go | 60 | ||||
-rw-r--r-- | app/app_test.go | 345 | ||||
-rw-r--r-- | app/apptestlib.go | 53 | ||||
-rw-r--r-- | app/authorization.go | 56 | ||||
-rw-r--r-- | app/authorization_test.go | 6 | ||||
-rw-r--r-- | app/channel.go | 4 | ||||
-rw-r--r-- | app/diagnostics.go | 2 | ||||
-rw-r--r-- | app/import_test.go | 16 | ||||
-rw-r--r-- | app/license.go | 1 | ||||
-rw-r--r-- | app/post.go | 11 | ||||
-rw-r--r-- | app/post_test.go | 37 | ||||
-rw-r--r-- | app/role.go | 86 | ||||
-rw-r--r-- | app/team.go | 4 | ||||
-rw-r--r-- | app/user.go | 4 |
14 files changed, 624 insertions, 61 deletions
diff --git a/app/app.go b/app/app.go index cd9fdaa66..27227d271 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{} @@ -71,7 +74,6 @@ type App struct { htmlTemplateWatcher *utils.HTMLTemplateWatcher sessionCache *utils.Cache - roles map[string]*model.Role configListenerId string licenseListenerId string disableConfigWatch bool @@ -155,7 +157,6 @@ func New(options ...Option) (outApp *App, outErr error) { }) app.regenerateClientConfig() - app.setDefaultRolesBasedOnConfig() l4g.Info(utils.T("api.server.new_server.init.info")) @@ -196,7 +197,6 @@ func New(options ...Option) (outApp *App, outErr error) { func (a *App) configOrLicenseListener() { a.regenerateClientConfig() - a.setDefaultRolesBasedOnConfig() } func (a *App) Shutdown() { @@ -495,3 +495,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..c2841ec53 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,346 @@ 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_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_EDIT_POST.Id, + }, + "channel_admin": []string{ + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + }, + "team_user": []string{ + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + }, + "team_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "team_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "team_admin": []string{ + model.PERMISSION_EDIT_OTHERS_POSTS.Id, + model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, + model.PERMISSION_MANAGE_TEAM.Id, + model.PERMISSION_IMPORT_TEAM.Id, + model.PERMISSION_MANAGE_TEAM_ROLES.Id, + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + }, + "system_user": []string{ + model.PERMISSION_CREATE_DIRECT_CHANNEL.Id, + model.PERMISSION_CREATE_GROUP_CHANNEL.Id, + model.PERMISSION_PERMANENT_DELETE_USER.Id, + model.PERMISSION_CREATE_TEAM.Id, + }, + "system_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "system_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "system_user_access_token": []string{ + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + "system_admin": []string{ + model.PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + model.PERMISSION_MANAGE_SYSTEM.Id, + model.PERMISSION_MANAGE_ROLES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_EDIT_OTHER_USERS.Id, + model.PERMISSION_MANAGE_OAUTH.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + model.PERMISSION_CREATE_TEAM.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + model.PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, + model.PERMISSION_MANAGE_JOBS.Id, + model.PERMISSION_CREATE_POST_PUBLIC.Id, + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + 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_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + model.PERMISSION_USE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_EDIT_POST.Id, + }, + "channel_admin": []string{ + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + }, + "team_user": []string{ + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + }, + "team_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "team_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "team_admin": []string{ + model.PERMISSION_EDIT_OTHERS_POSTS.Id, + model.PERMISSION_REMOVE_USER_FROM_TEAM.Id, + model.PERMISSION_MANAGE_TEAM.Id, + model.PERMISSION_IMPORT_TEAM.Id, + model.PERMISSION_MANAGE_TEAM_ROLES.Id, + model.PERMISSION_MANAGE_CHANNEL_ROLES.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + model.PERMISSION_MANAGE_WEBHOOKS.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + }, + "system_user": []string{ + model.PERMISSION_CREATE_DIRECT_CHANNEL.Id, + model.PERMISSION_CREATE_GROUP_CHANNEL.Id, + model.PERMISSION_PERMANENT_DELETE_USER.Id, + model.PERMISSION_CREATE_TEAM.Id, + }, + "system_post_all": []string{ + model.PERMISSION_CREATE_POST.Id, + }, + "system_post_all_public": []string{ + model.PERMISSION_CREATE_POST_PUBLIC.Id, + }, + "system_user_access_token": []string{ + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + "system_admin": []string{ + model.PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + model.PERMISSION_MANAGE_SYSTEM.Id, + model.PERMISSION_MANAGE_ROLES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + model.PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + model.PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + model.PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + model.PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + model.PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + model.PERMISSION_EDIT_OTHER_USERS.Id, + model.PERMISSION_MANAGE_OAUTH.Id, + model.PERMISSION_INVITE_USER.Id, + model.PERMISSION_DELETE_POST.Id, + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + model.PERMISSION_CREATE_TEAM.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + model.PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, + model.PERMISSION_MANAGE_JOBS.Id, + model.PERMISSION_CREATE_POST_PUBLIC.Id, + model.PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_READ_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + model.PERMISSION_REMOVE_OTHERS_REACTIONS.Id, + model.PERMISSION_LIST_TEAM_CHANNELS.Id, + model.PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + model.PERMISSION_READ_PUBLIC_CHANNEL.Id, + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_ADD_REACTION.Id, + model.PERMISSION_REMOVE_REACTION.Id, + model.PERMISSION_UPLOAD_FILE.Id, + model.PERMISSION_GET_PUBLIC_LINK.Id, + model.PERMISSION_CREATE_POST.Id, + 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 01f5b0102..6c2273c6e 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 }) @@ -217,6 +225,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() @@ -326,3 +335,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 4231cac77..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 } } @@ -181,17 +181,33 @@ func (a *App) HasPermissionToChannelByPost(askingUserId string, postId string, p return a.HasPermissionTo(askingUserId, permission) } -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) HasPermissionToUser(askingUserId string, userId string) bool { + if askingUserId == userId { + return true + } + + if a.HasPermissionTo(askingUserId, model.PERMISSION_EDIT_OTHER_USERS) { + return true + } + + return false +} + +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 49a797f15..eadb94c2f 100644 --- a/app/channel.go +++ b/app/channel.go @@ -439,6 +439,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 4cff5f02a..b08c4f86b 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -249,7 +249,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 23213d81b..073741b19 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -412,10 +412,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.") @@ -479,12 +475,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 { @@ -517,12 +507,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 c12f23d1d..148b10317 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 d9445155b..4ce009369 100644 --- a/app/post.go +++ b/app/post.go @@ -124,7 +124,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) } @@ -326,13 +326,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 @@ -349,7 +342,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 8455656d7..10b957751 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -48,6 +48,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 239ce4369..a7b32af33 100644 --- a/app/team.go +++ b/app/team.go @@ -160,6 +160,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 e1ba0e79f..8d3ec11be 100644 --- a/app/user.go +++ b/app/user.go @@ -1241,6 +1241,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) |