Files
Felipe MartinandGitHub 647b7e5b6f MM-68830: Preserve unknown permissions during migrations on downgrade (#36888) (#37006)
* MM-68830: Preserve unknown permissions during migrations on downgrade

A server that was upgraded to a newer release (which introduced new
permissions and wrote them into roles) and then downgraded fails fatally
at startup: the permissions migration re-saves every role, and
Role.Save() rejects any permission the older binary does not recognize,
making the downgrade unrecoverable.

Add RoleStore.SavePreservingUnknownPermissions, used only by
doPermissionsMigration, which tolerates and preserves permissions this
build does not recognize (logging a warning) instead of rejecting the
role. The regular Save() — and therefore the role API path — stays
strict, so unknown permissions cannot be introduced through user input.

Unrecognized permissions are kept on disk so they are not lost on a
later re-upgrade.

* MM-68830: assert save forwarding in role cache tests

Address review feedback: assert the underlying store's Save and
SavePreservingUnknownPermissions are actually invoked (the cache
invalidation defer fires regardless of forwarding), and check the
returned errors.

* MM-68830: address review feedback

- Shorten log message in validateForSave
- Rename validationRole -> roleCopy for clarity
- Trim doc comments to describe behavior only
- List all unknown permissions in IsValidWithoutId error
- Assert specific error type in storetest

* MM-68830: add Role.Clone and use it in validateForSave

* MM-68830: add tests for Role.Clone

* MM-68830: fix scheme id deep copy assertion in Role.Clone test
2026-06-11 15:33:13 +02:00

350 lines
7.6 KiB
Go

// Code generated by mockery v2.53.4. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
context "context"
model "github.com/mattermost/mattermost/server/public/model"
mock "github.com/stretchr/testify/mock"
)
// RoleStore is an autogenerated mock type for the RoleStore type
type RoleStore struct {
mock.Mock
}
// AllChannelSchemeRoles provides a mock function with no fields
func (_m *RoleStore) AllChannelSchemeRoles() ([]*model.Role, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for AllChannelSchemeRoles")
}
var r0 []*model.Role
var r1 error
if rf, ok := ret.Get(0).(func() ([]*model.Role, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []*model.Role); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Role)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ChannelHigherScopedPermissions provides a mock function with given fields: roleNames
func (_m *RoleStore) ChannelHigherScopedPermissions(roleNames []string) (map[string]*model.RolePermissions, error) {
ret := _m.Called(roleNames)
if len(ret) == 0 {
panic("no return value specified for ChannelHigherScopedPermissions")
}
var r0 map[string]*model.RolePermissions
var r1 error
if rf, ok := ret.Get(0).(func([]string) (map[string]*model.RolePermissions, error)); ok {
return rf(roleNames)
}
if rf, ok := ret.Get(0).(func([]string) map[string]*model.RolePermissions); ok {
r0 = rf(roleNames)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]*model.RolePermissions)
}
}
if rf, ok := ret.Get(1).(func([]string) error); ok {
r1 = rf(roleNames)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ChannelRolesUnderTeamRole provides a mock function with given fields: roleName
func (_m *RoleStore) ChannelRolesUnderTeamRole(roleName string) ([]*model.Role, error) {
ret := _m.Called(roleName)
if len(ret) == 0 {
panic("no return value specified for ChannelRolesUnderTeamRole")
}
var r0 []*model.Role
var r1 error
if rf, ok := ret.Get(0).(func(string) ([]*model.Role, error)); ok {
return rf(roleName)
}
if rf, ok := ret.Get(0).(func(string) []*model.Role); ok {
r0 = rf(roleName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Role)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(roleName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: roleID
func (_m *RoleStore) Delete(roleID string) (*model.Role, error) {
ret := _m.Called(roleID)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 *model.Role
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.Role, error)); ok {
return rf(roleID)
}
if rf, ok := ret.Get(0).(func(string) *model.Role); ok {
r0 = rf(roleID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Role)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(roleID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Get provides a mock function with given fields: roleID
func (_m *RoleStore) Get(roleID string) (*model.Role, error) {
ret := _m.Called(roleID)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 *model.Role
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.Role, error)); ok {
return rf(roleID)
}
if rf, ok := ret.Get(0).(func(string) *model.Role); ok {
r0 = rf(roleID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Role)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(roleID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetAll provides a mock function with no fields
func (_m *RoleStore) GetAll() ([]*model.Role, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetAll")
}
var r0 []*model.Role
var r1 error
if rf, ok := ret.Get(0).(func() ([]*model.Role, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []*model.Role); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Role)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetByName provides a mock function with given fields: ctx, name
func (_m *RoleStore) GetByName(ctx context.Context, name string) (*model.Role, error) {
ret := _m.Called(ctx, name)
if len(ret) == 0 {
panic("no return value specified for GetByName")
}
var r0 *model.Role
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (*model.Role, error)); ok {
return rf(ctx, name)
}
if rf, ok := ret.Get(0).(func(context.Context, string) *model.Role); ok {
r0 = rf(ctx, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Role)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetByNames provides a mock function with given fields: names
func (_m *RoleStore) GetByNames(names []string) ([]*model.Role, error) {
ret := _m.Called(names)
if len(ret) == 0 {
panic("no return value specified for GetByNames")
}
var r0 []*model.Role
var r1 error
if rf, ok := ret.Get(0).(func([]string) ([]*model.Role, error)); ok {
return rf(names)
}
if rf, ok := ret.Get(0).(func([]string) []*model.Role); ok {
r0 = rf(names)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Role)
}
}
if rf, ok := ret.Get(1).(func([]string) error); ok {
r1 = rf(names)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PermanentDeleteAll provides a mock function with no fields
func (_m *RoleStore) PermanentDeleteAll() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for PermanentDeleteAll")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Save provides a mock function with given fields: role
func (_m *RoleStore) Save(role *model.Role) (*model.Role, error) {
ret := _m.Called(role)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 *model.Role
var r1 error
if rf, ok := ret.Get(0).(func(*model.Role) (*model.Role, error)); ok {
return rf(role)
}
if rf, ok := ret.Get(0).(func(*model.Role) *model.Role); ok {
r0 = rf(role)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Role)
}
}
if rf, ok := ret.Get(1).(func(*model.Role) error); ok {
r1 = rf(role)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SavePreservingUnknownPermissions provides a mock function with given fields: role
func (_m *RoleStore) SavePreservingUnknownPermissions(role *model.Role) (*model.Role, error) {
ret := _m.Called(role)
if len(ret) == 0 {
panic("no return value specified for SavePreservingUnknownPermissions")
}
var r0 *model.Role
var r1 error
if rf, ok := ret.Get(0).(func(*model.Role) (*model.Role, error)); ok {
return rf(role)
}
if rf, ok := ret.Get(0).(func(*model.Role) *model.Role); ok {
r0 = rf(role)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Role)
}
}
if rf, ok := ret.Get(1).(func(*model.Role) error); ok {
r1 = rf(role)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewRoleStore creates a new instance of RoleStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRoleStore(t interface {
mock.TestingT
Cleanup(func())
}) *RoleStore {
mock := &RoleStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}