mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-09 20:50:12 +00:00
* fix(e2e): preserve iOS App Group container metadata across shard specs and use swipe helper in Edit Server spec * fix(maestro): dismiss iOS QuickPath keyboard onboarding and retry empty server URL input * fix(e2e): avoid replaying single-use FormData streams on HTML interstitials and add ABAC/login transport retries * fix(e2e): classify disabled-feature and server-timeout errors as infra in CMT notifications * fix(e2e): address CodeRabbit review on container cache, ABAC cleanup and Maestro connect scroll * fix(e2e): wait for restored denied state in upload permission spec as well * fix(e2e): confirm restored ABAC decision server-side and scroll to URL label before Maestro recovery * fix(e2e): return to channel list before ABAC cleanup logout and drop optional flag on recovery scroll * fix(e2e): retry ABAC decision login on transport failures and classify CF 521/523 as transport failures * test(e2e): cover apiUploadFileToChannel rebuilding FormData and stream on interstitial retry * fix(e2e): budget ABAC decision waits for observed server flip latency Co-authored-by: yasser khan <attitude3cena.yf@gmail.com>
302 lines
11 KiB
TypeScript
302 lines
11 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {timeouts} from '@support/utils';
|
|
import {withTransportRetry} from '@support/utils/transport_retry';
|
|
|
|
import client from './client';
|
|
import {getResponseFromError} from './common';
|
|
|
|
// ****************************************************************
|
|
// Access Control (ABAC)
|
|
// See https://api.mattermost.com/#tag/access-control
|
|
//
|
|
// Exported API function should have the following:
|
|
// - documented using JSDoc
|
|
// - meaningful description
|
|
// - match the referenced API endpoints
|
|
// - parameter/s defined by `@param`
|
|
// - return value defined by `@return`
|
|
// ****************************************************************
|
|
|
|
// ------------------------------------------------------------
|
|
// ABAC Config
|
|
// ------------------------------------------------------------
|
|
|
|
/**
|
|
* Enable Attribute-Based Access Control (ABAC) via server config.
|
|
* Requires admin session.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @return {Object} returns {config} on success or {error, status} on error
|
|
*/
|
|
export const apiEnableABAC = async (baseUrl: string): Promise<any> => {
|
|
try {
|
|
const response = await client.put(`${baseUrl}/api/v4/config/patch`, {
|
|
AccessControlSettings: {
|
|
EnableAttributeBasedAccessControl: true,
|
|
},
|
|
});
|
|
return {config: response.data};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
};
|
|
|
|
// ------------------------------------------------------------
|
|
// Custom Profile Attribute Fields
|
|
// Used to define user attributes that ABAC policies can reference.
|
|
// See /api/v4/custom_profile_attributes/fields
|
|
// ------------------------------------------------------------
|
|
|
|
/**
|
|
* List all custom profile attribute fields.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @return {Object} returns {fields} on success or {error, status} on error
|
|
*/
|
|
export const apiGetCustomProfileAttributeFields = async (baseUrl: string): Promise<any> => {
|
|
return withTransportRetry(async () => {
|
|
try {
|
|
const response = await client.get(`${baseUrl}/api/v4/custom_profile_attributes/fields`);
|
|
return {fields: response.data ?? []};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
}, {idempotent: true, label: 'apiGetCustomProfileAttributeFields', budgetMs: timeouts.ONE_MIN});
|
|
};
|
|
|
|
/**
|
|
* Delete a custom profile attribute field by ID.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} fieldId - the field ID to delete
|
|
* @return {Object} returns {} on success or {error, status} on error
|
|
*/
|
|
export const apiDeleteCustomProfileAttributeField = async (baseUrl: string, fieldId: string): Promise<any> => {
|
|
return withTransportRetry(async () => {
|
|
try {
|
|
await client.delete(`${baseUrl}/api/v4/custom_profile_attributes/fields/${fieldId}`);
|
|
return {};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
}, {idempotent: true, label: 'apiDeleteCustomProfileAttributeField', budgetMs: timeouts.ONE_MIN});
|
|
};
|
|
|
|
/**
|
|
* Find an existing custom profile attribute field by name, or create it if absent.
|
|
* Returns {field, created} where created=true means this call created the field
|
|
* and the caller is responsible for deleting it in cleanup.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} name - field name to look up or create
|
|
* @param {string} [type] - field type when creating, defaults to 'text'
|
|
* @return {Object} returns {field, created} on success or {error, status} on error
|
|
*/
|
|
export const apiGetOrCreateCustomProfileAttributeField = async (
|
|
baseUrl: string,
|
|
name: string,
|
|
type = 'text',
|
|
): Promise<any> => {
|
|
const {fields, error: listError} = await apiGetCustomProfileAttributeFields(baseUrl);
|
|
if (listError) {
|
|
return {error: listError};
|
|
}
|
|
const existing = fields.find((f: any) => f.name === name);
|
|
if (existing) {
|
|
return {field: existing, created: false};
|
|
}
|
|
const {field, error: createError} = await apiCreateCustomProfileAttributeField(baseUrl, name, type);
|
|
if (createError) {
|
|
return {error: createError};
|
|
}
|
|
return {field, created: true};
|
|
};
|
|
|
|
/**
|
|
* Create a custom profile attribute field.
|
|
* The returned field ID is required when setting attribute values on users.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} name - display name for the field, e.g. "Department"
|
|
* @param {string} [type] - field type, defaults to 'text'
|
|
* @return {Object} returns {field} on success or {error, status} on error
|
|
*/
|
|
export const apiCreateCustomProfileAttributeField = async (
|
|
baseUrl: string,
|
|
name: string,
|
|
type = 'text',
|
|
): Promise<any> => {
|
|
try {
|
|
const response = await client.post(
|
|
`${baseUrl}/api/v4/custom_profile_attributes/fields`,
|
|
{
|
|
name,
|
|
type,
|
|
attrs: {
|
|
visibility: 'when_set',
|
|
sort_order: 0,
|
|
},
|
|
},
|
|
);
|
|
return {field: response.data};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set one or more property values on a user.
|
|
* Used to assign ABAC-relevant attributes (e.g. Department) before policy evaluation.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} userId - the target user ID
|
|
* @param {Record<string, string>} values - map of field ID → value, e.g. { [fieldId]: 'Engineering' }
|
|
* @return {Object} returns {status} on success or {error, status} on error
|
|
*/
|
|
export const apiSetUserPropertyValues = async (
|
|
baseUrl: string,
|
|
userId: string,
|
|
values: Record<string, string>,
|
|
): Promise<any> => {
|
|
return withTransportRetry(async () => {
|
|
try {
|
|
const response = await client.patch(
|
|
`${baseUrl}/api/v4/users/${userId}/custom_profile_attributes`,
|
|
values,
|
|
);
|
|
return {status: response.status};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
}, {idempotent: true, label: 'apiSetUserPropertyValues', budgetMs: timeouts.ONE_MIN});
|
|
};
|
|
|
|
// ------------------------------------------------------------
|
|
// Permission Policies
|
|
// Controls which users can perform actions (e.g. download_file_attachment).
|
|
// CEL expression options:
|
|
// "false" → denies everyone unconditionally
|
|
// "true" → grants everyone (no restriction)
|
|
// "user.attributes.Department == \"Engineering\"" → grants only matching users
|
|
// ------------------------------------------------------------
|
|
|
|
/**
|
|
* Create an ABAC permission policy.
|
|
* Requires admin session and ABAC to be enabled.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} name - unique policy name
|
|
* @param {Array<{actions: string[]; expression: string}>} rules - policy rules; each rule maps actions to a CEL expression
|
|
* @param {string[]} [roles] - roles the policy applies to, e.g. ['system_user']. Defaults to ['system_user']
|
|
* @return {Object} returns {policy} on success or {error, status} on error
|
|
*/
|
|
export const apiCreatePermissionPolicy = async (
|
|
baseUrl: string,
|
|
name: string,
|
|
rules: Array<{actions: string[]; expression: string}>,
|
|
roles: string[] = ['system_user'],
|
|
): Promise<any> => {
|
|
try {
|
|
const response = await client.put(
|
|
`${baseUrl}/api/v4/access_control_policies`,
|
|
{
|
|
name,
|
|
type: 'permission',
|
|
roles,
|
|
rules,
|
|
},
|
|
);
|
|
return {policy: response.data};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a channel access policy whose rules gate permission actions in that channel only; channel
|
|
* membership is unaffected. The policy id is the channel id, so delete it with apiDeletePermissionPolicy.
|
|
* Requires admin session and the ChannelPermissionPolicies feature flag.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} channelId - the channel the policy applies to
|
|
* @param {Array<{name: string; role: string; actions: string[]; expression: string}>} rules - v0.4 rules; each needs a unique name and a channel role such as 'channel_user'
|
|
* @return {Object} returns {policy} on success or {error, status} on error
|
|
*/
|
|
export const apiCreateChannelPermissionPolicy = async (
|
|
baseUrl: string,
|
|
channelId: string,
|
|
rules: Array<{name: string; role: string; actions: string[]; expression: string}>,
|
|
): Promise<any> => {
|
|
return withTransportRetry(async () => {
|
|
try {
|
|
const response = await client.put(
|
|
`${baseUrl}/api/v4/access_control_policies`,
|
|
{id: channelId, type: 'channel', version: 'v0.4', rules},
|
|
);
|
|
return {policy: response.data};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
}, {idempotent: true, label: 'apiCreateChannelPermissionPolicy', budgetMs: timeouts.ONE_MIN});
|
|
};
|
|
|
|
/**
|
|
* Delete an access control policy by ID.
|
|
* Requires admin session.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} policyId - the policy ID to delete
|
|
* @return {Object} returns {} on success or {error, status} on error
|
|
*/
|
|
export const apiDeletePermissionPolicy = async (baseUrl: string, policyId: string): Promise<any> => {
|
|
return withTransportRetry(async () => {
|
|
try {
|
|
await client.delete(`${baseUrl}/api/v4/access_control_policies/${policyId}`);
|
|
return {};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
}, {idempotent: true, label: 'apiDeletePermissionPolicy', budgetMs: timeouts.ONE_MIN});
|
|
};
|
|
|
|
// ------------------------------------------------------------
|
|
// Channel (Membership) Policies
|
|
// Controls which users can access / remain members of a channel
|
|
// based on their attributes (CEL expression evaluated per user).
|
|
// ------------------------------------------------------------
|
|
|
|
/**
|
|
* Activate or deactivate an access control policy.
|
|
* A policy must be active to be enforced by the server.
|
|
* Uses PUT /api/v4/access_control_policies/activate with entries array.
|
|
* @param {string} baseUrl - the base server URL
|
|
* @param {string} policyId - the policy ID
|
|
* @param {boolean} [active] - true to activate (default), false to deactivate
|
|
* @return {Object} returns {} on success or {error, status} on error
|
|
*/
|
|
export const apiSetPolicyActive = async (
|
|
baseUrl: string,
|
|
policyId: string,
|
|
active = true,
|
|
): Promise<any> => {
|
|
return withTransportRetry(async () => {
|
|
try {
|
|
await client.put(
|
|
`${baseUrl}/api/v4/access_control_policies/activate`,
|
|
{entries: [{id: policyId, active}]},
|
|
);
|
|
return {};
|
|
} catch (err) {
|
|
return getResponseFromError(err);
|
|
}
|
|
}, {idempotent: true, label: 'apiSetPolicyActive', budgetMs: timeouts.ONE_MIN});
|
|
};
|
|
|
|
export const AccessControl = {
|
|
apiEnableABAC,
|
|
apiGetCustomProfileAttributeFields,
|
|
apiGetOrCreateCustomProfileAttributeField,
|
|
apiCreateCustomProfileAttributeField,
|
|
apiDeleteCustomProfileAttributeField,
|
|
apiSetUserPropertyValues,
|
|
apiCreatePermissionPolicy,
|
|
apiCreateChannelPermissionPolicy,
|
|
apiDeletePermissionPolicy,
|
|
apiSetPolicyActive,
|
|
};
|
|
|
|
export default AccessControl;
|