[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
@@ -4,8 +4,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
@@ -18,13 +20,41 @@ const (
|
||||
AccessControlPolicyVersionV0_1 = "v0.1"
|
||||
)
|
||||
|
||||
// ParentPolicy is a augmented version of AccessPolicy to be used in
|
||||
// system console and API responses.
|
||||
type ParentPolicy struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
Children []*AccessControlPolicy `json:"children"`
|
||||
// AccessControlAttribute represents a user attribute with its name and possible values
|
||||
type AccessControlAttribute struct {
|
||||
Attribute PropertyField `json:"attribute"`
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
type AccessControlPolicyTestResponse struct {
|
||||
Users []*User `json:"users"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type GetAccessControlPolicyOptions struct {
|
||||
Type string `json:"type"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Cursor AccessControlPolicyCursor `json:"cursor"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
type AccessControlPolicySearch struct {
|
||||
Term string `json:"term"`
|
||||
Type string `json:"type"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Cursor AccessControlPolicyCursor `json:"cursor"`
|
||||
Limit int `json:"limit"`
|
||||
IncludeChildren bool `json:"include_children"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
type AccessControlPolicyCursor struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type AccessControlPoliciesWithCount struct {
|
||||
Policies []*AccessControlPolicy `json:"policies"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type AccessControlPolicy struct {
|
||||
@@ -48,6 +78,16 @@ type AccessControlPolicyRule struct {
|
||||
Expression string `json:"expression"`
|
||||
}
|
||||
|
||||
type CELExpressionError struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AccessControlQueryResult struct {
|
||||
MatchedSubjectIDs []string `json:"matched_subject_ids"`
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) IsValid() *AppError {
|
||||
switch p.Version {
|
||||
case AccessControlPolicyVersionV0_1:
|
||||
@@ -103,3 +143,63 @@ func (p *AccessControlPolicy) accessPolicyVersionV0_1() *AppError {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) Inherit(resourceID, resourceType string) (*AccessControlPolicy, *AppError) {
|
||||
rules := make([]AccessControlPolicyRule, len(p.Rules))
|
||||
|
||||
switch p.Version {
|
||||
case AccessControlPolicyVersionV0_1:
|
||||
for i, rule := range p.Rules {
|
||||
actions := make([]string, len(rule.Actions))
|
||||
copy(actions, rule.Actions)
|
||||
rules[i] = AccessControlPolicyRule{
|
||||
Actions: actions,
|
||||
Expression: fmt.Sprintf("policies.id_%s", p.ID),
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, NewAppError("AccessControlPolicy.Inherit", "model.access_policy.inherit.version.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
child := &AccessControlPolicy{
|
||||
ID: resourceID,
|
||||
Type: resourceType,
|
||||
Active: p.Active,
|
||||
CreateAt: GetMillis(),
|
||||
Version: p.Version,
|
||||
Imports: []string{p.ID},
|
||||
Rules: rules,
|
||||
|
||||
Props: map[string]any{},
|
||||
}
|
||||
|
||||
if appErr := child.IsValid(); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return child, nil
|
||||
}
|
||||
|
||||
func (c *AccessControlPolicyCursor) IsEmpty() bool {
|
||||
return c.ID == ""
|
||||
}
|
||||
|
||||
func (c *AccessControlPolicyCursor) IsValid() error {
|
||||
if c.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !IsValidId(c.ID) {
|
||||
return errors.New("cursor id is invalid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) Auditable() map[string]any {
|
||||
return map[string]any{
|
||||
"id": p.ID,
|
||||
"type": p.Type,
|
||||
"revision": p.Revision,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,30 @@ type Subject struct {
|
||||
ID string `json:"id"`
|
||||
// Type specifies the type of the Subject, eg. user, bot, etc.
|
||||
Type string `json:"type"`
|
||||
// Properties are the key-value pairs assicuated with the subject.
|
||||
// Attributes are the key-value pairs assicuated with the subject.
|
||||
// An attribute may be single-valued or multi-valued and can be a primitive type
|
||||
// (string, boolean, number) or a complex type like a JSON object or array.
|
||||
Properties map[string]any `json:"properties"`
|
||||
Attributes map[string]any `json:"attributes"`
|
||||
}
|
||||
|
||||
type SubjectSearchOptions struct {
|
||||
Term string `json:"term"`
|
||||
TeamID string `json:"team_id"`
|
||||
// Query and Args should be generated within the Access Control Service
|
||||
// and passed here wrt database driver
|
||||
Query string `json:"query"`
|
||||
Args []any `json:"args"`
|
||||
Limit int `json:"limit"`
|
||||
Cursor SubjectCursor `json:"cursor"`
|
||||
AllowInactive bool `json:"allow_inactive"`
|
||||
IgnoreCount bool `json:"ignore_count"`
|
||||
// ExcludeChannelMembers is used to exclude members from the search results
|
||||
// specifically used when syncing channel members
|
||||
ExcludeChannelMembers string `json:"exclude_members"`
|
||||
}
|
||||
|
||||
type SubjectCursor struct {
|
||||
TargetID string `json:"target_id"`
|
||||
}
|
||||
|
||||
// Resource is the target of an access request.
|
||||
@@ -41,3 +61,10 @@ type AccessDecision struct {
|
||||
Decision bool `json:"decision"`
|
||||
Context map[string]any `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type QueryExpressionParams struct {
|
||||
Expression string `json:"expression"`
|
||||
Term string `json:"term"`
|
||||
Limit int `json:"limit"`
|
||||
After string `json:"after"`
|
||||
}
|
||||
|
||||
30
server/public/model/cel.go
Обычный файл
30
server/public/model/cel.go
Обычный файл
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
// ValueType indicates whether a value is a literal or another attribute.
|
||||
type ValueType int
|
||||
|
||||
const (
|
||||
LiteralValue ValueType = iota
|
||||
AttrValue
|
||||
)
|
||||
|
||||
// Condition represents a single logical condition (e.g., user.attributes.Team == "Engineering").
|
||||
type Condition struct {
|
||||
// Left-hand side attribute selector (e.g., "user.attributes.Team").
|
||||
Attribute string `json:"attribute"`
|
||||
// The comparison operator.
|
||||
Operator string `json:"operator"`
|
||||
// Right-hand side value(s). Can be a single value or a slice for 'in'.
|
||||
Value any `json:"value"`
|
||||
// Type of the Value (LiteralValue or AttributeValue). Needed for comparisons like user.attr1 == user.attr2.
|
||||
ValueType ValueType `json:"value_type"`
|
||||
}
|
||||
|
||||
// VisualExpression represents a series of conditions combined with logical AND.
|
||||
type VisualExpression struct {
|
||||
// Conditions is a list of individual conditions that will be ANDed together.
|
||||
Conditions []Condition `json:"conditions"`
|
||||
}
|
||||
@@ -99,6 +99,7 @@ type Channel struct {
|
||||
PolicyID *string `json:"policy_id"`
|
||||
LastRootPostAt int64 `json:"last_root_post_at"`
|
||||
BannerInfo *ChannelBannerInfo `json:"banner_info"`
|
||||
PolicyEnforced bool `json:"policy_enforced"`
|
||||
}
|
||||
|
||||
func (o *Channel) Auditable() map[string]any {
|
||||
@@ -119,6 +120,7 @@ func (o *Channel) Auditable() map[string]any {
|
||||
"total_msg_count_root": o.TotalMsgCountRoot,
|
||||
"type": o.Type,
|
||||
"update_at": o.UpdateAt,
|
||||
"policy_enforced": o.PolicyEnforced,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,26 +211,30 @@ type ChannelModeratedRolesPatch struct {
|
||||
// Paginate whether to paginate the results.
|
||||
// Page page requested, if results are paginated.
|
||||
// PerPage number of results per page, if paginated.
|
||||
// ExcludeAccessPolicyEnforced will exclude channels that are enforced by an access policy.
|
||||
type ChannelSearchOpts struct {
|
||||
NotAssociatedToGroup string
|
||||
ExcludeDefaultChannels bool
|
||||
IncludeDeleted bool // If true, deleted channels will be included in the results.
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeSearchById bool
|
||||
ExcludeRemote bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int // When combined with IncludeDeleted, only channels deleted after this time will be returned.
|
||||
LastUpdateAt int
|
||||
NotAssociatedToGroup string
|
||||
ExcludeDefaultChannels bool
|
||||
IncludeDeleted bool // If true, deleted channels will be included in the results.
|
||||
Deleted bool
|
||||
ExcludeChannelNames []string
|
||||
TeamIds []string
|
||||
GroupConstrained bool
|
||||
ExcludeGroupConstrained bool
|
||||
PolicyID string
|
||||
ExcludePolicyConstrained bool
|
||||
IncludePolicyID bool
|
||||
IncludeSearchById bool
|
||||
ExcludeRemote bool
|
||||
Public bool
|
||||
Private bool
|
||||
Page *int
|
||||
PerPage *int
|
||||
LastDeleteAt int // When combined with IncludeDeleted, only channels deleted after this time will be returned.
|
||||
LastUpdateAt int
|
||||
AccessControlPolicyEnforced bool
|
||||
ExcludeAccessControlPolicyEnforced bool
|
||||
ParentAccessControlPolicyId string
|
||||
}
|
||||
|
||||
type ChannelMemberCountByGroup struct {
|
||||
|
||||
@@ -6,19 +6,22 @@ package model
|
||||
const ChannelSearchDefaultLimit = 50
|
||||
|
||||
type ChannelSearch struct {
|
||||
Term string `json:"term"`
|
||||
ExcludeDefaultChannels bool `json:"exclude_default_channels"`
|
||||
NotAssociatedToGroup string `json:"not_associated_to_group"`
|
||||
TeamIds []string `json:"team_ids"`
|
||||
GroupConstrained bool `json:"group_constrained"`
|
||||
ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
|
||||
ExcludePolicyConstrained bool `json:"exclude_policy_constrained"`
|
||||
Public bool `json:"public"`
|
||||
Private bool `json:"private"`
|
||||
IncludeDeleted bool `json:"include_deleted"`
|
||||
IncludeSearchById bool `json:"include_search_by_id"`
|
||||
ExcludeRemote bool `json:"exclude_remote"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Page *int `json:"page,omitempty"`
|
||||
PerPage *int `json:"per_page,omitempty"`
|
||||
Term string `json:"term"`
|
||||
ExcludeDefaultChannels bool `json:"exclude_default_channels"`
|
||||
NotAssociatedToGroup string `json:"not_associated_to_group"`
|
||||
TeamIds []string `json:"team_ids"`
|
||||
GroupConstrained bool `json:"group_constrained"`
|
||||
ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
|
||||
ExcludePolicyConstrained bool `json:"exclude_policy_constrained"`
|
||||
Public bool `json:"public"`
|
||||
Private bool `json:"private"`
|
||||
IncludeDeleted bool `json:"include_deleted"`
|
||||
IncludeSearchById bool `json:"include_search_by_id"`
|
||||
ExcludeRemote bool `json:"exclude_remote"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Page *int `json:"page,omitempty"`
|
||||
PerPage *int `json:"per_page,omitempty"`
|
||||
AccessControlPolicyEnforced bool `json:"access_control_policy_enforced"`
|
||||
ExcludeAccessControlPolicyEnforced bool `json:"exclude_access_control_policy_enforced"`
|
||||
ParentAccessControlPolicyId string `json:"parent_access_control_policy_id"`
|
||||
}
|
||||
|
||||
@@ -622,6 +622,18 @@ func (c *Client4) customProfileAttributeValuesRoute() string {
|
||||
return fmt.Sprintf("%s/values", c.customProfileAttributesRoute())
|
||||
}
|
||||
|
||||
func (c *Client4) accessControlPoliciesRoute() string {
|
||||
return "/access_control_policies"
|
||||
}
|
||||
|
||||
func (c *Client4) celRoute() string {
|
||||
return "/access_control_policies/cel"
|
||||
}
|
||||
|
||||
func (c *Client4) accessControlPolicyRoute(policyID string) string {
|
||||
return fmt.Sprintf(c.accessControlPoliciesRoute()+"/%v", policyID)
|
||||
}
|
||||
|
||||
func (c *Client4) GetServerLimits(ctx context.Context) (*ServerLimits, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.limitsRoute()+"/users", "")
|
||||
if err != nil {
|
||||
@@ -9564,3 +9576,191 @@ func (c *Client4) PatchCPAValues(ctx context.Context, values map[string]json.Raw
|
||||
|
||||
return patchedValues, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// Access Control Policies Section
|
||||
|
||||
// CreateAccessControlPolicy creates a new access control policy.
|
||||
func (c *Client4) CreateAccessControlPolicy(ctx context.Context, policy *AccessControlPolicy) (*AccessControlPolicy, *Response, error) {
|
||||
b, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("CreateAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPutBytes(ctx, c.accessControlPoliciesRoute(), b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var p AccessControlPolicy
|
||||
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
||||
return nil, nil, NewAppError("CreateAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &p, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetAccessControlPolicy(ctx context.Context, id string) (*AccessControlPolicy, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.accessControlPolicyRoute(id), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var policy AccessControlPolicy
|
||||
if err := json.NewDecoder(r.Body).Decode(&policy); err != nil {
|
||||
return nil, nil, NewAppError("GetAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &policy, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) DeleteAccessControlPolicy(ctx context.Context, id string) (*Response, error) {
|
||||
r, err := c.DoAPIDelete(ctx, c.accessControlPolicyRoute(id))
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) CheckExpression(ctx context.Context, expression string) ([]CELExpressionError, *Response, error) {
|
||||
checkExpressionRequest := struct {
|
||||
Expression string `json:"expression"`
|
||||
}{
|
||||
Expression: expression,
|
||||
}
|
||||
b, err := json.Marshal(checkExpressionRequest)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("CheckExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.celRoute()+"/check", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var errors []CELExpressionError
|
||||
if err := json.NewDecoder(r.Body).Decode(&errors); err != nil {
|
||||
return nil, nil, NewAppError("CheckExpression", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return errors, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) TestExpression(ctx context.Context, params QueryExpressionParams) (*AccessControlPolicyTestResponse, *Response, error) {
|
||||
b, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("TestExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.celRoute()+"/test", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var testResponse AccessControlPolicyTestResponse
|
||||
if err := json.NewDecoder(r.Body).Decode(&testResponse); err != nil {
|
||||
return nil, nil, NewAppError("TestExpression", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &testResponse, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SearchAccessControlPolicies(ctx context.Context, options AccessControlPolicySearch) (*AccessControlPoliciesWithCount, *Response, error) {
|
||||
b, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("SearchAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.accessControlPoliciesRoute()+"/search", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var policies AccessControlPoliciesWithCount
|
||||
if err := json.NewDecoder(r.Body).Decode(&policies); err != nil {
|
||||
return nil, nil, NewAppError("SearchAccessControlPolicies", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &policies, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) AssignAccessControlPolicies(ctx context.Context, policyID string, resourceIDs []string) (*Response, error) {
|
||||
var assignments struct {
|
||||
ChannelIds []string `json:"channel_ids"`
|
||||
}
|
||||
assignments.ChannelIds = resourceIDs
|
||||
|
||||
b, err := json.Marshal(assignments)
|
||||
if err != nil {
|
||||
return nil, NewAppError("AssignAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.accessControlPolicyRoute(policyID)+"/assign", b)
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) UnassignAccessControlPolicies(ctx context.Context, policyID string, resourceIDs []string) (*Response, error) {
|
||||
var unassignments struct {
|
||||
ChannelIds []string `json:"channel_ids"`
|
||||
}
|
||||
unassignments.ChannelIds = resourceIDs
|
||||
|
||||
b, err := json.Marshal(unassignments)
|
||||
if err != nil {
|
||||
return nil, NewAppError("UnassignAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIDeleteBytes(ctx, c.accessControlPolicyRoute(policyID)+"/unassign", b)
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetChannelsForAccessControlPolicy(ctx context.Context, policyID string, after string, limit int) (*ChannelsWithCount, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels?after="+after+"&limit="+strconv.Itoa(limit), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var channels ChannelsWithCount
|
||||
if err := json.NewDecoder(r.Body).Decode(&channels); err != nil {
|
||||
return nil, nil, NewAppError("GetChannelsForAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &channels, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) SearchChannelsForAccessControlPolicy(ctx context.Context, policyID string, options ChannelSearch) (*ChannelsWithCount, *Response, error) {
|
||||
b, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("SearchChannelsForAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPostBytes(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels/search", b)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var channels ChannelsWithCount
|
||||
if err := json.NewDecoder(r.Body).Decode(&channels); err != nil {
|
||||
return nil, nil, NewAppError("SearchChannelsForAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return &channels, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ type FeatureFlags struct {
|
||||
ExperimentalAuditSettingsSystemConsoleUI bool
|
||||
|
||||
CustomProfileAttributes bool
|
||||
|
||||
AttributeBasedAccessControl bool
|
||||
}
|
||||
|
||||
func (f *FeatureFlags) SetDefaults() {
|
||||
@@ -81,6 +83,7 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.NotificationMonitoring = true
|
||||
f.ExperimentalAuditSettingsSystemConsoleUI = false
|
||||
f.CustomProfileAttributes = false
|
||||
f.AttributeBasedAccessControl = false
|
||||
}
|
||||
|
||||
// ToMap returns the feature flags as a map[string]string
|
||||
|
||||
@@ -44,6 +44,7 @@ const (
|
||||
JobTypeExportUsersToCSV = "export_users_to_csv"
|
||||
JobTypeDeleteDmsPreferencesMigration = "delete_dms_preferences_migration"
|
||||
JobTypeMobileSessionMetadata = "mobile_session_metadata"
|
||||
JobTypeAccessControlSync = "access_control_sync"
|
||||
|
||||
JobStatusPending = "pending"
|
||||
JobStatusInProgress = "in_progress"
|
||||
|
||||
Ссылка в новой задаче
Block a user