[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>
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2025-05-15 11:33:08 +02:00
коммит произвёл GitHub
родитель 4b445cbf16
Коммит a344b3225b
156 изменённых файлов: 14382 добавлений и 621 удалений

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

@@ -3,6 +3,7 @@
/* eslint-disable max-lines */
import type {AccessControlPolicy, CELExpressionError, AccessControlTestResult, AccessControlPoliciesResult, AccessControlPolicyChannelsResult, AccessControlVisualAST} from '@mattermost/types/access_control';
import type {ClusterInfo, AnalyticsRow, SchemaMigration, LogFilterQuery} from '@mattermost/types/admin';
import type {AppBinding, AppCallRequest, AppCallResponse} from '@mattermost/types/apps';
import type {Audit} from '@mattermost/types/audits';
@@ -108,7 +109,7 @@ import type {
import type {Post, PostList, PostSearchResults, PostsUsageResponse, TeamsUsageResponse, PaginatedPostList, FilesUsageResponse, PostAcknowledgement, PostAnalytics, PostInfo} from '@mattermost/types/posts';
import type {PreferenceType} from '@mattermost/types/preferences';
import type {ProductNotices} from '@mattermost/types/product_notices';
import type {UserPropertyField, UserPropertyFieldPatch} from '@mattermost/types/properties';
import type {PropertyField, UserPropertyField, UserPropertyFieldPatch} from '@mattermost/types/properties';
import type {Reaction} from '@mattermost/types/reactions';
import type {RemoteCluster, RemoteClusterAcceptInvite, RemoteClusterPatch, RemoteClusterWithPassword} from '@mattermost/types/remote_clusters';
import type {UserReport, UserReportFilter, UserReportOptions} from '@mattermost/types/reports';
@@ -1547,7 +1548,9 @@ export default class Client4 {
excludeDefaultChannels: boolean | undefined,
includeTotalCount: false | undefined,
includeDeleted: boolean | undefined,
excludePolicyConstrained: boolean | undefined
excludePolicyConstrained: boolean | undefined,
accessControlPolicyEnforced: boolean | undefined,
excludeAccessControlPolicyEnforced: boolean | undefined
): Promise<ChannelWithTeamData[]>;
getAllChannels(
page: number | undefined,
@@ -1556,7 +1559,9 @@ export default class Client4 {
excludeDefaultChannels: boolean | undefined,
includeTotalCount: true,
includeDeleted: boolean | undefined,
excludePolicyConstrained: boolean | undefined
excludePolicyConstrained: boolean | undefined,
accessControlPolicyEnforced: boolean | undefined,
excludeAccessControlPolicyEnforced: boolean | undefined
): Promise<ChannelsWithTotalCount>;
getAllChannels(
page = 0,
@@ -1566,16 +1571,36 @@ export default class Client4 {
includeTotalCount = false,
includeDeleted = false,
excludePolicyConstrained = false,
accessControlPolicyEnforced = false,
excludeAccessControlPolicyEnforced = false,
) {
const queryData = {
const queryData: Record<string, any> = {
page,
per_page: perPage,
not_associated_to_group: notAssociatedToGroup,
exclude_default_channels: excludeDefaultChannels,
include_total_count: includeTotalCount,
include_deleted: includeDeleted,
exclude_policy_constrained: excludePolicyConstrained,
};
if (notAssociatedToGroup) {
queryData.not_associated_to_group = notAssociatedToGroup;
}
if (excludeDefaultChannels) {
queryData.exclude_default_channels = excludeDefaultChannels;
}
if (excludePolicyConstrained) {
queryData.exclude_policy_constrained = excludePolicyConstrained;
}
if (accessControlPolicyEnforced) {
queryData.access_control_policy_enforced = accessControlPolicyEnforced;
}
if (excludeAccessControlPolicyEnforced) {
queryData.exclude_access_control_policy_enforced = excludeAccessControlPolicyEnforced;
}
return this.doFetch<ChannelWithTeamData[] | ChannelsWithTotalCount>(
`${this.getChannelsRoute()}${buildQueryString(queryData)}`,
{method: 'get'},
@@ -4382,6 +4407,111 @@ export default class Client4 {
{method: 'post', headers: {'Connection-Id': connectionId}},
);
};
getAccessControlPolicy = (id: string) => {
return this.doFetch<AccessControlPolicy>(
`${this.getBaseRoute()}/access_control_policies/${id}`,
{method: 'get'},
);
};
updateOrCreateAccessControlPolicy = (policy: AccessControlPolicy) => {
return this.doFetch<AccessControlPolicy>(
`${this.getBaseRoute()}/access_control_policies`,
{method: 'put', body: JSON.stringify(policy)},
);
};
deleteAccessControlPolicy = (id: string) => {
return this.doFetch<AccessControlPolicy>(
`${this.getBaseRoute()}/access_control_policies/${id}`,
{method: 'delete'},
);
};
getAccessControlPolicies = (after: string, limit: number) => {
return this.doFetch<AccessControlPoliciesResult>(
`${this.getBaseRoute()}/access_control_policies/search`,
{method: 'post', body: JSON.stringify({type: 'parent', cursor: {id: after}, limit})},
);
};
getChildPolicies = (parentId: string, after: string, limit: number) => {
return this.doFetch<AccessControlPoliciesResult>(
`${this.getBaseRoute()}/access_control_policies/search`,
{method: 'post', body: JSON.stringify({parent_id: parentId, cursor: {id: after}, limit})},
);
};
getChannelsForAccessControlPolicy = (policyId: string, after: string, limit: number) => {
return this.doFetch<AccessControlPolicyChannelsResult>(
`${this.getBaseRoute()}/access_control_policies/${policyId}/resources/channels?after=${after}&limit=${limit}`,
{method: 'get'},
);
};
searchAccessControlPolicies = (term: string, type: string, after: string, limit: number) => {
return this.doFetch<AccessControlPoliciesResult>(
`${this.getBaseRoute()}/access_control_policies/search`,
{method: 'post', body: JSON.stringify({term, type, cursor: {id: after}, limit, include_children: true})},
);
};
searchChildAccessControlPolicyChannels = (policyId: string, term: string, opts: ChannelSearchOpts) => {
return this.doFetch<ChannelsWithTotalCount>(
`${this.getBaseRoute()}/access_control_policies/${policyId}/resources/channels/search?term=${term}`,
{method: 'post', body: JSON.stringify({term, ...opts})},
);
};
updateAccessControlPolicyActive = (policyId: string, active: boolean) => {
return this.doFetch<StatusOK>(
`${this.getBaseRoute()}/access_control_policies/${policyId}/activate?active=${active}`,
{method: 'get'},
);
};
assignChannelsToAccessControlPolicy = (policyId: string, channelIds: string[]) => {
return this.doFetch<StatusOK>(
`${this.getBaseRoute()}/access_control_policies/${policyId}/assign`,
{method: 'post', body: JSON.stringify({channel_ids: channelIds})},
);
};
unassignChannelsFromAccessControlPolicy = (policyId: string, channelIds: string[]) => {
return this.doFetch<StatusOK>(
`${this.getBaseRoute()}/access_control_policies/${policyId}/unassign`,
{method: 'delete', body: JSON.stringify({channel_ids: channelIds})},
);
};
getAccessControlFields = (after: string, limit: number) => {
return this.doFetch<PropertyField[]>(
`${this.getBaseRoute()}/access_control_policies/cel/autocomplete/fields?after=${after}&limit=${limit}`,
{method: 'get'},
);
};
checkAccessControlExpression = (expression: string) => {
return this.doFetch<CELExpressionError[]>(
`${this.getBaseRoute()}/access_control_policies/cel/check`,
{method: 'post', body: JSON.stringify({expression})},
);
};
testAccessControlExpression = (expression: string, term: string, after: string, limit: number) => {
return this.doFetch<AccessControlTestResult>(
`${this.getBaseRoute()}/access_control_policies/cel/test`,
{method: 'post', body: JSON.stringify({expression, term, after, limit})},
);
};
expressionToVisualFormat = (expression: string) => {
return this.doFetch<AccessControlVisualAST>(
`${this.getBaseRoute()}/access_control_policies/cel/visual_ast`,
{method: 'post', body: JSON.stringify({expression})},
);
};
}
export function parseAndMergeNestedHeaders(originalHeaders: any) {

75
webapp/platform/types/src/access_control.ts Обычный файл
Просмотреть файл

@@ -0,0 +1,75 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ChannelWithTeamData} from './channels';
import type {UserProfile} from './users';
export type AccessControlPolicy = {
id: string;
name: string;
type: string;
revision?: number;
created_at?: number;
version?: string;
active?: boolean;
imports?: string[];
props?: Record<string, unknown[]>;
rules: AccessControlPolicyRule[];
}
export type AccessControlPolicyCursor = {
id: string;
}
export type AccessControlPoliciesResult = {
policies: AccessControlPolicy[];
total: number;
}
export type AccessControlPolicySearchOpts = {
term: string;
type: string;
cursor: AccessControlPolicyCursor;
limit: number;
}
export type AccessControlPolicyChannelsResult = {
channels: ChannelWithTeamData[];
total: number;
}
export type AccessControlPolicyRule = {
actions?: string[];
expression: string;
}
export type CELExpressionError = {
message: string;
line: number;
column: number;
}
export type AccessControlTestResult = {
users: UserProfile[];
total: number;
}
export type AccessControlEntity = {
name: string;
attributes: AccessControlAttribute[];
}
export type AccessControlAttribute = {
name: string;
values: string[];
}
export type AccessControlVisualAST = {
conditions: AccessControlVisualASTNode[];
}
export type AccessControlVisualASTNode = {
attribute: string;
operator: string;
value: any;
value_type: number;
}

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

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {AccessControlPolicy} from './access_control';
import type {Audit} from './audits';
import type {Compliance} from './compliance';
import type {AdminConfig, ClientLicense, EnvironmentConfig} from './config';
@@ -10,7 +11,7 @@ import type {PluginRedux, PluginStatusRedux} from './plugins';
import type {SamlCertificateStatus, SamlMetadataResponse} from './saml';
import type {Team} from './teams';
import type {UserAccessToken, UserProfile} from './users';
import type {RelationOneToOne} from './utilities';
import type {RelationOneToOne, IDMappedObjects} from './utilities';
export enum LogLevelEnum {
SILLY = 'silly',
@@ -69,6 +70,8 @@ export type AdminState = {
dataRetentionCustomPolicies: DataRetentionCustomPolicies;
dataRetentionCustomPoliciesCount: number;
prevTrialLicense: ClientLicense;
accessControlPolicies: IDMappedObjects<AccessControlPolicy>;
channelsForAccessControlPolicy: Record<string, string[]>;
};
export type AnalyticsState = {

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

@@ -68,6 +68,7 @@ export type Channel = {
props?: Record<string, any>;
policy_id?: string | null;
banner_info?: ChannelBanner;
policy_enforced?: boolean;
};
export type ServerChannel = Channel & {
@@ -230,4 +231,7 @@ export type ChannelSearchOpts = {
deleted?: boolean;
page?: number;
per_page?: number;
access_control_policy_enforced?: boolean;
exclude_access_control_policy_enforced?: boolean;
parent_access_control_policy_id?: string;
};

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

@@ -125,6 +125,7 @@ export type ClientConfig = {
FeatureFlagAppsEnabled: string;
FeatureFlagCallsEnabled: string;
FeatureFlagCustomProfileAttributes: string;
FeatureFlagAttributeBasedAccessControl: string;
FeatureFlagWebSocketEventScope: string;
ForgotPasswordLink: string;
GiphySdkKey: string;

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

@@ -3,7 +3,7 @@
import type {IDMappedObjects} from './utilities';
export type JobType = 'data_retention' | 'elasticsearch_post_indexing' | 'bleve_post_indexing' | 'ldap_sync' | 'message_export';
export type JobType = 'data_retention' | 'elasticsearch_post_indexing' | 'bleve_post_indexing' | 'ldap_sync' | 'message_export' | 'access_control_sync';
export type JobStatus = 'pending' | 'in_progress' | 'success' | 'error' | 'cancel_requested' | 'canceled' | 'warning';
export type Job = JobTypeBase & {
id: string;