mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-13 14:40:25 +00:00
* 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>
261 lines
9.0 KiB
TypeScript
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;
|