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.
This commit is contained in:
George Goldberg
2018-04-18 10:18:07 +01:00
committed by GitHub
parent a1882d4004
commit b13a228b04
17 changed files with 320 additions and 15 deletions
+25
View File
@@ -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
}
+66
View File
@@ -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
}
+8
View File
@@ -6562,6 +6562,14 @@
"id": "store.sql_post.search.disabled",
"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"
+6
View File
@@ -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)
})
}
+1
View File
@@ -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
}
+7
View File
@@ -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...)
}
+14
View File
@@ -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)
}
+10
View File
@@ -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
}
+11
View File
@@ -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
})
}
+2
View File
@@ -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
}
@@ -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))
@@ -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))
+16 -15
View File
@@ -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
}
+16
View File
@@ -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)
+16
View File
@@ -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)
+40
View File
@@ -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)
}
+36
View File
@@ -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)
}