summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Goldberg <george@gberg.me>2018-04-18 10:18:07 +0100
committerGitHub <noreply@github.com>2018-04-18 10:18:07 +0100
commitb13a228b0451098ea32933a36fe64566e366583d (patch)
tree569aa0c6c5f273b806b4c3e0c59801204916492c
parenta1882d4004ce624dad1b8624804e974f209efe8d (diff)
downloadchat-b13a228b0451098ea32933a36fe64566e366583d.tar.gz
chat-b13a228b0451098ea32933a36fe64566e366583d.tar.bz2
chat-b13a228b0451098ea32933a36fe64566e366583d.zip
MM-10121: CLI command to reset permissions system to default state. (#8637)
* MM-10121: CLI command to reset permissions system to default state. * Review comment.
-rw-r--r--app/permissions.go25
-rw-r--r--cmd/commands/permissions.go66
-rw-r--r--i18n/en.json8
-rw-r--r--store/layered_store.go6
-rw-r--r--store/layered_store_supplier.go1
-rw-r--r--store/local_cache_supplier_roles.go7
-rw-r--r--store/redis_supplier_roles.go14
-rw-r--r--store/sqlstore/role_supplier.go10
-rw-r--r--store/sqlstore/system_store.go11
-rw-r--r--store/store.go2
-rw-r--r--store/storetest/mocks/LayeredStoreDatabaseLayer.go23
-rw-r--r--store/storetest/mocks/LayeredStoreSupplier.go23
-rw-r--r--store/storetest/mocks/PostStore.go31
-rw-r--r--store/storetest/mocks/RoleStore.go16
-rw-r--r--store/storetest/mocks/SystemStore.go16
-rw-r--r--store/storetest/role_store.go40
-rw-r--r--store/storetest/system_store.go36
17 files changed, 320 insertions, 15 deletions
diff --git a/app/permissions.go b/app/permissions.go
new file mode 100644
index 000000000..be975e03d
--- /dev/null
+++ b/app/permissions.go
@@ -0,0 +1,25 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/mattermost-server/model"
+)
+
+func (a *App) ResetPermissionsSystem() *model.AppError {
+ // Purge all roles from the database.
+ if result := <-a.Srv.Store.Role().PermanentDeleteAll(); result.Err != nil {
+ return result.Err
+ }
+
+ // Remove the "System" table entry that marks the advanced permissions migration as done.
+ if result := <-a.Srv.Store.System().PermanentDeleteByName(ADVANCED_PERMISSIONS_MIGRATION_KEY); result.Err != nil {
+ return result.Err
+ }
+
+ // Now that the permissions system has been reset, re-run the migration to reinitialise it.
+ a.DoAdvancedPermissionsMigration()
+
+ return nil
+}
diff --git a/cmd/commands/permissions.go b/cmd/commands/permissions.go
new file mode 100644
index 000000000..33c255a31
--- /dev/null
+++ b/cmd/commands/permissions.go
@@ -0,0 +1,66 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package commands
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/mattermost/mattermost-server/cmd"
+)
+
+var PermissionsCmd = &cobra.Command{
+ Use: "permissions",
+ Short: "Management of the Permissions system",
+}
+
+var ResetPermissionsCmd = &cobra.Command{
+ Use: "reset",
+ Short: "Reset the permissions system to its default state",
+ Long: "Reset the permissions system to its default state",
+ Example: " permissions reset",
+ RunE: resetPermissionsCmdF,
+}
+
+func init() {
+ ResetPermissionsCmd.Flags().Bool("confirm", false, "Confirm you really want to reset the permissions system and a database backup has been performed.")
+
+ PermissionsCmd.AddCommand(
+ ResetPermissionsCmd,
+ )
+ cmd.RootCmd.AddCommand(PermissionsCmd)
+}
+
+func resetPermissionsCmdF(command *cobra.Command, args []string) error {
+ a, err := cmd.InitDBCommandContextCobra(command)
+ if err != nil {
+ return err
+ }
+
+ confirmFlag, _ := command.Flags().GetBool("confirm")
+ if !confirmFlag {
+ var confirm string
+ cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
+ fmt.Scanln(&confirm)
+
+ if confirm != "YES" {
+ return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
+ }
+ cmd.CommandPrettyPrintln("Are you sure you want to reset the permissions system? All data related to the permissions system will be permanently deleted and all users will revert to having the default permissions. (YES/NO): ")
+ fmt.Scanln(&confirm)
+ if confirm != "YES" {
+ return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
+ }
+ }
+
+ if err := a.ResetPermissionsSystem(); err != nil {
+ return errors.New(err.Error())
+ }
+
+ cmd.CommandPrettyPrintln("Permissions system successfully reset")
+
+ return nil
+}
diff --git a/i18n/en.json b/i18n/en.json
index e66b88a82..d71ad8009 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -6563,6 +6563,14 @@
"translation": "Searching has been disabled on this server. Please contact your System Administrator."
},
{
+ "id": "store.sql_system.permanent_delete_by_name.app_error",
+ "translation": "We could not permanently delete the system table entry"
+ },
+ {
+ "id": "store.sql_role.permanent_delete_all.app_error",
+ "translation": "We could not permanently delete all the roles"
+ },
+ {
"id": "store.sql_post.search.warn",
"translation": "Query error searching posts: %v"
},
diff --git a/store/layered_store.go b/store/layered_store.go
index cac0f61d3..5ef907260 100644
--- a/store/layered_store.go
+++ b/store/layered_store.go
@@ -252,3 +252,9 @@ func (s *LayeredRoleStore) GetByNames(names []string) StoreChannel {
return supplier.RoleGetByNames(s.TmpContext, names)
})
}
+
+func (s *LayeredRoleStore) PermanentDeleteAll() StoreChannel {
+ return s.RunQuery(func(supplier LayeredStoreSupplier) *LayeredStoreSupplierResult {
+ return supplier.RolePermanentDeleteAll(s.TmpContext)
+ })
+}
diff --git a/store/layered_store_supplier.go b/store/layered_store_supplier.go
index 80fe3cb24..9a7604b20 100644
--- a/store/layered_store_supplier.go
+++ b/store/layered_store_supplier.go
@@ -35,4 +35,5 @@ type LayeredStoreSupplier interface {
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
+ RolePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult
}
diff --git a/store/local_cache_supplier_roles.go b/store/local_cache_supplier_roles.go
index 8cbde0a23..7c82f60eb 100644
--- a/store/local_cache_supplier_roles.go
+++ b/store/local_cache_supplier_roles.go
@@ -68,3 +68,10 @@ func (s *LocalCacheSupplier) RoleGetByNames(ctx context.Context, roleNames []str
return result
}
+
+func (s *LocalCacheSupplier) RolePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ defer s.roleCache.Purge()
+ defer s.doClearCacheCluster(s.roleCache)
+
+ return s.Next().RolePermanentDeleteAll(ctx, hints...)
+}
diff --git a/store/redis_supplier_roles.go b/store/redis_supplier_roles.go
index 170420f1f..232a8c040 100644
--- a/store/redis_supplier_roles.go
+++ b/store/redis_supplier_roles.go
@@ -84,6 +84,20 @@ func (s *RedisSupplier) RoleGetByNames(ctx context.Context, roleNames []string,
return result
}
+func (s *RedisSupplier) RolePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *LayeredStoreSupplierResult {
+ defer func() {
+ if keys, err := s.client.Keys("roles:*").Result(); err != nil {
+ l4g.Error("Redis encountered an error on read: " + err.Error())
+ } else {
+ if err := s.client.Del(keys...).Err(); err != nil {
+ l4g.Error("Redis encountered an error on delete: " + err.Error())
+ }
+ }
+ }()
+
+ return s.Next().RolePermanentDeleteAll(ctx, hints...)
+}
+
func buildRedisKeyForRoleName(roleName string) string {
return fmt.Sprintf("roles:%s", roleName)
}
diff --git a/store/sqlstore/role_supplier.go b/store/sqlstore/role_supplier.go
index 828f4484e..ddbdaca52 100644
--- a/store/sqlstore/role_supplier.go
+++ b/store/sqlstore/role_supplier.go
@@ -174,3 +174,13 @@ func (s *SqlSupplier) RoleGetByNames(ctx context.Context, names []string, hints
return result
}
+
+func (s *SqlSupplier) RolePermanentDeleteAll(ctx context.Context, hints ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult {
+ result := store.NewSupplierResult()
+
+ if _, err := s.GetMaster().Exec("DELETE FROM Roles"); err != nil {
+ result.Err = model.NewAppError("SqlRoleStore.PermanentDeleteAll", "store.sql_role.permanent_delete_all.app_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return result
+}
diff --git a/store/sqlstore/system_store.go b/store/sqlstore/system_store.go
index 496ff2ced..4065bb955 100644
--- a/store/sqlstore/system_store.go
+++ b/store/sqlstore/system_store.go
@@ -85,3 +85,14 @@ func (s SqlSystemStore) GetByName(name string) store.StoreChannel {
result.Data = &system
})
}
+
+func (s SqlSystemStore) PermanentDeleteByName(name string) store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ var system model.System
+ if _, err := s.GetReplica().Exec("DELETE FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil {
+ result.Err = model.NewAppError("SqlSystemStore.PermanentDeleteByName", "store.sql_system.permanent_delete_by_name.app_error", nil, "", http.StatusInternalServerError)
+ }
+
+ result.Data = &system
+ })
+}
diff --git a/store/store.go b/store/store.go
index 79a14baba..e64089068 100644
--- a/store/store.go
+++ b/store/store.go
@@ -322,6 +322,7 @@ type SystemStore interface {
Update(system *model.System) StoreChannel
Get() StoreChannel
GetByName(name string) StoreChannel
+ PermanentDeleteByName(name string) StoreChannel
}
type WebhookStore interface {
@@ -476,4 +477,5 @@ type RoleStore interface {
Get(roleId string) StoreChannel
GetByName(name string) StoreChannel
GetByNames(names []string) StoreChannel
+ PermanentDeleteAll() StoreChannel
}
diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go
index d0162a01e..6fa31bb1b 100644
--- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go
+++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go
@@ -501,6 +501,29 @@ func (_m *LayeredStoreDatabaseLayer) RoleGetByNames(ctx context.Context, names [
return r0
}
+// RolePermanentDeleteAll provides a mock function with given fields: ctx, hints
+func (_m *LayeredStoreDatabaseLayer) RolePermanentDeleteAll(ctx context.Context, 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)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, 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))
diff --git a/store/storetest/mocks/LayeredStoreSupplier.go b/store/storetest/mocks/LayeredStoreSupplier.go
index 59fd31cb8..d4242708b 100644
--- a/store/storetest/mocks/LayeredStoreSupplier.go
+++ b/store/storetest/mocks/LayeredStoreSupplier.go
@@ -214,6 +214,29 @@ func (_m *LayeredStoreSupplier) RoleGetByNames(ctx context.Context, names []stri
return r0
}
+// RolePermanentDeleteAll provides a mock function with given fields: ctx, hints
+func (_m *LayeredStoreSupplier) RolePermanentDeleteAll(ctx context.Context, 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)
+ _ca = append(_ca, _va...)
+ ret := _m.Called(_ca...)
+
+ var r0 *store.LayeredStoreSupplierResult
+ if rf, ok := ret.Get(0).(func(context.Context, ...store.LayeredStoreHint) *store.LayeredStoreSupplierResult); ok {
+ r0 = rf(ctx, 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))
diff --git a/store/storetest/mocks/PostStore.go b/store/storetest/mocks/PostStore.go
index bdd0d1d16..bdfbb3321 100644
--- a/store/storetest/mocks/PostStore.go
+++ b/store/storetest/mocks/PostStore.go
@@ -162,6 +162,22 @@ func (_m *PostStore) GetFlaggedPostsForTeam(userId string, teamId string, offset
return r0
}
+// GetMaxPostSize provides a mock function with given fields:
+func (_m *PostStore) GetMaxPostSize() store.StoreChannel {
+ ret := _m.Called()
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func() store.StoreChannel); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
// GetOldest provides a mock function with given fields:
func (_m *PostStore) GetOldest() store.StoreChannel {
ret := _m.Called()
@@ -422,18 +438,3 @@ func (_m *PostStore) Update(newPost *model.Post, oldPost *model.Post) store.Stor
return r0
}
-
-func (_m *PostStore) GetMaxPostSize() store.StoreChannel {
- ret := _m.Called()
-
- var r0 store.StoreChannel
- if rf, ok := ret.Get(0).(func() store.StoreChannel); ok {
- r0 = rf()
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(store.StoreChannel)
- }
- }
-
- return r0
-}
diff --git a/store/storetest/mocks/RoleStore.go b/store/storetest/mocks/RoleStore.go
index 8150460ae..3c01ee341 100644
--- a/store/storetest/mocks/RoleStore.go
+++ b/store/storetest/mocks/RoleStore.go
@@ -61,6 +61,22 @@ func (_m *RoleStore) GetByNames(names []string) store.StoreChannel {
return r0
}
+// PermanentDeleteAll provides a mock function with given fields:
+func (_m *RoleStore) PermanentDeleteAll() store.StoreChannel {
+ ret := _m.Called()
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func() store.StoreChannel); ok {
+ r0 = rf()
+ } 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)
diff --git a/store/storetest/mocks/SystemStore.go b/store/storetest/mocks/SystemStore.go
index 2c5774fcf..b31e4646d 100644
--- a/store/storetest/mocks/SystemStore.go
+++ b/store/storetest/mocks/SystemStore.go
@@ -45,6 +45,22 @@ func (_m *SystemStore) GetByName(name string) store.StoreChannel {
return r0
}
+// PermanentDeleteByName provides a mock function with given fields: name
+func (_m *SystemStore) PermanentDeleteByName(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
+}
+
// Save provides a mock function with given fields: system
func (_m *SystemStore) Save(system *model.System) store.StoreChannel {
ret := _m.Called(system)
diff --git a/store/storetest/role_store.go b/store/storetest/role_store.go
index 499e36e1e..e51c32622 100644
--- a/store/storetest/role_store.go
+++ b/store/storetest/role_store.go
@@ -17,6 +17,7 @@ func TestRoleStore(t *testing.T, ss store.Store) {
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) })
+ t.Run("PermanentDeleteAll", func(t *testing.T) { testRoleStorePermanentDeleteAll(t, ss) })
}
func testRoleStoreSave(t *testing.T, ss store.Store) {
@@ -242,3 +243,42 @@ func testRoleStoreGetByNames(t *testing.T, ss store.Store) {
assert.NotContains(t, roles6, d2)
assert.NotContains(t, roles6, d3)
}
+
+func testRoleStorePermanentDeleteAll(t *testing.T, ss store.Store) {
+ 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,
+ }
+
+ store.Must(ss.Role().Save(r1))
+ store.Must(ss.Role().Save(r2))
+
+ res1 := <-ss.Role().GetByNames([]string{r1.Name, r2.Name})
+ assert.Nil(t, res1.Err)
+ assert.Len(t, res1.Data.([]*model.Role), 2)
+
+ res2 := <-ss.Role().PermanentDeleteAll()
+ assert.Nil(t, res2.Err)
+
+ res3 := <-ss.Role().GetByNames([]string{r1.Name, r2.Name})
+ assert.Nil(t, res3.Err)
+ assert.Len(t, res3.Data.([]*model.Role), 0)
+}
diff --git a/store/storetest/system_store.go b/store/storetest/system_store.go
index 32c39ee41..a06b72a83 100644
--- a/store/storetest/system_store.go
+++ b/store/storetest/system_store.go
@@ -6,6 +6,8 @@ package storetest
import (
"testing"
+ "github.com/stretchr/testify/assert"
+
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
@@ -13,6 +15,7 @@ import (
func TestSystemStore(t *testing.T, ss store.Store) {
t.Run("", func(t *testing.T) { testSystemStore(t, ss) })
t.Run("SaveOrUpdate", func(t *testing.T) { testSystemStoreSaveOrUpdate(t, ss) })
+ t.Run("PermanentDeleteByName", func(t *testing.T) { testSystemStorePermanentDeleteByName(t, ss) })
}
func testSystemStore(t *testing.T, ss store.Store) {
@@ -56,3 +59,36 @@ func testSystemStoreSaveOrUpdate(t *testing.T, ss store.Store) {
t.Fatal(r.Err)
}
}
+
+func testSystemStorePermanentDeleteByName(t *testing.T, ss store.Store) {
+ s1 := &model.System{Name: model.NewId(), Value: "value"}
+ s2 := &model.System{Name: model.NewId(), Value: "value"}
+
+ store.Must(ss.System().Save(s1))
+ store.Must(ss.System().Save(s2))
+
+ res1 := <-ss.System().GetByName(s1.Name)
+ assert.Nil(t, res1.Err)
+
+ res2 := <-ss.System().GetByName(s2.Name)
+ assert.Nil(t, res2.Err)
+
+ res3 := <-ss.System().PermanentDeleteByName(s1.Name)
+ assert.Nil(t, res3.Err)
+
+ res4 := <-ss.System().GetByName(s1.Name)
+ assert.NotNil(t, res4.Err)
+
+ res5 := <-ss.System().GetByName(s2.Name)
+ assert.Nil(t, res5.Err)
+
+ res6 := <-ss.System().PermanentDeleteByName(s2.Name)
+ assert.Nil(t, res6.Err)
+
+ res7 := <-ss.System().GetByName(s1.Name)
+ assert.NotNil(t, res7.Err)
+
+ res8 := <-ss.System().GetByName(s2.Name)
+ assert.NotNil(t, res8.Err)
+
+}