[MM-63428] add access control policy store (#30597)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3eb854c58d
Коммит
10b1f4c5ac
@@ -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
|
||||
}
|
||||
|
||||
|
||||
421
server/channels/store/sqlstore/access_control_policy_store.go
Обычный файл
421
server/channels/store/sqlstore/access_control_policy_store.go
Обычный файл
@@ -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
|
||||
}
|
||||
|
||||
329
server/channels/store/storetest/access_control_policy_store.go
Обычный файл
329
server/channels/store/storetest/access_control_policy_store.go
Обычный файл
@@ -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)
|
||||
})
|
||||
}
|
||||
170
server/channels/store/storetest/mocks/AccessControlPolicyStore.go
Обычный файл
170
server/channels/store/storetest/mocks/AccessControlPolicyStore.go
Обычный файл
@@ -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=
|
||||
|
||||
Ссылка в новой задаче
Block a user