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.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
a1882d4004
Коммит
b13a228b04
25
app/permissions.go
Обычный файл
25
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
|
||||
}
|
||||
66
cmd/commands/permissions.go
Обычный файл
66
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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user