[MM-63428] add access control policy store (#30597)

Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2025-04-02 13:39:28 +02:00
коммит произвёл GitHub
родитель 3eb854c58d
Коммит 10b1f4c5ac
19 изменённых файлов: 1261 добавлений и 17 удалений

Просмотреть файл

@@ -0,0 +1,329 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/require"
)
func TestAccessControlPolicyStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("Save", func(t *testing.T) { testAccessControlPolicyStoreSaveAndGet(t, rctx, ss) })
t.Run("Delete", func(t *testing.T) { testAccessControlPolicyStoreDelete(t, rctx, ss) })
t.Run("SetActive", func(t *testing.T) { testAccessControlPolicyStoreSetActive(t, rctx, ss) })
t.Run("GetAll", func(t *testing.T) { testAccessControlPolicyStoreGetAll(t, rctx, ss) })
}
func testAccessControlPolicyStoreSaveAndGet(t *testing.T, rctx request.CTX, ss store.Store) {
t.Run("Save parent policy", func(t *testing.T) {
policy := &model.AccessControlPolicy{
ID: model.NewId(),
Name: "Name",
Type: model.AccessControlPolicyTypeParent,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "user.properties.program == \"engineering\"",
},
},
}
policy, err := ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
t.Cleanup(func() {
err := ss.AccessControlPolicy().Delete(rctx, policy.ID)
require.NoError(t, err)
})
})
t.Run("Save resource policy", func(t *testing.T) {
parent1 := model.NewId()
policy := &model.AccessControlPolicy{
ID: model.NewId(),
Name: "Name",
Type: model.AccessControlPolicyTypeChannel,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{parent1},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "policies." + parent1 + " == true",
},
},
}
policy, err := ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
t.Cleanup(func() {
err := ss.AccessControlPolicy().Delete(rctx, policy.ID)
require.NoError(t, err)
})
})
t.Run("update resource policy", func(t *testing.T) {
policyID := model.NewId()
policy := &model.AccessControlPolicy{
ID: policyID,
Name: "Name",
Type: model.AccessControlPolicyTypeChannel,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "user.properties.program == \"engineering\"",
},
},
}
policy, err := ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
policy.Rules = []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "user.properties.program == \"engineering\" || user.properties.department == \"engineering\"",
},
}
policy, err = ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
policy, err = ss.AccessControlPolicy().Get(rctx, policyID)
require.NoError(t, err)
require.NotNil(t, policy)
require.Equal(t, 2, policy.Revision)
t.Cleanup(func() {
err := ss.AccessControlPolicy().Delete(rctx, policy.ID)
require.NoError(t, err)
})
})
t.Run("Get non-existent policy", func(t *testing.T) {
id := model.NewId()
policy, err := ss.AccessControlPolicy().Get(rctx, id)
require.EqualError(t, err, store.NewErrNotFound("AccessControlPolicy", id).Error())
require.Nil(t, policy)
})
}
func testAccessControlPolicyStoreDelete(t *testing.T, rctx request.CTX, ss store.Store) {
t.Run("Delete parent policy", func(t *testing.T) {
policy := &model.AccessControlPolicy{
ID: model.NewId(),
Name: "Name",
Type: model.AccessControlPolicyTypeParent,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "user.properties.program == \"engineering\"",
},
},
}
policy, err := ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
err = ss.AccessControlPolicy().Delete(rctx, policy.ID)
require.NoError(t, err)
id := policy.ID
policy, err = ss.AccessControlPolicy().Get(rctx, policy.ID)
require.EqualError(t, err, store.NewErrNotFound("AccessControlPolicy", id).Error())
require.Nil(t, policy)
})
t.Run("Delete resource policy", func(t *testing.T) {
parent1 := model.NewId()
policy := &model.AccessControlPolicy{
ID: model.NewId(),
Name: "Name",
Type: model.AccessControlPolicyTypeChannel,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{parent1},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "policies." + parent1 + " == true",
},
},
}
policy, err := ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
err = ss.AccessControlPolicy().Delete(rctx, policy.ID)
require.NoError(t, err)
id := policy.ID
policy, err = ss.AccessControlPolicy().Get(rctx, policy.ID)
require.EqualError(t, err, store.NewErrNotFound("AccessControlPolicy", id).Error())
require.Nil(t, policy)
})
t.Run("Delete non-existent policy", func(t *testing.T) {
err := ss.AccessControlPolicy().Delete(rctx, model.NewId())
require.NoError(t, err)
})
}
func testAccessControlPolicyStoreSetActive(t *testing.T, rctx request.CTX, ss store.Store) {
t.Run("Save policy", func(t *testing.T) {
id := model.NewId()
policy := &model.AccessControlPolicy{
ID: id,
Name: "Name",
Type: model.AccessControlPolicyTypeChannel,
Active: false,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "user.properties.program == \"engineering\"",
},
},
}
policy, err := ss.AccessControlPolicy().Save(rctx, policy)
require.NoError(t, err)
require.NotNil(t, policy)
t.Cleanup(func() {
err = ss.AccessControlPolicy().Delete(rctx, id)
require.NoError(t, err)
})
policy, err = ss.AccessControlPolicy().Get(rctx, policy.ID)
require.NoError(t, err)
require.NotNil(t, policy)
require.False(t, policy.Active)
policy, err = ss.AccessControlPolicy().SetActiveStatus(rctx, policy.ID, true)
require.NoError(t, err)
require.NotNil(t, policy)
require.True(t, policy.Active)
policy, err = ss.AccessControlPolicy().Get(rctx, policy.ID)
require.NoError(t, err)
require.NotNil(t, policy)
require.True(t, policy.Active)
})
}
func testAccessControlPolicyStoreGetAll(t *testing.T, rctx request.CTX, ss store.Store) {
id := model.NewId()
parentPolicy := &model.AccessControlPolicy{
ID: id,
Name: "Name",
Type: model.AccessControlPolicyTypeParent,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "user.properties.program == \"engineering\"",
},
},
}
t.Cleanup(func() {
err := ss.AccessControlPolicy().Delete(rctx, id)
require.NoError(t, err)
})
parentPolicy, err := ss.AccessControlPolicy().Save(rctx, parentPolicy)
require.NoError(t, err)
require.NotNil(t, parentPolicy)
id2 := model.NewId()
resourcePolicy := &model.AccessControlPolicy{
ID: id2,
Name: "Name",
Type: model.AccessControlPolicyTypeChannel,
Active: true,
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Imports: []string{parentPolicy.ID},
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"action"},
Expression: "policies." + parentPolicy.ID + " == true",
},
},
}
t.Cleanup(func() {
err = ss.AccessControlPolicy().Delete(rctx, id2)
require.NoError(t, err)
})
resourcePolicy, err = ss.AccessControlPolicy().Save(rctx, resourcePolicy)
require.NoError(t, err)
require.NotNil(t, resourcePolicy)
t.Run("GetAll", func(t *testing.T) {
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{})
require.NoError(t, err)
require.NotNil(t, policies)
require.Len(t, policies, 2)
})
t.Run("GetAll by type", func(t *testing.T) {
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{Type: model.AccessControlPolicyTypeParent})
require.NoError(t, err)
require.NotNil(t, policies)
require.Len(t, policies, 1)
require.Equal(t, parentPolicy.ID, policies[0].ID)
policies, err = ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{Type: model.AccessControlPolicyTypeChannel})
require.NoError(t, err)
require.NotNil(t, policies)
require.Len(t, policies, 1)
require.Equal(t, resourcePolicy.ID, policies[0].ID)
})
t.Run("GetAll by parent", func(t *testing.T) {
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{ParentID: parentPolicy.ID})
require.NoError(t, err)
require.NotNil(t, policies)
require.Len(t, policies, 1)
require.Equal(t, resourcePolicy.ID, policies[0].ID)
policies, err = ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{ParentID: model.NewId()})
require.NoError(t, err)
require.NotNil(t, policies)
require.Len(t, policies, 0)
})
}

