summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorGeorge Goldberg <george@gberg.me>2018-03-27 22:36:55 +0100
committerGeorge Goldberg <george@gberg.me>2018-03-27 22:36:55 +0100
commit71c9dff7662868770f66ab876ad66b354133c2c1 (patch)
treee2d5d8c5ad203b42af868ee18399c42a9ab08385 /app
parent2af4c7e6496d4c5192fedf5001817f6f1eb3664b (diff)
parente13e64711f7a7e8ceadb8cbc6af72c4022c95b36 (diff)
downloadchat-71c9dff7662868770f66ab876ad66b354133c2c1.tar.gz
chat-71c9dff7662868770f66ab876ad66b354133c2c1.tar.bz2
chat-71c9dff7662868770f66ab876ad66b354133c2c1.zip
Merge branch 'advanced-permissions-phase-1'
Diffstat (limited to 'app')
-rw-r--r--app/app.go60
-rw-r--r--app/app_test.go345
-rw-r--r--app/apptestlib.go53
-rw-r--r--app/authorization.go56
-rw-r--r--app/authorization_test.go6
-rw-r--r--app/channel.go4
-rw-r--r--app/diagnostics.go2
-rw-r--r--app/import_test.go16
-rw-r--r--app/license.go1
-rw-r--r--app/post.go11
-rw-r--r--app/post_test.go37
-rw-r--r--app/role.go86
-rw-r--r--app/team.go4
-rw-r--r--app/user.go4
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)