mattermost-mobile-upstream/detox/e2e/support/server_api/access_control.ts
Pablo Vélez 7df4a0ab97
Some checks are pending
ci / lint-typecheck (push) Waiting to run
ci / detox-lint-typecheck (push) Waiting to run
ci / test (push) Waiting to run
Mm 68219 show placeholder for redacted files (#9719)
* MM-68219 - Add redacted files placeholder and related functionality

* add feature flag control and e2e tests

* adjust placeholder design, polish code, fix permalink preview issue

* polish code and add missing test

* use import path with right path

* gate post refetch behind the feature flag, refetch the open thread and avoid observeCount

* restore filesInfo for the permalink files prop added upstream

* fix stale redaction on background servers, consume the flag on every fetch path and share the viewport emitter

* replace the in-memory redaction flag with a persisted per-post epoch so ABAC file decisions survive restarts and stale posts stay hidden

* raise the redaction epoch on every resync, capture the epoch counter, only invalidate channels on role changes, and store the newer epoch when a re-verified post is otherwise unchanged

* store superseded responses unverified instead of dropping them, keep the since-fetch when re-verifying, verify permalink embeds against the linked channel, and capture the websocket epoch on arrival

* nvalidate file decisions when a user attribute field changes or all its values are cleared

* batch channel invalidations, close file viewers only when affected, reset the epoch on unstamped redaction changes, and only  re-verify the newest page's attachment posts

* re-check older channel posts a page at a time instead of one request per post

* skip the unstamped epoch lookup for posts with no files, redacted count or permalink

* keep the attribute-view retry after later invalidations, retry linked posts unless refused for the current epoch, and stop older payloads from rolling back edits via the redaction check

* MM-70905 - disable and grey out the attachment button when an ABAC pe… (#10195)

* MM-70905 - disable and grey out the attachment button when an ABAC permission policy denies uploads in the channel

* only back off render permission fetches when the server request fails, and log other errors without retrying

* remove the disableServer helper and have removeServer do both steps

* split defaultAllowed into allowWhenUnenforced and allowWhenNotEvaluated, and treat render permissions as unenforced  on servers without the decisions API

* MM-69627: Add playbook task requirements support on mobile (#10035)

* MM-69627: Add playbook task requirements support on mobile

Allow filling required task fields when checking off checklist items during
a run, gated by the same Playbooks beta setting used on web.

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69627: Address PR review feedback for task requirements

Propagate settings write failures, harden fill-requirements save/title handling, and add missing unit coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69627: Fix TypeScript errors in new unit tests

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69627: Use defineMessages for fill requirements titles

Keep i18n extraction able to find the header titles after moving them to the route.

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69627: Address review feedback for task requirements

Update checklist items locally when saving requirement values instead of
refetching the run, add missing query/navigation tests, and apply small
callback/reduce cleanups from review.

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69627: Fix task requirements review follow-ups

Propagate local checklist write failures, clear a stale task-requirements
flag when playbooks is unsupported, and cap fill values at the desktop/
server max length.

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69627: Move requirement field into its own component

Keep one component per file on the fill-requirements screen.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* MM-69122: Add file element support to interactive dialogs (#9898)

* Implement Interactive Dialog - file upload

* update demo plugin version for e2etests

* fix and add e2etests

* review fixes

* Fix version bump checkout when a tag shares the branch name (#10229)

* Fix Android crashes caused by animated custom emoji (#10030)

* Route GIF decoding through Glide

- Keep APNG4Android for APNG, animated WebP, and animated AVIF support.
- Prepend Glide's GIF decoders to avoid APNG4Android's native GIF overflow.

* Clear Expo image resources with Glide targets

- Detach drawables when Glide clears their custom target.
- Prevent recycled GIF frame resources from remaining attached to Expo image views.

* Preserve foreground image during GIF target recycling

---------

Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Nuno Simões <nuno.simoes@mattermost.com>
Co-authored-by: MangelSpec <74370284+MangelSpec@users.noreply.github.com>

* detox - fix clobbering of feature flags that cause failures in other suites.  Also small fix of a incorrectly set test-id

---------

Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Nuno Simões <nuno.simoes@mattermost.com>
Co-authored-by: MangelSpec <74370284+MangelSpec@users.noreply.github.com>
Co-authored-by: Nate Schlossberg <nate.schlossberg@mattermost.com>
2026-10-15 15:59:13 -07:00

261 lines
9.0 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
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> => {
try {
const response = await client.get(`${baseUrl}/api/v4/custom_profile_attributes/fields`);
return {fields: response.data ?? []};
} catch (err) {
return getResponseFromError(err);
}
};
/**
* 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> => {
try {
await client.delete(`${baseUrl}/api/v4/custom_profile_attributes/fields/${fieldId}`);
return {};
} catch (err) {
return getResponseFromError(err);
}
};
/**
* 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> => {
try {
const response = await client.patch(
`${baseUrl}/api/v4/users/${userId}/custom_profile_attributes`,
values,
);
return {status: response.status};
} catch (err) {
return getResponseFromError(err);
}
};
// ------------------------------------------------------------
// 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);
}
};
/**
* 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> => {
try {
await client.delete(`${baseUrl}/api/v4/access_control_policies/${policyId}`);
return {};
} catch (err) {
return getResponseFromError(err);
}
};
// ------------------------------------------------------------
// 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> => {
try {
await client.put(
`${baseUrl}/api/v4/access_control_policies/activate`,
{entries: [{id: policyId, active}]},
);
return {};
} catch (err) {
return getResponseFromError(err);
}
};
export const AccessControl = {
apiEnableABAC,
apiGetCustomProfileAttributeFields,
apiGetOrCreateCustomProfileAttributeField,
apiCreateCustomProfileAttributeField,
apiDeleteCustomProfileAttributeField,
apiSetUserPropertyValues,
apiCreatePermissionPolicy,
apiDeletePermissionPolicy,
apiSetPolicyActive,
};
export default AccessControl;