Просмотреть файл

@@ -0,0 +1,170 @@
// Code generated by mockery v2.42.2. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
request "github.com/mattermost/mattermost/server/public/shared/request"
mock "github.com/stretchr/testify/mock"
store "github.com/mattermost/mattermost/server/v8/channels/store"
)
// AccessControlPolicyStore is an autogenerated mock type for the AccessControlPolicyStore type
type AccessControlPolicyStore struct {
mock.Mock
}
// Delete provides a mock function with given fields: c, id
func (_m *AccessControlPolicyStore) Delete(c request.CTX, id string) error {
ret := _m.Called(c, id)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 error
if rf, ok := ret.Get(0).(func(request.CTX, string) error); ok {
r0 = rf(c, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: c, id
func (_m *AccessControlPolicyStore) Get(c request.CTX, id string) (*model.AccessControlPolicy, error) {
ret := _m.Called(c, id)
if len(ret) == 0 {
panic("no return value specified for Get")
}
var r0 *model.AccessControlPolicy
var r1 error
if rf, ok := ret.Get(0).(func(request.CTX, string) (*model.AccessControlPolicy, error)); ok {
return rf(c, id)
}
if rf, ok := ret.Get(0).(func(request.CTX, string) *model.AccessControlPolicy); ok {
r0 = rf(c, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AccessControlPolicy)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, string) error); ok {
r1 = rf(c, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetAll provides a mock function with given fields: rctxc, opts
func (_m *AccessControlPolicyStore) GetAll(rctxc request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
ret := _m.Called(rctxc, opts)
if len(ret) == 0 {
panic("no return value specified for GetAll")
}
var r0 []*model.AccessControlPolicy
var r1 error
if rf, ok := ret.Get(0).(func(request.CTX, store.GetPolicyOptions) ([]*model.AccessControlPolicy, error)); ok {
return rf(rctxc, opts)
}
if rf, ok := ret.Get(0).(func(request.CTX, store.GetPolicyOptions) []*model.AccessControlPolicy); ok {
r0 = rf(rctxc, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.AccessControlPolicy)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, store.GetPolicyOptions) error); ok {
r1 = rf(rctxc, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Save provides a mock function with given fields: c, policy
func (_m *AccessControlPolicyStore) Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
ret := _m.Called(c, policy)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 *model.AccessControlPolicy
var r1 error
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) (*model.AccessControlPolicy, error)); ok {
return rf(c, policy)
}
if rf, ok := ret.Get(0).(func(request.CTX, *model.AccessControlPolicy) *model.AccessControlPolicy); ok {
r0 = rf(c, policy)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AccessControlPolicy)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, *model.AccessControlPolicy) error); ok {
r1 = rf(c, policy)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetActiveStatus provides a mock function with given fields: c, id, active
func (_m *AccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
ret := _m.Called(c, id, active)
if len(ret) == 0 {
panic("no return value specified for SetActiveStatus")
}
var r0 *model.AccessControlPolicy
var r1 error
if rf, ok := ret.Get(0).(func(request.CTX, string, bool) (*model.AccessControlPolicy, error)); ok {
return rf(c, id, active)
}
if rf, ok := ret.Get(0).(func(request.CTX, string, bool) *model.AccessControlPolicy); ok {
r0 = rf(c, id, active)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.AccessControlPolicy)
}
}
if rf, ok := ret.Get(1).(func(request.CTX, string, bool) error); ok {
r1 = rf(c, id, active)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewAccessControlPolicyStore creates a new instance of AccessControlPolicyStore. 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 NewAccessControlPolicyStore(t interface {
mock.TestingT
Cleanup(func())
}) *AccessControlPolicyStore {
mock := &AccessControlPolicyStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

Просмотреть файл

@@ -24,6 +24,26 @@ type Store struct {
mock.Mock
}
// AccessControlPolicy provides a mock function with given fields:
func (_m *Store) AccessControlPolicy() store.AccessControlPolicyStore {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for AccessControlPolicy")
}
var r0 store.AccessControlPolicyStore
if rf, ok := ret.Get(0).(func() store.AccessControlPolicyStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.AccessControlPolicyStore)
}
}
return r0
}
// Audit provides a mock function with given fields:
func (_m *Store) Audit() store.AuditStore {
ret := _m.Called()

Просмотреть файл

@@ -69,6 +69,7 @@ type Store struct {
PropertyGroupStore mocks.PropertyGroupStore
PropertyFieldStore mocks.PropertyFieldStore
PropertyValueStore mocks.PropertyValueStore
AccessControlPolicyStore mocks.AccessControlPolicyStore
}
func (s *Store) SetContext(context context.Context) { s.context = context }
@@ -154,6 +155,9 @@ func (s *Store) CheckIntegrity() <-chan model.IntegrityCheckResult {
}
func (s *Store) ReplicaLagAbs() error { return nil }
func (s *Store) ReplicaLagTime() error { return nil }
func (s *Store) AccessControlPolicy() store.AccessControlPolicyStore {
return &s.AccessControlPolicyStore
}
func (s *Store) AssertExpectations(t mock.TestingT) bool {
return mock.AssertExpectationsForObjects(t,
@@ -197,5 +201,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.DesktopTokensStore,
&s.ChannelBookmarkStore,
&s.ScheduledPostStore,
&s.AccessControlPolicyStore,
)
}