[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 удалений

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

@@ -263,6 +263,8 @@ channels/db/migrations/mysql/000132_create_index_pagination_on_property_fields.d
channels/db/migrations/mysql/000132_create_index_pagination_on_property_fields.up.sql
channels/db/migrations/mysql/000133_add_channel_banner_fields.down.sql
channels/db/migrations/mysql/000133_add_channel_banner_fields.up.sql
channels/db/migrations/mysql/000134_create_access_control_policies.down.sql
channels/db/migrations/mysql/000134_create_access_control_policies.up.sql
channels/db/migrations/postgres/000001_create_teams.down.sql
channels/db/migrations/postgres/000001_create_teams.up.sql
channels/db/migrations/postgres/000002_create_team_members.down.sql
@@ -527,3 +529,5 @@ channels/db/migrations/postgres/000132_create_index_pagination_on_property_field
channels/db/migrations/postgres/000132_create_index_pagination_on_property_fields.up.sql
channels/db/migrations/postgres/000133_add_channel_banner_fields.down.sql
channels/db/migrations/postgres/000133_add_channel_banner_fields.up.sql
channels/db/migrations/postgres/000134_create_access_control_policies.down.sql
channels/db/migrations/postgres/000134_create_access_control_policies.up.sql

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

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS AccessControlPolicyHistory;
DROP TABLE IF EXISTS AccessControlPolicies;

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

@@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS AccessControlPolicies (
ID varchar(26) PRIMARY KEY,
Name varchar(128) NOT NULL,
Type varchar(128) NOT NULL,
Active bool NOT NULL,
CreateAt bigint(20) NOT NULL,
Revision int NOT NULL,
Version varchar(8) NOT NULL,
Data json,
Props json
);
CREATE TABLE IF NOT EXISTS AccessControlPolicyHistory (
ID varchar(26) NOT NULL,
Name varchar(128) NOT NULL,
Type varchar(128) NOT NULL,
CreateAt bigint(20) NOT NULL,
Revision int NOT NULL,
Version varchar(8) NOT NULL,
Data json,
Props json,
PRIMARY KEY (ID, Revision)
);

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

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS AccessControlPolicyHistory;
DROP TABLE IF EXISTS AccessControlPolicies;

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

@@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS AccessControlPolicies (
ID varchar(26) PRIMARY KEY,
Name varchar(128) NOT NULL,
Type varchar(128) NOT NULL,
Active bool NOT NULL,
CreateAt bigint NOT NULL,
Revision int NOT NULL,
Version varchar(8) NOT NULL,
Data jsonb,
Props jsonb
);
CREATE TABLE IF NOT EXISTS AccessControlPolicyHistory (
ID varchar(26) NOT NULL,
Name varchar(128) NOT NULL,
Type varchar(128) NOT NULL,
CreateAt bigint NOT NULL,
Revision int NOT NULL,
Version varchar(8) NOT NULL,
Data jsonb,
Props jsonb,
PRIMARY KEY (ID, Revision)
);

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

@@ -250,7 +250,7 @@ func generateLayer(name, templateFile string) ([]byte, error) {
paramsWithType := []string{}
for _, param := range params {
switch param.Type {
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts":
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts", "GetPolicyOptions":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type))
case "*UserGetByIdsOpts", "*SidebarCategorySearchOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*")))
@@ -264,7 +264,7 @@ func generateLayer(name, templateFile string) ([]byte, error) {
paramsWithType := []string{}
for _, param := range params {
switch param.Type {
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts":
case "ChannelSearchOpts", "UserGetByIdsOpts", "ThreadMembershipOpts", "GetPolicyOptions":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s store.%s", param.Name, param.Type))
case "*UserGetByIdsOpts", "*SidebarCategorySearchOpts":
paramsWithType = append(paramsWithType, fmt.Sprintf("%s *store.%s", param.Name, strings.TrimPrefix(param.Type, "*")))

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

@@ -23,6 +23,7 @@ const mySQLDeadlockCode = uint16(1213)
type RetryLayer struct {
store.Store
AccessControlPolicyStore store.AccessControlPolicyStore
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
@@ -74,6 +75,10 @@ type RetryLayer struct {
WebhookStore store.WebhookStore
}
func (s *RetryLayer) AccessControlPolicy() store.AccessControlPolicyStore {
return s.AccessControlPolicyStore
}
func (s *RetryLayer) Audit() store.AuditStore {
return s.AuditStore
}
@@ -270,6 +275,11 @@ func (s *RetryLayer) Webhook() store.WebhookStore {
return s.WebhookStore
}
type RetryLayerAccessControlPolicyStore struct {
store.AccessControlPolicyStore
Root *RetryLayer
}
type RetryLayerAuditStore struct {
store.AuditStore
Root *RetryLayer
@@ -531,6 +541,111 @@ func isRepeatableError(err error) bool {
return false
}
func (s *RetryLayerAccessControlPolicyStore) Delete(c request.CTX, id string) error {
tries := 0
for {
err := s.AccessControlPolicyStore.Delete(c, id)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerAccessControlPolicyStore) Get(c request.CTX, id string) (*model.AccessControlPolicy, error) {
tries := 0
for {
result, err := s.AccessControlPolicyStore.Get(c, id)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerAccessControlPolicyStore) GetAll(rctxc request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
tries := 0
for {
result, err := s.AccessControlPolicyStore.GetAll(rctxc, opts)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
tries := 0
for {
result, err := s.AccessControlPolicyStore.Save(c, policy)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
tries := 0
for {
result, err := s.AccessControlPolicyStore.SetActiveStatus(c, id, active)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerAuditStore) Get(userID string, offset int, limit int) (model.Audits, error) {
tries := 0
@@ -16334,6 +16449,7 @@ func New(childStore store.Store) *RetryLayer {
Store: childStore,
}
newStore.AccessControlPolicyStore = &RetryLayerAccessControlPolicyStore{AccessControlPolicyStore: childStore.AccessControlPolicy(), Root: &newStore}
newStore.AuditStore = &RetryLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
newStore.BotStore = &RetryLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
newStore.ChannelStore = &RetryLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}

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

@@ -66,6 +66,7 @@ func genStore() *mocks.Store {
mock.On("PropertyField").Return(&mocks.PropertyFieldStore{})
mock.On("PropertyGroup").Return(&mocks.PropertyGroupStore{})
mock.On("PropertyValue").Return(&mocks.PropertyValueStore{})
mock.On("AccessControlPolicy").Return(&mocks.AccessControlPolicyStore{})
return mock
}

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

@@ -0,0 +1,421 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"encoding/json"
"fmt"
"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/mattermost/mattermost/server/v8/einterfaces"
"github.com/pkg/errors"
sq "github.com/mattermost/squirrel"
)
// Usually rules are how we define the policy, hence the versioning. For v0.1, we also
// have the imports field which is used to link with the parent policy.
type accessControlPolicyV0_1 struct {
Imports []string `json:"imports"`
Rules []model.AccessControlPolicyRule `json:"rules"`
}
// These are the fields that meant to be unchanged with the policy versions.
type storeAccessControlPolicy struct {
ID string
Name string
Type string
Active bool
CreateAt int64
Revision int
Version string
Data []byte
Props []byte
}
// This needs to be updated with the new version of the policy.
// with the new name as this only supports v0.1
func (s *storeAccessControlPolicy) toModel() (*model.AccessControlPolicy, error) {
policy := &model.AccessControlPolicy{
ID: s.ID,
Name: s.Name,
Type: s.Type,
Active: s.Active,
CreateAt: s.CreateAt,
Revision: s.Revision,
Version: s.Version,
}
var p accessControlPolicyV0_1
if err := json.Unmarshal(s.Data, &p); err != nil {
return nil, err
}
policy.Imports = p.Imports
policy.Rules = p.Rules
if err := json.Unmarshal(s.Props, &policy.Props); err != nil {
return nil, err
}
return policy, nil
}
func fromModel(policy *model.AccessControlPolicy) (*storeAccessControlPolicy, error) {
data, err := json.Marshal(&accessControlPolicyV0_1{
Imports: policy.Imports,
Rules: policy.Rules,
})
if err != nil {
return nil, err
}
props, err := json.Marshal(policy.Props)
if err != nil {
return nil, err
}
return &storeAccessControlPolicy{
ID: policy.ID,
Name: policy.Name,
Type: policy.Type,
Active: policy.Active,
CreateAt: policy.CreateAt,
Revision: policy.Revision,
Version: policy.Version,
Data: data,
Props: props,
}, nil
}
func accessControlPolicySliceColumns(prefix ...string) []string {
var p string
if len(prefix) == 1 {
p = prefix[0] + "."
} else if len(prefix) > 1 {
panic("cannot accept multiple prefixes")
}
return []string{
p + "ID",
p + "Name",
p + "Type",
p + "Active",
p + "CreateAt",
p + "Revision",
p + "Version",
p + "Data",
p + "Props",
}
}
func accessControlPolicyHistorySliceColumns(prefix ...string) []string {
var p string
if len(prefix) == 1 {
p = prefix[0] + "."
} else if len(prefix) > 1 {
panic("cannot accept multiple prefixes")
}
return []string{
p + "ID",
p + "Name",
p + "Type",
p + "CreateAt",
p + "Revision",
p + "Version",
p + "Data",
p + "Props",
}
}
type SqlAccessControlPolicyStore struct {
*SqlStore
metrics einterfaces.MetricsInterface
selectQueryBuilder sq.SelectBuilder
}
// newSqlAccessControlPolicyStore creates an instance of AccessControlPolicyStorea.
func newSqlAccessControlPolicyStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.AccessControlPolicyStore {
s := &SqlAccessControlPolicyStore{
SqlStore: sqlStore,
metrics: metrics,
}
s.selectQueryBuilder = s.getQueryBuilder().Select(accessControlPolicySliceColumns()...).From("AccessControlPolicies")
return s
}
func preSaveAccessControlPolicy(policy, existingPolicy *model.AccessControlPolicy) {
// since policies are immutable, we need to create a new revision
// also if it's going to be saved, eventually it will be the new one
// we overwrite createAt to make sure it gets the correct timestamp before saving
// if there is no existing policy, we set the revision to 1
policy.CreateAt = model.GetMillis()
if existingPolicy != nil {
policy.Revision = existingPolicy.Revision + 1
} else {
policy.Revision = 1
}
}
func (s *SqlAccessControlPolicyStore) Save(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
if err := policy.IsValid(); err != nil {
return nil, err
}
tx, err := s.GetMaster().Beginx()
if err != nil {
return nil, errors.Wrap(err, "failed to start transaction")
}
defer finalizeTransactionX(tx, &err)
existingPolicy, err := s.getT(rctx, tx, policy.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, errors.Wrapf(err, "failed to fetch policy with id=%s", policy.ID)
}
if existingPolicy != nil {
// move existing policy to history
tmp, err2 := fromModel(existingPolicy)
if err2 != nil {
return nil, errors.Wrapf(err2, "failed to parse policy with id=%s", policy.ID)
}
data := tmp.Data
props := tmp.Props
if s.IsBinaryParamEnabled() {
data = AppendBinaryFlag(data)
props = AppendBinaryFlag(props)
}
query := s.getQueryBuilder().
Insert("AccessControlPolicyHistory").
Columns(accessControlPolicyHistorySliceColumns()...).
Values(tmp.ID, tmp.Name, tmp.Type, tmp.CreateAt, tmp.Revision, tmp.Version, data, props)
_, err = tx.ExecBuilder(query)
if err != nil {
return nil, errors.Wrapf(err, "failed to save policy with id=%s to history", policy.ID)
}
err = s.deleteT(rctx, tx, existingPolicy.ID)
if err != nil {
return nil, errors.Wrapf(err, "failed to delete policy with id=%s", policy.ID)
}
}
preSaveAccessControlPolicy(policy, existingPolicy)
storePolicy, err := fromModel(policy)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse policy with Id=%s", policy.ID)
}
data := storePolicy.Data
props := storePolicy.Props
if s.IsBinaryParamEnabled() {
data = AppendBinaryFlag(data)
props = AppendBinaryFlag(props)
}
query := s.getQueryBuilder().
Insert("AccessControlPolicies").
Columns(accessControlPolicySliceColumns()...).
Values(storePolicy.ID, storePolicy.Name, storePolicy.Type, storePolicy.Active, storePolicy.CreateAt, storePolicy.Revision, storePolicy.Version, data, props)
_, err = tx.ExecBuilder(query)
if err != nil {
return nil, errors.Wrapf(err, "failed to save policy with id=%s", policy.ID)
}
cp, err := storePolicy.toModel()
if err != nil {
return nil, errors.Wrapf(err, "failed to parse policy with id=%s", policy.ID)
}
if err = tx.Commit(); err != nil {
return nil, errors.Wrap(err, "commit_transaction")
}
return cp, nil
}
func (s *SqlAccessControlPolicyStore) Delete(rctx request.CTX, id string) error {
tx, err := s.GetMaster().Beginx()
if err != nil {
return errors.Wrap(err, "failed to start transaction")
}
defer finalizeTransactionX(tx, &err)
existingPolicy, err := s.getT(rctx, tx, id)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return errors.Wrapf(err, "failed to fetch policy with id=%s", id)
}
if existingPolicy != nil {
tmp, err2 := fromModel(existingPolicy)
if err2 != nil {
return errors.Wrapf(err2, "failed to parse policy with id=%s", id)
}
data := tmp.Data
props := tmp.Props
if s.IsBinaryParamEnabled() {
data = AppendBinaryFlag(data)
props = AppendBinaryFlag(props)
}
query := s.getQueryBuilder().
Insert("AccessControlPolicyHistory").
Columns(accessControlPolicyHistorySliceColumns()...).
Values(tmp.ID, tmp.Name, tmp.Type, tmp.CreateAt, tmp.Revision, tmp.Version, data, props)
_, err = tx.ExecBuilder(query)
if err != nil {
return errors.Wrapf(err, "failed to save policy with id=%s to history", id)
}
err = s.deleteT(rctx, tx, existingPolicy.ID)
if err != nil {
return errors.Wrapf(err, "failed to delete policy with id=%s", id)
}
}
if err = tx.Commit(); err != nil {
return errors.Wrap(err, "commit_transaction")
}
return nil
}
func (s *SqlAccessControlPolicyStore) deleteT(_ request.CTX, tx *sqlxTxWrapper, id string) error {
query := s.getQueryBuilder().Delete("AccessControlPolicies").Where(sq.Eq{"ID": id})
_, err := tx.ExecBuilder(query)
if err != nil {
return errors.Wrapf(err, "failed to delete policy with id=%s", id)
}
return nil
}
func (s *SqlAccessControlPolicyStore) SetActiveStatus(rctx request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
tx, err := s.GetMaster().Beginx()
if err != nil {
return nil, errors.Wrap(err, "failed to start transaction")
}
defer finalizeTransactionX(tx, &err)
existingPolicy, err := s.getT(rctx, tx, id)
if err != nil {
return nil, errors.Wrapf(err, "failed to fetch policy with id=%s", id)
} else if errors.Is(err, sql.ErrNoRows) {
return nil, store.NewErrNotFound("AccessControlPolicy", id)
}
// also make sure if the policy is valid before updating active status
// just in case
existingPolicy.Active = active
if appErr := existingPolicy.IsValid(); err != nil {
return nil, appErr
}
query, args, err := s.getQueryBuilder().Update("AccessControlPolicies").Set("Active", active).Where(sq.Eq{"ID": id}).ToSql()
if err != nil {
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
}
_, err = tx.Query(query, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to update policy with id=%s", id)
}
if err = tx.Commit(); err != nil {
return nil, errors.Wrap(err, "commit_transaction")
}
return existingPolicy, nil
}
func (s *SqlAccessControlPolicyStore) Get(_ request.CTX, id string) (*model.AccessControlPolicy, error) {
p := storeAccessControlPolicy{}
query := s.selectQueryBuilder.Where(sq.Eq{"ID": id})
err := s.GetReplica().GetBuilder(&p, query)
if err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("AccessControlPolicy", id)
}
return nil, errors.Wrapf(err, "failed to find policy with id=%s", id)
}
policy, err := p.toModel()
if err != nil {
return nil, errors.Wrapf(err, "failed to parse policy with id=%s", id)
}
return policy, nil
}
func (s *SqlAccessControlPolicyStore) getT(_ request.CTX, tx *sqlxTxWrapper, id string) (*model.AccessControlPolicy, error) {
query := s.getQueryBuilder().
Select(accessControlPolicySliceColumns()...).
From("AccessControlPolicies").
Where(
sq.Eq{"ID": id},
)
sql, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
}
var storePolicy storeAccessControlPolicy
err = tx.Get(&storePolicy, sql, args...)
if err != nil {
return nil, err
}
policy, err := storePolicy.toModel()
if err != nil {
return nil, errors.Wrapf(err, "failed to parse policy with id=%s", id)
}
return policy, nil
}
func (s *SqlAccessControlPolicyStore) GetAll(_ request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
p := []storeAccessControlPolicy{}
query := s.selectQueryBuilder
if opts.ParentID != "" {
if s.DriverName() == model.DatabaseDriverPostgres {
query = query.Where(sq.Expr("Data->'imports' @> ?", fmt.Sprintf("%q", opts.ParentID)))
} else {
query = query.Where(sq.Expr("JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?)", fmt.Sprintf("%q", opts.ParentID)))
}
}
if opts.Type != "" {
query = query.Where(sq.Eq{"Type": opts.Type})
}
err := s.GetReplica().SelectBuilder(&p, query)
if err != nil {
return nil, errors.Wrapf(err, "failed to find policies with opts={\"parentID\"=%q, \"resourceType\"=%q", opts.ParentID, opts.Type)
}
policies := make([]*model.AccessControlPolicy, len(p))
for i := range p {
policies[i], err = p[i].toModel()
if err != nil {
return nil, errors.Wrapf(err, "failed to parse policy with id=%s", p[i].ID)
}
}
return policies, nil
}

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

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
)
func TestAccessControlPolicyStore(t *testing.T) {
StoreTestWithSqlStore(t, storetest.TestAccessControlPolicyStore)
}

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

@@ -118,6 +118,7 @@ type SqlStoreStores struct {
propertyGroup store.PropertyGroupStore
propertyField store.PropertyFieldStore
propertyValue store.PropertyValueStore
accessControlPolicy store.AccessControlPolicyStore
}
type SqlStore struct {
@@ -263,6 +264,7 @@ func New(settings model.SqlSettings, logger mlog.LoggerIFace, metrics einterface
store.stores.propertyGroup = newPropertyGroupStore(store)
store.stores.propertyField = newPropertyFieldStore(store)
store.stores.propertyValue = newPropertyValueStore(store)
store.stores.accessControlPolicy = newSqlAccessControlPolicyStore(store, metrics)
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
@@ -1079,6 +1081,10 @@ func (ss *SqlStore) PropertyValue() store.PropertyValueStore {
return ss.stores.propertyValue
}
func (ss *SqlStore) AccessControlPolicy() store.AccessControlPolicyStore {
return ss.stores.accessControlPolicy
}
func (ss *SqlStore) DropAllTables() {
if ss.DriverName() == model.DatabaseDriverPostgres {
ss.masterX.Exec(`DO

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

@@ -95,6 +95,7 @@ type Store interface {
PropertyGroup() PropertyGroupStore
PropertyField() PropertyFieldStore
PropertyValue() PropertyValueStore
AccessControlPolicy() AccessControlPolicyStore
}
type RetentionPolicyStore interface {
@@ -1107,6 +1108,14 @@ type PropertyValueStore interface {
DeleteForField(id string) error
}
type AccessControlPolicyStore interface {
Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error)
Delete(c request.CTX, id string) error
SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error)
Get(c request.CTX, id string) (*model.AccessControlPolicy, error)
GetAll(rctxc request.CTX, opts GetPolicyOptions) ([]*model.AccessControlPolicy, error)
}
// ChannelSearchOpts contains options for searching channels.
//
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
@@ -1199,3 +1208,11 @@ type ThreadMembershipImportData struct {
// UnreadMentions is the number of unread mentions to set the UnreadMentions field to.
UnreadMentions int64
}
// GetPolicyOptions contains options for filtering policy records.
type GetPolicyOptions struct {
// ParentID will filter policy records where they inherit parent with PolicyID.
ParentID string
// Type will filter policy records where they are associated with the Type.
Type string
}

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

@@ -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,
)
}

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

@@ -19,6 +19,7 @@ import (
type TimerLayer struct {
store.Store
Metrics einterfaces.MetricsInterface
AccessControlPolicyStore store.AccessControlPolicyStore
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
@@ -70,6 +71,10 @@ type TimerLayer struct {
WebhookStore store.WebhookStore
}
func (s *TimerLayer) AccessControlPolicy() store.AccessControlPolicyStore {
return s.AccessControlPolicyStore
}
func (s *TimerLayer) Audit() store.AuditStore {
return s.AuditStore
}
@@ -266,6 +271,11 @@ func (s *TimerLayer) Webhook() store.WebhookStore {
return s.WebhookStore
}
type TimerLayerAccessControlPolicyStore struct {
store.AccessControlPolicyStore
Root *TimerLayer
}
type TimerLayerAuditStore struct {
store.AuditStore
Root *TimerLayer
@@ -511,6 +521,86 @@ type TimerLayerWebhookStore struct {
Root *TimerLayer
}
func (s *TimerLayerAccessControlPolicyStore) Delete(c request.CTX, id string) error {
start := time.Now()
err := s.AccessControlPolicyStore.Delete(c, id)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.Delete", success, elapsed)
}
return err
}
func (s *TimerLayerAccessControlPolicyStore) Get(c request.CTX, id string) (*model.AccessControlPolicy, error) {
start := time.Now()
result, err := s.AccessControlPolicyStore.Get(c, id)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.Get", success, elapsed)
}
return result, err
}
func (s *TimerLayerAccessControlPolicyStore) GetAll(rctxc request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
start := time.Now()
result, err := s.AccessControlPolicyStore.GetAll(rctxc, opts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.GetAll", success, elapsed)
}
return result, err
}
func (s *TimerLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, error) {
start := time.Now()
result, err := s.AccessControlPolicyStore.Save(c, policy)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.Save", success, elapsed)
}
return result, err
}
func (s *TimerLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
start := time.Now()
result, err := s.AccessControlPolicyStore.SetActiveStatus(c, id, active)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("AccessControlPolicyStore.SetActiveStatus", success, elapsed)
}
return result, err
}
func (s *TimerLayerAuditStore) Get(userID string, offset int, limit int) (model.Audits, error) {
start := time.Now()
@@ -12882,6 +12972,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
Metrics: metrics,
}
newStore.AccessControlPolicyStore = &TimerLayerAccessControlPolicyStore{AccessControlPolicyStore: childStore.AccessControlPolicy(), Root: &newStore}
newStore.AuditStore = &TimerLayerAuditStore{AuditStore: childStore.Audit(), Root: &newStore}
newStore.BotStore = &TimerLayerBotStore{BotStore: childStore.Bot(), Root: &newStore}
newStore.ChannelStore = &TimerLayerChannelStore{ChannelStore: childStore.Channel(), Root: &newStore}

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

@@ -44,7 +44,7 @@ require (
github.com/mattermost/gosaml2 v0.8.0
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956
github.com/mattermost/logr/v2 v2.0.21
github.com/mattermost/mattermost/server/public v0.1.9
github.com/mattermost/mattermost/server/public v0.1.11
github.com/mattermost/morph v1.1.0
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
github.com/mattermost/squirrel v0.4.0
@@ -72,11 +72,11 @@ require (
github.com/wiggin77/merror v1.0.5
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.32.0
golang.org/x/crypto v0.35.0
golang.org/x/image v0.23.0
golang.org/x/net v0.34.0
golang.org/x/net v0.36.0
golang.org/x/sync v0.12.0
golang.org/x/term v0.28.0
golang.org/x/term v0.29.0
gopkg.in/mail.v2 v2.3.1
gopkg.in/yaml.v3 v3.0.1
)
@@ -221,7 +221,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect

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

@@ -378,8 +378,8 @@ github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=
github.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4=
github.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc=
github.com/mattermost/mattermost/server/public v0.1.9 h1:l/OKPRVuFeqL0yqRVC/JpveG5sLNKcT9llxqMkO9e+s=
github.com/mattermost/mattermost/server/public v0.1.9/go.mod h1:SkTKbMul91Rq0v2dIxe8mqzUOY+3KwlwwLmAlxDfGCk=
github.com/mattermost/mattermost/server/public v0.1.11 h1:qxn36BE1rk5lTiMrHCVjdEdeUcbkOy/fxFvlRVyK0rI=
github.com/mattermost/mattermost/server/public v0.1.11/go.mod h1:h/rage94cF+ZWqDdVRaROXFUEPVyO3Wbq8tfKRPRL0s=
github.com/mattermost/morph v1.1.0 h1:Q9vrJbeM3s2jfweGheq12EFIzdNp9a/6IovcbvOQ6Cw=
github.com/mattermost/morph v1.1.0/go.mod h1:gD+EaqX2UMyyuzmF4PFh4r33XneQ8Nzi+0E8nXjMa3A=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
@@ -696,8 +696,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
@@ -740,8 +740,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -800,8 +800,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -812,8 +812,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=