[MM-61756] Attribute Based Access Control - Phase 1 (#30785)
Attribute Based Access Control - Base * MM-63662 * MM-63919 * MM-63954 * MM-63955 * MM-63425 * MM-63426 * MM-63458 * MM-63459 * MM-63603 * MM-63845 * MM-64146 * MM-64199 * MM-64201 * MM-64233 * MM-64247 * MM-64268 --------- Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com> Co-authored-by: Pablo Andrés Vélez Vidal <pablovv2012@gmail.com> Co-authored-by: abhijit-singh <abhijitsingh0702@gmail.com> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
4b445cbf16
Коммит
a344b3225b
@@ -24,6 +24,7 @@ const mySQLDeadlockCode = uint16(1213)
|
||||
type RetryLayer struct {
|
||||
store.Store
|
||||
AccessControlPolicyStore store.AccessControlPolicyStore
|
||||
AttributesStore store.AttributesStore
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
@@ -79,6 +80,10 @@ func (s *RetryLayer) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return s.AccessControlPolicyStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Attributes() store.AttributesStore {
|
||||
return s.AttributesStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Audit() store.AuditStore {
|
||||
return s.AuditStore
|
||||
}
|
||||
@@ -280,6 +285,11 @@ type RetryLayerAccessControlPolicyStore struct {
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerAttributesStore struct {
|
||||
store.AttributesStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerAuditStore struct {
|
||||
store.AuditStore
|
||||
Root *RetryLayer
|
||||
@@ -583,27 +593,6 @@ func (s *RetryLayerAccessControlPolicyStore) Get(c request.CTX, id string) (*mod
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
@@ -625,6 +614,27 @@ func (s *RetryLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.A
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.AccessControlPolicyStore.SearchPolicies(rctx, opts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, resultVar1, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, resultVar1, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -646,6 +656,90 @@ func (s *RetryLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id s
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.AttributesStore.GetChannelMembersToRemove(rctx, channelID, 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 *RetryLayerAttributesStore) GetSubject(rctx request.CTX, ID string, groupID string) (*model.Subject, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.AttributesStore.GetSubject(rctx, ID, groupID)
|
||||
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 *RetryLayerAttributesStore) RefreshAttributes() error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.AttributesStore.RefreshAttributes()
|
||||
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 *RetryLayerAttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.AttributesStore.SearchUsers(rctx, opts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, resultVar1, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, resultVar1, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerAuditStore) Get(userID string, offset int, limit int) (model.Audits, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -16513,6 +16607,7 @@ func New(childStore store.Store) *RetryLayer {
|
||||
}
|
||||
|
||||
newStore.AccessControlPolicyStore = &RetryLayerAccessControlPolicyStore{AccessControlPolicyStore: childStore.AccessControlPolicy(), Root: &newStore}
|
||||
newStore.AttributesStore = &RetryLayerAttributesStore{AttributesStore: childStore.Attributes(), 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}
|
||||
|
||||
@@ -67,6 +67,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("PropertyGroup").Return(&mocks.PropertyGroupStore{})
|
||||
mock.On("PropertyValue").Return(&mocks.PropertyValueStore{})
|
||||
mock.On("AccessControlPolicy").Return(&mocks.AccessControlPolicyStore{})
|
||||
mock.On("Attributes").Return(&mocks.AttributesStore{})
|
||||
return mock
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -17,6 +18,8 @@ import (
|
||||
sq "github.com/mattermost/squirrel"
|
||||
)
|
||||
|
||||
const MaxPerPage = 1000
|
||||
|
||||
// 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 {
|
||||
@@ -152,7 +155,7 @@ func newSqlAccessControlPolicyStore(sqlStore *SqlStore, metrics einterfaces.Metr
|
||||
return s
|
||||
}
|
||||
|
||||
func preSaveAccessControlPolicy(policy, existingPolicy *model.AccessControlPolicy) {
|
||||
func preSaveAccessControlPolicy(policy *storeAccessControlPolicy, 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
|
||||
@@ -181,38 +184,6 @@ func (s *SqlAccessControlPolicyStore) Save(rctx request.CTX, policy *model.Acces
|
||||
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)
|
||||
@@ -225,6 +196,57 @@ func (s *SqlAccessControlPolicyStore) Save(rctx request.CTX, policy *model.Acces
|
||||
props = AppendBinaryFlag(props)
|
||||
}
|
||||
|
||||
if existingPolicy != nil {
|
||||
if existingPolicy.Type != policy.Type {
|
||||
return nil, errors.New("cannot change type of existing policy")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Check if the policy has actually changed
|
||||
// We compare data, name, and version fields, and ensure type hasn't changed
|
||||
if bytes.Equal(storePolicy.Data, tmp.Data) &&
|
||||
storePolicy.Name == tmp.Name &&
|
||||
storePolicy.Version == tmp.Version {
|
||||
return existingPolicy, nil
|
||||
}
|
||||
|
||||
existingData := tmp.Data
|
||||
existingProps := tmp.Props
|
||||
if s.IsBinaryParamEnabled() {
|
||||
existingData = AppendBinaryFlag(existingData)
|
||||
existingProps = AppendBinaryFlag(existingProps)
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Insert("AccessControlPolicyHistory").
|
||||
Columns(accessControlPolicyHistorySliceColumns()...).
|
||||
Values(tmp.ID, tmp.Name, tmp.Type, tmp.CreateAt, tmp.Revision, tmp.Version, existingData, existingProps)
|
||||
|
||||
_, 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)
|
||||
}
|
||||
} else {
|
||||
// if there is no existing policy, also check the history table
|
||||
// to make sure we are not overwriting an existing policy
|
||||
existingPolicy, err = s.getHistoryT(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)
|
||||
}
|
||||
}
|
||||
|
||||
preSaveAccessControlPolicy(storePolicy, existingPolicy)
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Insert("AccessControlPolicies").
|
||||
Columns(accessControlPolicySliceColumns()...).
|
||||
@@ -329,11 +351,29 @@ func (s *SqlAccessControlPolicyStore) SetActiveStatus(rctx request.CTX, id strin
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
|
||||
}
|
||||
_, err = tx.Query(query, args...)
|
||||
_, err = tx.Exec(query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update policy with id=%s", id)
|
||||
}
|
||||
|
||||
if existingPolicy.Type == model.AccessControlPolicyTypeParent {
|
||||
// if the policy is a parent, we need to update the child policies
|
||||
var expr sq.Sqlizer
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
expr = sq.Expr("Data->'imports' @> ?::jsonb", fmt.Sprintf("%q", id))
|
||||
} else {
|
||||
expr = sq.Expr("JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?)", fmt.Sprintf("%q", id))
|
||||
}
|
||||
query, args, err = s.getQueryBuilder().Update("AccessControlPolicies").Set("Active", active).Where(expr).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to build query for policy with id=%s", id)
|
||||
}
|
||||
_, err = tx.Exec(query, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update child policies with id=%s", id)
|
||||
}
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "commit_transaction")
|
||||
}
|
||||
@@ -345,7 +385,7 @@ func (s *SqlAccessControlPolicyStore) Get(_ request.CTX, id string) (*model.Acce
|
||||
p := storeAccessControlPolicy{}
|
||||
query := s.selectQueryBuilder.Where(sq.Eq{"ID": id})
|
||||
|
||||
err := s.GetReplica().GetBuilder(&p, query)
|
||||
err := s.GetMaster().GetBuilder(&p, query)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("AccessControlPolicy", id)
|
||||
@@ -388,7 +428,35 @@ func (s *SqlAccessControlPolicyStore) getT(_ request.CTX, tx *sqlxTxWrapper, id
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (s *SqlAccessControlPolicyStore) GetAll(_ request.CTX, opts store.GetPolicyOptions) ([]*model.AccessControlPolicy, error) {
|
||||
func (s *SqlAccessControlPolicyStore) getHistoryT(_ request.CTX, tx *sqlxTxWrapper, id string) (*model.AccessControlPolicy, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(accessControlPolicyHistorySliceColumns()...).
|
||||
From("AccessControlPolicyHistory").
|
||||
Where(
|
||||
sq.Eq{"ID": id},
|
||||
).OrderBy("Revision DESC").
|
||||
Limit(1)
|
||||
|
||||
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 model.GetAccessControlPolicyOptions) ([]*model.AccessControlPolicy, model.AccessControlPolicyCursor, error) {
|
||||
p := []storeAccessControlPolicy{}
|
||||
query := s.selectQueryBuilder
|
||||
|
||||
@@ -404,18 +472,156 @@ func (s *SqlAccessControlPolicyStore) GetAll(_ request.CTX, opts store.GetPolicy
|
||||
query = query.Where(sq.Eq{"Type": opts.Type})
|
||||
}
|
||||
|
||||
cursor := opts.Cursor
|
||||
|
||||
if !cursor.IsEmpty() {
|
||||
query = query.Where(sq.Or{
|
||||
sq.Gt{"Id": cursor.ID},
|
||||
})
|
||||
}
|
||||
|
||||
limit := uint64(opts.Limit)
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
} else if limit > MaxPerPage {
|
||||
limit = MaxPerPage
|
||||
}
|
||||
|
||||
query = query.Limit(limit)
|
||||
|
||||
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)
|
||||
return nil, cursor, 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 nil, cursor, errors.Wrapf(err, "failed to parse policy with id=%s", p[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
if len(policies) != 0 {
|
||||
cursor.ID = policies[len(policies)-1].ID
|
||||
}
|
||||
|
||||
return policies, cursor, nil
|
||||
}
|
||||
|
||||
func (s *SqlAccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
type wrapper struct {
|
||||
storeAccessControlPolicy
|
||||
ChildIDs json.RawMessage
|
||||
}
|
||||
|
||||
p := []wrapper{}
|
||||
var query sq.SelectBuilder
|
||||
if opts.IncludeChildren && opts.ParentID == "" {
|
||||
columns := accessControlPolicySliceColumns("p")
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
childIDs := `COALESCE((SELECT JSON_AGG(c.ID)
|
||||
FROM AccessControlPolicies c
|
||||
WHERE c.Type != 'parent'
|
||||
AND c.Data->'imports' @> JSONB_BUILD_ARRAY(p.ID)), '[]'::json) AS ChildIDs`
|
||||
columns = append(columns, childIDs)
|
||||
} else {
|
||||
childIDs := `COALESCE((SELECT JSON_ARRAYAGG(c.ID)
|
||||
FROM AccessControlPolicies c
|
||||
WHERE c.Type != 'parent'
|
||||
AND JSON_SEARCH(c.Data->'$.imports', 'one', p.ID) IS NOT NULL), JSON_ARRAY()) AS ChildIDs`
|
||||
columns = append(columns, childIDs)
|
||||
}
|
||||
query = s.getQueryBuilder().Select(columns...).From("AccessControlPolicies p")
|
||||
} else {
|
||||
query = s.selectQueryBuilder
|
||||
}
|
||||
|
||||
count := s.getQueryBuilder().Select("COUNT(*)").From("AccessControlPolicies")
|
||||
|
||||
if opts.Term != "" {
|
||||
condition := sq.Like{"Name": fmt.Sprintf("%%%s%%", opts.Term)}
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
}
|
||||
|
||||
if opts.Type != "" {
|
||||
condition := sq.Eq{"Type": opts.Type}
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
}
|
||||
|
||||
if opts.ParentID != "" {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
condition := sq.Expr("Data->'imports' @> ?", fmt.Sprintf("%q", opts.ParentID))
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
} else {
|
||||
condition := sq.Expr("JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?)", fmt.Sprintf("%q", opts.ParentID))
|
||||
query = query.Where(condition)
|
||||
count = count.Where(condition)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Active {
|
||||
query = query.Where(sq.Eq{"Active": true})
|
||||
count = count.Where(sq.Eq{"Active": true})
|
||||
}
|
||||
|
||||
cursor := opts.Cursor
|
||||
|
||||
if !cursor.IsEmpty() {
|
||||
query = query.Where(sq.Gt{"Id": cursor.ID})
|
||||
}
|
||||
|
||||
limit := uint64(opts.Limit)
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
} else if limit > MaxPerPage {
|
||||
limit = MaxPerPage
|
||||
}
|
||||
|
||||
query = query.Limit(limit)
|
||||
|
||||
err := s.GetReplica().SelectBuilder(&p, query)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to find policies with opts={\"name\"=%q, \"resourceType\"=%q", opts.Term, opts.Type)
|
||||
}
|
||||
|
||||
policies := make([]*model.AccessControlPolicy, len(p))
|
||||
for i := range p {
|
||||
m, err2 := p[i].toModel()
|
||||
if err2 != nil {
|
||||
return nil, 0, errors.Wrapf(err2, "failed to parse policy with id=%s", p[i].ID)
|
||||
}
|
||||
|
||||
// Props field is not guaranteed to be persisted correctly, and it shouldn't be.
|
||||
// This is a field that we want to include metadata, some values may be stored but
|
||||
// not all of them. For example for the childs, we don't want to update it whenever a
|
||||
// child policy changes.
|
||||
if opts.IncludeChildren && opts.ParentID == "" {
|
||||
if m.Props == nil {
|
||||
m.Props = make(map[string]any)
|
||||
}
|
||||
// Unmarshal the JSON array into a slice of strings
|
||||
var childIDs []string
|
||||
if err = json.Unmarshal(p[i].ChildIDs, &childIDs); err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to unmarshal child IDs for policy with id=%s", p[i].ID)
|
||||
}
|
||||
m.Props["child_ids"] = childIDs
|
||||
}
|
||||
policies[i] = m
|
||||
}
|
||||
|
||||
var total int64
|
||||
err = s.GetReplica().GetBuilder(&total, count)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to count policies with opts={\"name\"=%q, \"resourceType\"=%q", opts.Term, opts.Type)
|
||||
}
|
||||
|
||||
if len(policies) != 0 {
|
||||
cursor.ID = policies[len(policies)-1].ID
|
||||
}
|
||||
|
||||
return policies, total, nil
|
||||
}
|
||||
|
||||
253
server/channels/store/sqlstore/attributes_store.go
Обычный файл
253
server/channels/store/sqlstore/attributes_store.go
Обычный файл
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SqlAttributesStore struct {
|
||||
*SqlStore
|
||||
metrics einterfaces.MetricsInterface
|
||||
|
||||
selectQueryBuilder sq.SelectBuilder
|
||||
}
|
||||
|
||||
func attributesSliceColumns(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 + "TargetID as ID",
|
||||
p + "TargetType as Type",
|
||||
p + "Attributes",
|
||||
}
|
||||
}
|
||||
|
||||
func newSqlAttributesStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) store.AttributesStore {
|
||||
s := &SqlAttributesStore{
|
||||
SqlStore: sqlStore,
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
s.selectQueryBuilder = s.getQueryBuilder().Select(attributesSliceColumns()...).From("AttributeView")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) RefreshAttributes() error {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
if _, err := s.GetMaster().Exec("REFRESH MATERIALIZED VIEW AttributeView"); err != nil {
|
||||
return errors.Wrap(err, "error refreshing materialized view AttributeView")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) GetSubject(rctx request.CTX, ID, groupID string) (*model.Subject, error) {
|
||||
query := s.selectQueryBuilder.Where(sq.And{sq.Eq{"TargetID": ID}, sq.Eq{"GroupID": groupID}})
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query for subject")
|
||||
}
|
||||
|
||||
row := s.GetReplica().QueryRowxContext(rctx.Context(), q, args...)
|
||||
if err := row.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get subject")
|
||||
}
|
||||
|
||||
var subject model.Subject
|
||||
var properties []byte
|
||||
|
||||
if err := row.Scan(&subject.ID, &subject.Type, &properties); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("Attributes", ID)
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to scan subject row")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(properties, &subject.Attributes); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal attributes")
|
||||
}
|
||||
|
||||
return &subject, nil
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(getUsersColumns()...).From("Users").LeftJoin("AttributeView ON Users.Id = AttributeView.TargetID").
|
||||
OrderBy("Users.Id ASC")
|
||||
|
||||
count := s.getQueryBuilder().Select("COUNT(*)").From("Users").LeftJoin("AttributeView ON Users.Id = AttributeView.TargetID")
|
||||
|
||||
if opts.Query != "" {
|
||||
query = query.Where(sq.Expr(opts.Query, opts.Args...))
|
||||
count = count.Where(sq.Expr(opts.Query, opts.Args...))
|
||||
}
|
||||
|
||||
argCount := len(opts.Args)
|
||||
|
||||
if opts.Limit > 0 {
|
||||
query = query.Limit(uint64(opts.Limit))
|
||||
} else if opts.Limit > MaxPerPage {
|
||||
query = query.Limit(uint64(MaxPerPage))
|
||||
}
|
||||
|
||||
if !opts.AllowInactive {
|
||||
query = query.Where("Users.DeleteAt = 0")
|
||||
count = count.Where("Users.DeleteAt = 0")
|
||||
}
|
||||
|
||||
if opts.TeamID != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = ? AND DeleteAt = 0)", opts.TeamID)
|
||||
count = count.Where("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = ? AND DeleteAt = 0)", opts.TeamID)
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = $%d AND DeleteAt = 0)", argCount), opts.TeamID))
|
||||
count = count.Where(sq.Expr(fmt.Sprintf("Users.Id IN (SELECT UserId FROM TeamMembers WHERE TeamId = $%d AND DeleteAt = 0)", argCount), opts.TeamID))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ExcludeChannelMembers != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Expr("NOT EXISTS (SELECT 1 FROM ChannelMembers WHERE ChannelMembers.UserId = Users.Id AND ChannelMembers.ChannelId = ?)", opts.ExcludeChannelMembers))
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("NOT EXISTS (SELECT 1 FROM ChannelMembers WHERE ChannelMembers.UserId = Users.Id AND ChannelMembers.ChannelId = $%d)", argCount), opts.ExcludeChannelMembers))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Cursor.TargetID != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Expr("TargetID > ?", opts.Cursor.TargetID))
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("TargetID > $%d", argCount), opts.Cursor.TargetID))
|
||||
}
|
||||
}
|
||||
|
||||
searchFields := make([]string, 0, len(UserSearchTypeNames))
|
||||
for _, field := range UserSearchTypeNames {
|
||||
searchFields = append(searchFields, strings.Join([]string{"Users", field}, "."))
|
||||
}
|
||||
|
||||
if term := opts.Term; strings.TrimSpace(term) != "" {
|
||||
_, query = generateSearchQueryForExpression(query, strings.Fields(term), searchFields, s.DriverName() == model.DatabaseDriverPostgres, argCount)
|
||||
_, count = generateSearchQueryForExpression(count, strings.Fields(term), searchFields, s.DriverName() == model.DatabaseDriverPostgres, argCount)
|
||||
}
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to build query for subjects")
|
||||
}
|
||||
|
||||
users := []*model.User{}
|
||||
if err = s.GetReplica().Select(&users, q, args...); err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to find Users with term=%s and searchType=%v", opts.Term, searchFields)
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
u.Sanitize(map[string]bool{})
|
||||
}
|
||||
|
||||
var total int64
|
||||
|
||||
if !opts.IgnoreCount {
|
||||
err = s.GetReplica().GetBuilder(&total, count)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed to count Users with term=%s and searchType=%v", opts.Term, searchFields)
|
||||
}
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func (s *SqlAttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelMemberSliceColumns()...).From("ChannelMembers").LeftJoin("AttributeView ON ChannelMembers.UserId = AttributeView.TargetID").
|
||||
OrderBy("ChannelMembers.UserId ASC")
|
||||
|
||||
if opts.Query != "" {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("(NOT (%s) OR AttributeView.TargetID IS NULL)", opts.Query), opts.Args...))
|
||||
}
|
||||
|
||||
argCount := len(opts.Args)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Eq{"ChannelMembers.ChannelId": channelID})
|
||||
} else {
|
||||
argCount++
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("ChannelMembers.ChannelId = $%d", argCount), channelID))
|
||||
}
|
||||
|
||||
if opts.Limit > 0 {
|
||||
query = query.Limit(uint64(opts.Limit))
|
||||
} else if opts.Limit > MaxPerPage {
|
||||
query = query.Limit(uint64(MaxPerPage))
|
||||
}
|
||||
|
||||
if opts.Cursor.TargetID != "" {
|
||||
argCount++
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = query.Where(sq.Expr("ChannelMembers.UserId > ?", opts.Cursor.TargetID))
|
||||
} else {
|
||||
query = query.Where(sq.Expr(fmt.Sprintf("ChannelMembers.UserId > $%d", argCount), opts.Cursor.TargetID))
|
||||
}
|
||||
}
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build query for subjects")
|
||||
}
|
||||
|
||||
members := []*model.ChannelMember{}
|
||||
if err := s.GetReplica().Select(&members, q, args...); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find channel members with for channel id=%s", channelID)
|
||||
}
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func generateSearchQueryForExpression(query sq.SelectBuilder, terms []string, fields []string, isPostgreSQL bool, prevArgs int) (int, sq.SelectBuilder) {
|
||||
for _, term := range terms {
|
||||
searchFields := []string{}
|
||||
termArgs := []any{}
|
||||
for _, field := range fields {
|
||||
if isPostgreSQL {
|
||||
prevArgs++
|
||||
searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower($%d) escape '*' ", field, prevArgs))
|
||||
} else {
|
||||
searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field))
|
||||
}
|
||||
termArgs = append(termArgs, fmt.Sprintf("%%%s%%", strings.TrimLeft(term, "@")))
|
||||
}
|
||||
if isPostgreSQL {
|
||||
prevArgs++
|
||||
searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower($%d) escape '*' ", "Id", prevArgs))
|
||||
} else {
|
||||
searchFields = append(searchFields, "Id = ?")
|
||||
}
|
||||
termArgs = append(termArgs, strings.TrimLeft(term, "@"))
|
||||
query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...)
|
||||
}
|
||||
|
||||
return prevArgs, query
|
||||
}
|
||||
14
server/channels/store/sqlstore/attributes_store_test.go
Обычный файл
14
server/channels/store/sqlstore/attributes_store_test.go
Обычный файл
@@ -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 TestAttributesStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestAttributesStore)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func channelMemberSliceColumns() []string {
|
||||
|
||||
// channelSliceColumns returns fields of the channel as a string slice.
|
||||
// Optionally, you can add a prefix (accepts only 1 value) to the fields.
|
||||
func channelSliceColumns(prefix ...string) []string {
|
||||
func channelSliceColumns(isSelect bool, prefix ...string) []string {
|
||||
var p string
|
||||
if len(prefix) == 1 {
|
||||
p = prefix[0] + "."
|
||||
@@ -116,7 +116,7 @@ func channelSliceColumns(prefix ...string) []string {
|
||||
panic("cannot accept multiple prefixes")
|
||||
}
|
||||
|
||||
return []string{
|
||||
columns := []string{
|
||||
p + "Id",
|
||||
p + "CreateAt",
|
||||
p + "UpdateAt",
|
||||
@@ -138,6 +138,16 @@ func channelSliceColumns(prefix ...string) []string {
|
||||
p + "LastRootPostAt",
|
||||
p + "BannerInfo",
|
||||
}
|
||||
|
||||
if isSelect {
|
||||
if p == "" {
|
||||
p = "Channels."
|
||||
}
|
||||
|
||||
columns = append(columns, fmt.Sprintf("EXISTS (SELECT 1 FROM AccessControlPolicies acp WHERE acp.ID = %sId) AS PolicyEnforced", p))
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
func channelToSlice(channel *model.Channel) []any {
|
||||
@@ -493,7 +503,7 @@ func newSqlChannelStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface
|
||||
metrics: metrics,
|
||||
}
|
||||
|
||||
s.tableSelectQuery = s.getQueryBuilder().Select(channelSliceColumns()...).From("Channels")
|
||||
s.tableSelectQuery = s.getQueryBuilder().Select(channelSliceColumns(true)...).From("Channels")
|
||||
|
||||
s.sidebarCategorySelectQuery = s.getQueryBuilder().
|
||||
Select("SidebarCategories.Id", "SidebarCategories.UserId", "SidebarCategories.TeamId", "SidebarCategories.SortOrder", "SidebarCategories.Sorting", "SidebarCategories.Type", "SidebarCategories.DisplayName", "SidebarCategories.Muted", "SidebarCategories.Collapsed").
|
||||
@@ -731,7 +741,7 @@ func (s SqlChannelStore) saveChannelT(transaction *sqlxTxWrapper, channel *model
|
||||
|
||||
insert := s.getQueryBuilder().
|
||||
Insert("Channels").
|
||||
Columns(channelSliceColumns()...).
|
||||
Columns(channelSliceColumns(false)...).
|
||||
Values(channelToSlice(channel)...)
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
insert = insert.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Id=Id"))
|
||||
@@ -908,7 +918,7 @@ func (s SqlChannelStore) Get(id string, allowFromCache bool) (*model.Channel, er
|
||||
//nolint:unparam
|
||||
func (s SqlChannelStore) GetMany(ids []string, allowFromCache bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Id": ids})
|
||||
sql, args, err := query.ToSql()
|
||||
@@ -1070,7 +1080,7 @@ func (s SqlChannelStore) PermanentDeleteMembersByChannel(rctx request.CTX, chann
|
||||
|
||||
func (s SqlChannelStore) GetChannels(teamId string, userId string, opts *model.ChannelSearchOpts) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("ch")...).
|
||||
Select(channelSliceColumns(true, "ch")...).
|
||||
From("Channels ch, ChannelMembers cm").
|
||||
Where(
|
||||
sq.And{
|
||||
@@ -1125,7 +1135,7 @@ func (s SqlChannelStore) GetChannels(teamId string, userId string, opts *model.C
|
||||
|
||||
func (s SqlChannelStore) GetChannelsByUser(userId string, includeDeleted bool, lastDeleteAt, pageSize int, fromChannelID string) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels, ChannelMembers").
|
||||
Where(
|
||||
sq.And{
|
||||
@@ -1233,7 +1243,7 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo
|
||||
Select("count(c.Id)")
|
||||
} else {
|
||||
selectQuery = s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...).
|
||||
Select(channelSliceColumns(true, "c")...).
|
||||
Columns(
|
||||
"Teams.DisplayName AS TeamDisplayName",
|
||||
"Teams.Name AS TeamName",
|
||||
@@ -1280,6 +1290,11 @@ func (s SqlChannelStore) getAllChannelsQuery(opts store.ChannelSearchOpts, forCo
|
||||
if opts.ExcludePolicyConstrained {
|
||||
query = query.Where("RetentionPoliciesChannels.ChannelId IS NULL")
|
||||
}
|
||||
if opts.ExcludeAccessControlPolicyEnforced {
|
||||
query = query.Where("c.Id NOT IN (SELECT ID From AccessControlPolicies WHERE Type = ?)", model.AccessControlPolicyTypeChannel)
|
||||
} else if opts.AccessControlPolicyEnforced {
|
||||
query = query.InnerJoin("AccessControlPolicies acp ON c.Id = acp.ID")
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
@@ -1296,7 +1311,7 @@ func (s SqlChannelStore) GetMoreChannels(teamId string, userId string, offset in
|
||||
})
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id = Channels.Id)").
|
||||
Where(sq.Eq{
|
||||
@@ -1321,7 +1336,7 @@ func (s SqlChannelStore) GetPrivateChannelsForTeam(teamId string, offset int, li
|
||||
channels := model.ChannelList{}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Type": model.ChannelTypePrivate, "TeamId": teamId, "DeleteAt": 0}).
|
||||
OrderBy("DisplayName").
|
||||
@@ -1342,7 +1357,7 @@ func (s SqlChannelStore) GetPrivateChannelsForTeam(teamId string, offset int, li
|
||||
|
||||
func (s SqlChannelStore) GetPublicChannelsForTeam(teamId string, offset int, limit int) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels pc ON (pc.Id = Channels.Id)").
|
||||
Where(sq.Eq{
|
||||
@@ -1386,7 +1401,7 @@ func (s SqlChannelStore) GetPublicChannelsByIdsForTeam(teamId string, channelIds
|
||||
var data model.ChannelList
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels pc ON (pc.Id = Channels.Id)").
|
||||
Where(sq.And{
|
||||
@@ -1481,7 +1496,7 @@ func (s SqlChannelStore) getByNames(teamId string, names []string, allowFromCach
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(cond)
|
||||
|
||||
@@ -1516,7 +1531,7 @@ func (s SqlChannelStore) GetByName(teamId string, name string, allowFromCache bo
|
||||
|
||||
func (s SqlChannelStore) getByName(teamId string, name string, includeDeleted bool, allowFromCache bool) (*model.Channel, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Name": name}).
|
||||
Where(sq.Or{
|
||||
@@ -1567,7 +1582,7 @@ func (s SqlChannelStore) GetDeleted(teamId string, offset int, limit int, userId
|
||||
channels := model.ChannelList{}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Or{
|
||||
sq.Eq{"TeamId": teamId},
|
||||
@@ -2883,7 +2898,7 @@ func (s SqlChannelStore) GetAll(teamId string) ([]*model.Channel, error) {
|
||||
|
||||
func (s SqlChannelStore) GetChannelsByIds(channelIds []string, includeDeleted bool) ([]*model.Channel, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns()...).
|
||||
Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Eq{"Id": channelIds}).
|
||||
OrderBy("Name")
|
||||
@@ -2907,7 +2922,7 @@ func (s SqlChannelStore) GetChannelsByIds(channelIds []string, includeDeleted bo
|
||||
|
||||
func (s SqlChannelStore) GetChannelsWithTeamDataByIds(channelIDs []string, includeDeleted bool) ([]*model.ChannelWithTeamData, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...).
|
||||
Select(channelSliceColumns(true, "c")...).
|
||||
Columns(
|
||||
"COALESCE(t.DisplayName, '') As TeamDisplayName",
|
||||
"COALESCE(t.Name, '') AS TeamName",
|
||||
@@ -2937,7 +2952,7 @@ func (s SqlChannelStore) GetChannelsWithTeamDataByIds(channelIDs []string, inclu
|
||||
|
||||
func (s SqlChannelStore) GetForPost(postId string) (*model.Channel, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("Posts ON Channels.Id = Posts.ChannelId").
|
||||
Where(sq.Eq{
|
||||
@@ -3110,7 +3125,7 @@ func (s SqlChannelStore) GetTeamMembersForChannel(channelID string) ([]string, e
|
||||
|
||||
func (s SqlChannelStore) Autocomplete(rctx request.CTX, userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...).
|
||||
Select(channelSliceColumns(true, "c")...).
|
||||
Columns(
|
||||
"t.DisplayName AS TeamDisplayName",
|
||||
"t.Name AS TeamName",
|
||||
@@ -3167,7 +3182,7 @@ func (s SqlChannelStore) Autocomplete(rctx request.CTX, userID, term string, inc
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) AutocompleteInTeam(rctx request.CTX, teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns()...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "c")...).
|
||||
From("Channels c").
|
||||
Where(sq.Eq{"c.TeamId": teamID}).
|
||||
OrderBy("c.DisplayName").
|
||||
@@ -3203,7 +3218,7 @@ func (s SqlChannelStore) AutocompleteInTeam(rctx request.CTX, teamID, userID, te
|
||||
|
||||
func (s SqlChannelStore) AutocompleteInTeamForSearch(teamID string, userID string, term string, includeDeleted bool) (model.ChannelList, error) {
|
||||
// shared query
|
||||
query := s.getSubQueryBuilder().Select(channelSliceColumns("C")...).
|
||||
query := s.getSubQueryBuilder().Select(channelSliceColumns(true, "C")...).
|
||||
From("Channels AS C").
|
||||
Join("ChannelMembers AS CM ON CM.ChannelId = C.Id").
|
||||
Limit(50).
|
||||
@@ -3294,7 +3309,7 @@ func (s SqlChannelStore) AutocompleteInTeamForSearch(teamID string, userID strin
|
||||
func (s SqlChannelStore) autocompleteInTeamForSearchDirectMessages(userID string, term string) ([]*model.Channel, error) {
|
||||
// create the main query
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("C")...).
|
||||
Select(channelSliceColumns(true, "C")...).
|
||||
Columns("OtherUsers.Username AS DisplayName").
|
||||
From("Channels AS C").
|
||||
Join("ChannelMembers AS CM ON CM.ChannelId = C.Id").
|
||||
@@ -3339,7 +3354,7 @@ func (s SqlChannelStore) autocompleteInTeamForSearchDirectMessages(userID string
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id = Channels.Id)").
|
||||
Where(sq.Eq{"c.TeamId": teamId}).
|
||||
@@ -3361,7 +3376,7 @@ func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchArchivedInTeam(teamId string, term string, userId string) (model.ChannelList, error) {
|
||||
queryBase := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
queryBase := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("Channels c ON (c.Id = Channels.Id)").
|
||||
Where(sq.And{
|
||||
@@ -3405,7 +3420,7 @@ func (s SqlChannelStore) SearchArchivedInTeam(teamId string, term string, userId
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchForUserInTeam(userId string, teamId string, term string, includeDeleted bool) (model.ChannelList, error) {
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id = Channels.Id)").
|
||||
Join("ChannelMembers cm ON (c.Id = cm.ChannelId)").
|
||||
@@ -3441,7 +3456,7 @@ func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.Se
|
||||
selectQuery = s.getQueryBuilder().Select("count(*)")
|
||||
} else {
|
||||
selectQuery = s.getQueryBuilder().
|
||||
Select(channelSliceColumns("c")...)
|
||||
Select(channelSliceColumns(true, "c")...)
|
||||
if opts.IncludeTeamInfo {
|
||||
selectQuery = selectQuery.Columns(
|
||||
"t.DisplayName AS TeamDisplayName",
|
||||
@@ -3557,6 +3572,18 @@ func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.Se
|
||||
})
|
||||
}
|
||||
|
||||
if opts.ExcludeAccessControlPolicyEnforced {
|
||||
query = query.Where("c.Id NOT IN (SELECT ID From AccessControlPolicies WHERE Type = ?)", model.AccessControlPolicyTypeChannel)
|
||||
} else if opts.ParentAccessControlPolicyId != "" {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
query = query.Where(sq.Expr("c.Id IN (SELECT ID From AccessControlPolicies WHERE Type = ? AND Data->'imports' @> ?)", model.AccessControlPolicyTypeChannel, fmt.Sprintf("%q", opts.ParentAccessControlPolicyId)))
|
||||
} else {
|
||||
query = query.Where(sq.Expr("c.Id IN (SELECT ID From AccessControlPolicies WHERE Type = ? AND JSON_CONTAINS(JSON_EXTRACT(Data, '$.imports'), ?))", model.AccessControlPolicyTypeChannel, fmt.Sprintf("%q", opts.ParentAccessControlPolicyId)))
|
||||
}
|
||||
} else if opts.AccessControlPolicyEnforced {
|
||||
query = query.InnerJoin("AccessControlPolicies acp ON acp.ID = c.Id")
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -3601,7 +3628,7 @@ func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) (
|
||||
"c.DeleteAt": 0,
|
||||
})
|
||||
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns("Channels")...).
|
||||
query := s.getQueryBuilder().Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Join("PublicChannels c ON (c.Id=Channels.Id)").
|
||||
Where(sq.And{
|
||||
@@ -3808,7 +3835,7 @@ func (s SqlChannelStore) searchGroupChannelsQuery(userId, term string, isPostgre
|
||||
Having(having).
|
||||
Limit(model.ChannelSearchDefaultLimit)
|
||||
|
||||
return s.getQueryBuilder().Select(channelSliceColumns()...).
|
||||
return s.getQueryBuilder().Select(channelSliceColumns(true)...).
|
||||
From("Channels").
|
||||
Where(sq.Expr("Id IN (?)", subq))
|
||||
}
|
||||
@@ -3820,7 +3847,7 @@ func (s SqlChannelStore) searchGroupChannelsQuery(userId, term string, isPostgre
|
||||
having = append(having, sq.Expr(baseLikeTerm, "%"+term+"%"))
|
||||
}
|
||||
|
||||
cc := s.getSubQueryBuilder().Select(channelSliceColumns("c")...).
|
||||
cc := s.getSubQueryBuilder().Select(channelSliceColumns(true, "c")...).
|
||||
From("Channels c").
|
||||
Join("ChannelMembers cm ON c.Id=cm.ChannelId").
|
||||
Join("Users u on u.Id = cm.UserId").
|
||||
@@ -4154,7 +4181,7 @@ func (s SqlChannelStore) ClearAllCustomRoleAssignments() (err error) {
|
||||
|
||||
func (s SqlChannelStore) GetAllChannelsForExportAfter(limit int, afterId string) ([]*model.ChannelForExport, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
Columns(
|
||||
"Teams.Name as TeamName",
|
||||
"Schemes.Name as SchemeName",
|
||||
@@ -4222,7 +4249,7 @@ func (s SqlChannelStore) GetChannelMembersForExport(userId string, teamId string
|
||||
func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId string, includeArchivedChannels bool) ([]*model.DirectChannelForExport, error) {
|
||||
directChannelsForExport := []*model.DirectChannelForExport{}
|
||||
query := s.getQueryBuilder().
|
||||
Select(channelSliceColumns("Channels")...).
|
||||
Select(channelSliceColumns(true, "Channels")...).
|
||||
From("Channels").
|
||||
Where(sq.And{
|
||||
sq.Gt{"Channels.Id": afterId},
|
||||
|
||||
@@ -119,6 +119,7 @@ type SqlStoreStores struct {
|
||||
propertyField store.PropertyFieldStore
|
||||
propertyValue store.PropertyValueStore
|
||||
accessControlPolicy store.AccessControlPolicyStore
|
||||
Attributes store.AttributesStore
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
@@ -265,6 +266,7 @@ func New(settings model.SqlSettings, logger mlog.LoggerIFace, metrics einterface
|
||||
store.stores.propertyField = newPropertyFieldStore(store)
|
||||
store.stores.propertyValue = newPropertyValueStore(store)
|
||||
store.stores.accessControlPolicy = newSqlAccessControlPolicyStore(store, metrics)
|
||||
store.stores.Attributes = newSqlAttributesStore(store, metrics)
|
||||
|
||||
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
|
||||
|
||||
@@ -1085,6 +1087,10 @@ func (ss *SqlStore) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return ss.stores.accessControlPolicy
|
||||
}
|
||||
|
||||
func (ss *SqlStore) Attributes() store.AttributesStore {
|
||||
return ss.stores.Attributes
|
||||
}
|
||||
|
||||
func (ss *SqlStore) DropAllTables() {
|
||||
if ss.DriverName() == model.DatabaseDriverPostgres {
|
||||
ss.masterX.Exec(`DO
|
||||
|
||||
@@ -96,6 +96,7 @@ type Store interface {
|
||||
PropertyField() PropertyFieldStore
|
||||
PropertyValue() PropertyValueStore
|
||||
AccessControlPolicy() AccessControlPolicyStore
|
||||
Attributes() AttributesStore
|
||||
}
|
||||
|
||||
type RetentionPolicyStore interface {
|
||||
@@ -1116,7 +1117,14 @@ type AccessControlPolicyStore interface {
|
||||
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)
|
||||
SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error)
|
||||
}
|
||||
|
||||
type AttributesStore interface {
|
||||
RefreshAttributes() error
|
||||
GetSubject(rctx request.CTX, ID, groupID string) (*model.Subject, error)
|
||||
SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error)
|
||||
GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error)
|
||||
}
|
||||
|
||||
// ChannelSearchOpts contains options for searching channels.
|
||||
@@ -1129,27 +1137,30 @@ type AccessControlPolicyStore interface {
|
||||
// Page page requested, if results are paginated.
|
||||
// PerPage number of results per page, if paginated.
|
||||
type ChannelSearchOpts struct {
|
||||
Term string
|
||||
NotAssociatedToGroup string
|
||||
IncludeDeleted bool
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeTeamInfo bool
|
||||
IncludeSearchByID bool
|
||||
ExcludeRemote bool
|
||||
CountOnly bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int
|
||||
LastUpdateAt int
|
||||
Term string
|
||||
NotAssociatedToGroup string
|
||||
IncludeDeleted bool
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeTeamInfo bool
|
||||
IncludeSearchByID bool
|
||||
ExcludeRemote bool
|
||||
CountOnly bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int
|
||||
LastUpdateAt int
|
||||
AccessControlPolicyEnforced bool
|
||||
ExcludeAccessControlPolicyEnforced bool
|
||||
ParentAccessControlPolicyId string
|
||||
}
|
||||
|
||||
func (c *ChannelSearchOpts) IsPaginated() bool {
|
||||
@@ -1211,11 +1222,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -290,24 +290,51 @@ func testAccessControlPolicyStoreGetAll(t *testing.T, rctx request.CTX, ss store
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
id3 := "zzz" + model.NewId()[3:] // ensure the order of the ID
|
||||
parentPolicy2 := &model.AccessControlPolicy{
|
||||
ID: id3,
|
||||
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)
|
||||
})
|
||||
|
||||
_, err = ss.AccessControlPolicy().Save(rctx, parentPolicy2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
|
||||
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{})
|
||||
policies, _, err := ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 2)
|
||||
require.Len(t, policies, 3)
|
||||
})
|
||||
|
||||
t.Run("GetAll by type", func(t *testing.T) {
|
||||
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{Type: model.AccessControlPolicyTypeParent})
|
||||
policies, _, err := ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{Type: model.AccessControlPolicyTypeParent, IncludeChildren: true})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 1)
|
||||
require.Len(t, policies, 2)
|
||||
require.Equal(t, parentPolicy.ID, policies[0].ID)
|
||||
require.Equal(t, map[string]any{"child_ids": []string{resourcePolicy.ID}}, policies[0].Props)
|
||||
require.Equal(t, map[string]any{"child_ids": []string{}}, policies[1].Props)
|
||||
|
||||
policies, err = ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{Type: model.AccessControlPolicyTypeChannel})
|
||||
policies, _, err = ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{Type: model.AccessControlPolicyTypeChannel})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 1)
|
||||
@@ -315,13 +342,13 @@ func testAccessControlPolicyStoreGetAll(t *testing.T, rctx request.CTX, ss store
|
||||
})
|
||||
|
||||
t.Run("GetAll by parent", func(t *testing.T) {
|
||||
policies, err := ss.AccessControlPolicy().GetAll(rctx, store.GetPolicyOptions{ParentID: parentPolicy.ID})
|
||||
policies, _, err := ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{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()})
|
||||
policies, _, err = ss.AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{ParentID: model.NewId()})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 0)
|
||||
|
||||
282
server/channels/store/storetest/attributes_store.go
Обычный файл
282
server/channels/store/storetest/attributes_store.go
Обычный файл
@@ -0,0 +1,282 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
testPropertyGroupName = "test_property_group"
|
||||
testPropertyA = "test_property_a"
|
||||
testPropertyB = "test_property_b"
|
||||
testPropertyValueA1 = "value_a1"
|
||||
testPropertyValueA2 = "value_a2"
|
||||
testPropertyValueB1 = "value_b1"
|
||||
)
|
||||
|
||||
var (
|
||||
testTeamID = model.NewId()
|
||||
)
|
||||
|
||||
func TestAttributesStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
t.Run("RefreshAndGet", func(t *testing.T) { testAttributesStoreRefresh(t, rctx, ss) })
|
||||
t.Run("SearchUsers", func(t *testing.T) { testAttributesStoreSearchUsers(t, rctx, ss, s) })
|
||||
}
|
||||
|
||||
func createTestUsers(t *testing.T, rctx request.CTX, ss store.Store) ([]*model.User, string, func()) {
|
||||
maxUsersPerTeam := 50
|
||||
|
||||
u1 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
|
||||
_, err := ss.User().Save(rctx, &u1)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
_, nErr := ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: testTeamID, UserId: u1.Id}, maxUsersPerTeam)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
u2 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
_, err = ss.User().Save(rctx, &u2)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
_, nErr = ss.Team().SaveMember(rctx, &model.TeamMember{TeamId: testTeamID, UserId: u2.Id}, maxUsersPerTeam)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// user3 does not have any attributes
|
||||
u3 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
|
||||
_, err = ss.User().Save(rctx, &u3)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
// user3 does not have any attributes
|
||||
u4 := model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: model.NewUsername(),
|
||||
}
|
||||
|
||||
_, err = ss.User().Save(rctx, &u4)
|
||||
require.NoError(t, err, "couldn't save user")
|
||||
|
||||
group, err := ss.PropertyGroup().Register(testPropertyGroupName)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, group.ID)
|
||||
require.Equal(t, testPropertyGroupName, group.Name)
|
||||
groupID := group.ID
|
||||
|
||||
fieldA, err := ss.PropertyField().Create(&model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: testPropertyA,
|
||||
Type: model.PropertyFieldTypeText,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
fieldB, err := ss.PropertyField().Create(&model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: testPropertyB,
|
||||
Type: model.PropertyFieldTypeText,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
vala1, err := json.Marshal(testPropertyValueA1)
|
||||
require.NoError(t, err)
|
||||
vala2, err := json.Marshal(testPropertyValueA2)
|
||||
require.NoError(t, err)
|
||||
valab1, err := json.Marshal(testPropertyValueB1)
|
||||
require.NoError(t, err)
|
||||
|
||||
pva1, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u1.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldA.ID,
|
||||
Value: vala1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pvb1, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u1.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldB.ID,
|
||||
Value: valab1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pva2, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u2.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldA.ID,
|
||||
Value: vala2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
pva3, err := ss.PropertyValue().Create(&model.PropertyValue{
|
||||
TargetID: u3.Id,
|
||||
TargetType: "user",
|
||||
GroupID: groupID,
|
||||
FieldID: fieldA.ID,
|
||||
Value: vala1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return []*model.User{&u1, &u2, &u3}, groupID, func() {
|
||||
for _, pv := range []*model.PropertyValue{pva1, pvb1, pva2, pva3} {
|
||||
dErr := ss.PropertyValue().Delete(groupID, pv.ID)
|
||||
require.NoError(t, dErr, "couldn't delete property value")
|
||||
}
|
||||
for _, field := range []*model.PropertyField{fieldA, fieldB} {
|
||||
dErr := ss.PropertyField().Delete(groupID, field.ID)
|
||||
require.NoError(t, dErr, "couldn't delete property field")
|
||||
}
|
||||
for _, u := range []*model.User{&u1, &u2, &u3, &u4} {
|
||||
dErr := ss.User().PermanentDelete(rctx, u.Id)
|
||||
require.NoError(t, dErr, "couldn't delete user")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testAttributesStoreRefresh(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
users, groupID, cleanup := createTestUsers(t, rctx, ss)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
t.Run("Refresh attributes", func(t *testing.T) {
|
||||
err := ss.Attributes().RefreshAttributes()
|
||||
require.NoError(t, err, "couldn't refresh attributes")
|
||||
|
||||
// Check if the attributes are set correctly
|
||||
for _, user := range users {
|
||||
subject, err := ss.Attributes().GetSubject(rctx, user.Id, groupID)
|
||||
require.NoError(t, err, "couldn't get subject")
|
||||
|
||||
require.Equal(t, user.Id, subject.ID)
|
||||
require.Equal(t, "user", subject.Type)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get non-existing subject", func(t *testing.T) {
|
||||
subject, err := ss.Attributes().GetSubject(rctx, "non-existing-id", groupID)
|
||||
require.Error(t, err, "expected error when getting non-existing subject")
|
||||
require.IsType(t, &store.ErrNotFound{}, err, "expected not found error")
|
||||
require.Nil(t, subject, "expected nil subject for non-existing ID")
|
||||
})
|
||||
}
|
||||
|
||||
func testAttributesStoreSearchUsers(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
||||
users, _, cleanup := createTestUsers(t, rctx, ss)
|
||||
t.Cleanup(cleanup)
|
||||
require.Len(t, users, 3, "expected 3 users")
|
||||
|
||||
err := ss.Attributes().RefreshAttributes()
|
||||
require.NoError(t, err, "couldn't refresh attributes")
|
||||
|
||||
t.Run("Search users without query", func(t *testing.T) {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 4, "expected 4 users")
|
||||
require.Equal(t, int64(4), count, "expected count 4 users")
|
||||
})
|
||||
|
||||
t.Run("Search users without query, limit by team", func(t *testing.T) {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
TeamID: testTeamID,
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 2, "expected 2 users")
|
||||
require.Equal(t, int64(2), count, "expected count 2 users")
|
||||
})
|
||||
|
||||
t.Run("Search users with a random value query", func(t *testing.T) {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: "Attributes ->> '$." + testPropertyA + "' = ?",
|
||||
Args: []any{"random_value"},
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Empty(t, subjects, "expected no users with the query")
|
||||
require.Equal(t, int64(0), count, "expected count 0 users")
|
||||
})
|
||||
|
||||
t.Run("Search users with a valid value query", func(t *testing.T) {
|
||||
var query string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = "Attributes ->> '$." + testPropertyB + "' = ?"
|
||||
} else {
|
||||
query = "Attributes ->> '" + testPropertyB + "' = $1::text"
|
||||
}
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: query,
|
||||
Args: []any{testPropertyValueB1},
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 1, "expected 1 user with the query")
|
||||
require.Equal(t, subjects[0].Id, users[0].Id, "expected user ID to match")
|
||||
require.Equal(t, int64(1), count, "expected count 1 user")
|
||||
})
|
||||
|
||||
t.Run("Search users with a valid value query and limit", func(t *testing.T) {
|
||||
var query string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = "Attributes ->> '$." + testPropertyA + "' = ?"
|
||||
} else {
|
||||
query = "Attributes ->> '" + testPropertyA + "' = $1::text"
|
||||
}
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: query,
|
||||
Args: []any{testPropertyValueA1},
|
||||
Limit: 1,
|
||||
})
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 1, "expected 1 user with the query")
|
||||
if users[0].Id < users[2].Id {
|
||||
require.Equal(t, subjects[0].Id, users[0].Id, "expected user ID to match")
|
||||
} else {
|
||||
require.Equal(t, subjects[0].Id, users[2].Id, "expected user ID to match")
|
||||
}
|
||||
require.Equal(t, int64(2), count, "expected count 1 user")
|
||||
})
|
||||
|
||||
t.Run("Search users with pagination", func(t *testing.T) {
|
||||
var query string
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
query = "Attributes ->> '$." + testPropertyA + "' = ?"
|
||||
} else {
|
||||
query = "Attributes ->> '" + testPropertyA + "' = $1::text"
|
||||
}
|
||||
|
||||
cursor := strings.Repeat("0", 26)
|
||||
for i := 0; i < 5; i++ {
|
||||
subjects, count, err := ss.Attributes().SearchUsers(rctx, model.SubjectSearchOptions{
|
||||
Query: query,
|
||||
Args: []any{testPropertyValueA1},
|
||||
Limit: 1,
|
||||
Cursor: model.SubjectCursor{
|
||||
TargetID: cursor,
|
||||
},
|
||||
})
|
||||
if len(subjects) == 0 {
|
||||
break
|
||||
}
|
||||
cursor = subjects[0].Id
|
||||
|
||||
require.NoError(t, err, "couldn't search users")
|
||||
require.Len(t, subjects, 1, "expected 1 user with the query")
|
||||
require.Equal(t, int64(2), count, "expected count 2 user with the query")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -8,8 +8,6 @@ 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
|
||||
@@ -65,36 +63,6 @@ func (_m *AccessControlPolicyStore) Get(c request.CTX, id string) (*model.Access
|
||||
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)
|
||||
@@ -125,6 +93,43 @@ func (_m *AccessControlPolicyStore) Save(c request.CTX, policy *model.AccessCont
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchPolicies provides a mock function with given fields: rctx, opts
|
||||
func (_m *AccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
ret := _m.Called(rctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SearchPolicies")
|
||||
}
|
||||
|
||||
var r0 []*model.AccessControlPolicy
|
||||
var r1 int64
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error)); ok {
|
||||
return rf(rctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.AccessControlPolicySearch) []*model.AccessControlPolicy); ok {
|
||||
r0 = rf(rctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.AccessControlPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, model.AccessControlPolicySearch) int64); ok {
|
||||
r1 = rf(rctx, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, model.AccessControlPolicySearch) error); ok {
|
||||
r2 = rf(rctx, opts)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
145
server/channels/store/storetest/mocks/AttributesStore.go
Обычный файл
145
server/channels/store/storetest/mocks/AttributesStore.go
Обычный файл
@@ -0,0 +1,145 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// AttributesStore is an autogenerated mock type for the AttributesStore type
|
||||
type AttributesStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetChannelMembersToRemove provides a mock function with given fields: rctx, channelID, opts
|
||||
func (_m *AttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
ret := _m.Called(rctx, channelID, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetChannelMembersToRemove")
|
||||
}
|
||||
|
||||
var r0 []*model.ChannelMember
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) ([]*model.ChannelMember, error)); ok {
|
||||
return rf(rctx, channelID, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, model.SubjectSearchOptions) []*model.ChannelMember); ok {
|
||||
r0 = rf(rctx, channelID, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.ChannelMember)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, model.SubjectSearchOptions) error); ok {
|
||||
r1 = rf(rctx, channelID, opts)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSubject provides a mock function with given fields: rctx, ID, groupID
|
||||
func (_m *AttributesStore) GetSubject(rctx request.CTX, ID string, groupID string) (*model.Subject, error) {
|
||||
ret := _m.Called(rctx, ID, groupID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSubject")
|
||||
}
|
||||
|
||||
var r0 *model.Subject
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) (*model.Subject, error)); ok {
|
||||
return rf(rctx, ID, groupID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, string, string) *model.Subject); ok {
|
||||
r0 = rf(rctx, ID, groupID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Subject)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, string, string) error); ok {
|
||||
r1 = rf(rctx, ID, groupID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RefreshAttributes provides a mock function with given fields:
|
||||
func (_m *AttributesStore) RefreshAttributes() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RefreshAttributes")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SearchUsers provides a mock function with given fields: rctx, opts
|
||||
func (_m *AttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
ret := _m.Called(rctx, opts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SearchUsers")
|
||||
}
|
||||
|
||||
var r0 []*model.User
|
||||
var r1 int64
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.SubjectSearchOptions) ([]*model.User, int64, error)); ok {
|
||||
return rf(rctx, opts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, model.SubjectSearchOptions) []*model.User); ok {
|
||||
r0 = rf(rctx, opts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, model.SubjectSearchOptions) int64); ok {
|
||||
r1 = rf(rctx, opts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, model.SubjectSearchOptions) error); ok {
|
||||
r2 = rf(rctx, opts)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// NewAttributesStore creates a new instance of AttributesStore. 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 NewAttributesStore(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AttributesStore {
|
||||
mock := &AttributesStore{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -44,6 +44,26 @@ func (_m *Store) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Attributes provides a mock function with given fields:
|
||||
func (_m *Store) Attributes() store.AttributesStore {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Attributes")
|
||||
}
|
||||
|
||||
var r0 store.AttributesStore
|
||||
if rf, ok := ret.Get(0).(func() store.AttributesStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.AttributesStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Audit provides a mock function with given fields:
|
||||
func (_m *Store) Audit() store.AuditStore {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -70,6 +70,7 @@ type Store struct {
|
||||
PropertyFieldStore mocks.PropertyFieldStore
|
||||
PropertyValueStore mocks.PropertyValueStore
|
||||
AccessControlPolicyStore mocks.AccessControlPolicyStore
|
||||
AttributesStore mocks.AttributesStore
|
||||
}
|
||||
|
||||
func (s *Store) SetContext(context context.Context) { s.context = context }
|
||||
@@ -158,6 +159,9 @@ func (s *Store) ReplicaLagTime() error { return nil }
|
||||
func (s *Store) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return &s.AccessControlPolicyStore
|
||||
}
|
||||
func (s *Store) Attributes() store.AttributesStore {
|
||||
return &s.AttributesStore
|
||||
}
|
||||
|
||||
func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
return mock.AssertExpectationsForObjects(t,
|
||||
@@ -202,5 +206,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
&s.ChannelBookmarkStore,
|
||||
&s.ScheduledPostStore,
|
||||
&s.AccessControlPolicyStore,
|
||||
&s.AttributesStore,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type TimerLayer struct {
|
||||
store.Store
|
||||
Metrics einterfaces.MetricsInterface
|
||||
AccessControlPolicyStore store.AccessControlPolicyStore
|
||||
AttributesStore store.AttributesStore
|
||||
AuditStore store.AuditStore
|
||||
BotStore store.BotStore
|
||||
ChannelStore store.ChannelStore
|
||||
@@ -75,6 +76,10 @@ func (s *TimerLayer) AccessControlPolicy() store.AccessControlPolicyStore {
|
||||
return s.AccessControlPolicyStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Attributes() store.AttributesStore {
|
||||
return s.AttributesStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Audit() store.AuditStore {
|
||||
return s.AuditStore
|
||||
}
|
||||
@@ -276,6 +281,11 @@ type TimerLayerAccessControlPolicyStore struct {
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerAttributesStore struct {
|
||||
store.AttributesStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerAuditStore struct {
|
||||
store.AuditStore
|
||||
Root *TimerLayer
|
||||
@@ -553,22 +563,6 @@ func (s *TimerLayerAccessControlPolicyStore) Get(c request.CTX, id string) (*mod
|
||||
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()
|
||||
|
||||
@@ -585,6 +579,22 @@ func (s *TimerLayerAccessControlPolicyStore) Save(c request.CTX, policy *model.A
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAccessControlPolicyStore) SearchPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, resultVar1, err := s.AccessControlPolicyStore.SearchPolicies(rctx, 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.SearchPolicies", success, elapsed)
|
||||
}
|
||||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id string, active bool) (*model.AccessControlPolicy, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -601,6 +611,70 @@ func (s *TimerLayerAccessControlPolicyStore) SetActiveStatus(c request.CTX, id s
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) GetChannelMembersToRemove(rctx request.CTX, channelID string, opts model.SubjectSearchOptions) ([]*model.ChannelMember, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.AttributesStore.GetChannelMembersToRemove(rctx, channelID, 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("AttributesStore.GetChannelMembersToRemove", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) GetSubject(rctx request.CTX, ID string, groupID string) (*model.Subject, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.AttributesStore.GetSubject(rctx, ID, groupID)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AttributesStore.GetSubject", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) RefreshAttributes() error {
|
||||
start := time.Now()
|
||||
|
||||
err := s.AttributesStore.RefreshAttributes()
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("AttributesStore.RefreshAttributes", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAttributesStore) SearchUsers(rctx request.CTX, opts model.SubjectSearchOptions) ([]*model.User, int64, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, resultVar1, err := s.AttributesStore.SearchUsers(rctx, 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("AttributesStore.SearchUsers", success, elapsed)
|
||||
}
|
||||
return result, resultVar1, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerAuditStore) Get(userID string, offset int, limit int) (model.Audits, error) {
|
||||
start := time.Now()
|
||||
|
||||
@@ -13021,6 +13095,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
|
||||
}
|
||||
|
||||
newStore.AccessControlPolicyStore = &TimerLayerAccessControlPolicyStore{AccessControlPolicyStore: childStore.AccessControlPolicy(), Root: &newStore}
|
||||
newStore.AttributesStore = &TimerLayerAttributesStore{AttributesStore: childStore.Attributes(), 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}
|
||||
|
||||
Ссылка в новой задаче
Block a user