[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
@@ -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
Обычный файл
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;
|
||||
|
||||
Ссылка в новой задаче
Block a user