Unverified Commit b13a228b authored by George Goldberg's avatar George Goldberg Committed by GitHub

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.
parent a1882d40
// 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
}
// 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
}
......@@ -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"
......
......@@ -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)
})
}
......@@ -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
}
......@@ -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...)
}
......@@ -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)
}
......@@ -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
}
......@@ -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
})
}
......@@ -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))
......
......@@ -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
}
......@@ -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)
......
......@@ -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)
......
......@@ -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)
}
......@@ -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)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment