snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,474 @@
import { AbacAttributeStoreExternalError, getPdpHealthErrorCode } from '@rocket.chat/abac';
import { Abac } from '@rocket.chat/core-services';
import type { AbacActor } from '@rocket.chat/core-services';
import type { IServerEvents, IUser } from '@rocket.chat/core-typings';
import { ServerEvents } from '@rocket.chat/models';
import { validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings/src/v1/Ajv';
import { convertSubObjectsIntoPaths } from '@rocket.chat/tools';
import {
GenericSuccessSchema,
PUTAbacAttributeUpdateBodySchema,
GETAbacAttributesQuerySchema,
GETAbacAttributesResponseSchema,
GETAbacAttributeByIdResponseSchema,
POSTAbacAttributeDefinitionSchema,
GETAbacAttributeIsInUseResponseSchema,
POSTRoomAbacAttributesBodySchema,
POSTSingleRoomAbacAttributeBodySchema,
PUTRoomAbacAttributeValuesBodySchema,
POSTAbacUsersSyncBodySchema,
GenericErrorSchema,
GETAbacRoomsListQueryValidator,
GETAbacRoomsResponseValidator,
GETAbacAuditEventsQuerySchema,
GETAbacAuditEventsResponseSchema,
GETAbacPdpHealthResponseSchema,
GETAbacPdpHealthErrorResponseSchema,
} from './schemas';
import { API } from '../../../../server/api';
import type { ExtractRoutesFromAPI } from '../../../../server/api/ApiClass';
import { getPaginationItems } from '../../../../server/api/lib/getPaginationItems';
import { settings } from '../../../../server/settings';
const getActorFromUser = (user?: IUser | null): AbacActor | undefined =>
user?._id
? {
_id: user._id,
username: user.username,
name: user.name,
}
: undefined;
const assertLocalAttributeStore = async (): Promise<void> => {
if (await Abac.isExternalAttributeStore()) {
throw new AbacAttributeStoreExternalError();
}
};
const abacEndpoints = API.v1
.post(
'abac/rooms/:rid/attributes',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-rooms'],
body: POSTRoomAbacAttributesBodySchema,
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
license: ['abac'],
},
async function action() {
const { rid } = this.urlParams;
const { attributes } = this.bodyParams;
if (!settings.get('ABAC_Enabled')) {
throw new Error('error-abac-not-enabled');
}
// This is a replace-all operation
// IF you need fine grained, use the other endpoints for removing, editing & adding single attributes
await Abac.setRoomAbacAttributes(rid, attributes, getActorFromUser(this.user));
return API.v1.success();
},
)
.delete(
'abac/rooms/:rid/attributes',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-rooms'],
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { rid } = this.urlParams;
// We don't need to check if ABAC is enabled to clear attributes
// Since we're always allowing this operation
// license check is also not required
await Abac.setRoomAbacAttributes(rid, {}, getActorFromUser(this.user));
return API.v1.success();
},
)
// add an abac attribute by key
.post(
'abac/rooms/:rid/attributes/:key',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-rooms'],
license: ['abac'],
body: POSTSingleRoomAbacAttributeBodySchema,
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { rid, key } = this.urlParams;
const { values } = this.bodyParams;
if (!settings.get('ABAC_Enabled')) {
throw new Error('error-abac-not-enabled');
}
await Abac.addRoomAbacAttributeByKey(rid, key, values, getActorFromUser(this.user));
return API.v1.success();
},
)
// edit a room attribute
.put(
'abac/rooms/:rid/attributes/:key',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-rooms'],
body: PUTRoomAbacAttributeValuesBodySchema,
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
license: ['abac'],
},
async function action() {
const { rid, key } = this.urlParams;
const { values } = this.bodyParams;
if (!settings.get('ABAC_Enabled')) {
throw new Error('error-abac-not-enabled');
}
await Abac.replaceRoomAbacAttributeByKey(rid, key, values, getActorFromUser(this.user));
return API.v1.success();
},
)
// delete a room attribute
.delete(
'abac/rooms/:rid/attributes/:key',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-rooms'],
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { rid, key } = this.urlParams;
await Abac.removeRoomAbacAttribute(rid, key, getActorFromUser(this.user));
return API.v1.success();
},
)
// attribute endpoints
// list attributes
.get(
'abac/attributes',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
query: GETAbacAttributesQuerySchema,
response: {
200: GETAbacAttributesResponseSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { offset, count } = await getPaginationItems(this.queryParams as Record<string, string | string[] | number | null | undefined>);
const { key, values } = this.queryParams;
return API.v1.success(
await Abac.listAbacAttributes(
{
key,
values,
offset,
count,
},
getActorFromUser(this.user),
),
);
},
)
.post(
'abac/users/sync',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
license: ['abac'],
body: POSTAbacUsersSyncBodySchema,
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
if (!settings.get('ABAC_Enabled')) {
throw new Error('error-abac-not-enabled');
}
const { usernames, ids, emails, ldapIds } = this.bodyParams;
await Abac.reevaluateUsers({ usernames, ids, emails, ldapIds });
return API.v1.success();
},
)
.post(
'abac/attributes',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
license: ['abac'],
body: POSTAbacAttributeDefinitionSchema,
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
if (!settings.get('ABAC_Enabled')) {
throw new Error('error-abac-not-enabled');
}
await assertLocalAttributeStore();
await Abac.addAbacAttribute(this.bodyParams, getActorFromUser(this.user));
return API.v1.success();
},
)
// update attribute definition (key and/or values)
.put(
'abac/attributes/:_id',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
license: ['abac'],
body: PUTAbacAttributeUpdateBodySchema,
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { _id } = this.urlParams;
if (!settings.get('ABAC_Enabled')) {
throw new Error('error-abac-not-enabled');
}
await assertLocalAttributeStore();
await Abac.updateAbacAttributeById(_id, this.bodyParams, getActorFromUser(this.user));
return API.v1.success();
},
)
// get single attribute with usage
.get(
'abac/attributes/:_id',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
response: {
200: GETAbacAttributeByIdResponseSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { _id } = this.urlParams;
await assertLocalAttributeStore();
const result = await Abac.getAbacAttributeById(_id, getActorFromUser(this.user));
return API.v1.success(result);
},
)
// delete attribute (only if not in use)
.delete(
'abac/attributes/:_id',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
response: {
200: GenericSuccessSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { _id } = this.urlParams;
await assertLocalAttributeStore();
await Abac.deleteAbacAttributeById(_id, getActorFromUser(this.user));
return API.v1.success();
},
)
// check if attribute is in use
.get(
'abac/attributes/:key/is-in-use',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
response: {
200: GETAbacAttributeIsInUseResponseSchema,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { key } = this.urlParams;
await assertLocalAttributeStore();
const inUse = await Abac.isAbacAttributeInUseByKey(key);
return API.v1.success({ inUse });
},
)
.get(
'abac/rooms',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-rooms'],
response: {
200: GETAbacRoomsResponseValidator,
401: validateUnauthorizedErrorResponse,
400: GenericErrorSchema,
403: validateUnauthorizedErrorResponse,
},
query: GETAbacRoomsListQueryValidator,
},
async function action() {
const { offset, count } = await getPaginationItems(this.queryParams as Record<string, string | string[] | number | null | undefined>);
const { filter, filterType } = this.queryParams;
const result = await Abac.listAbacRooms(
{
offset,
count,
filter,
filterType,
},
getActorFromUser(this.user),
);
return API.v1.success(result);
},
)
.get(
'abac/pdp/health',
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-settings'],
rateLimiterOptions: {
numRequestsAllowed: 5,
intervalTimeInMS: 60000,
},
response: {
200: GETAbacPdpHealthResponseSchema,
400: GETAbacPdpHealthErrorResponseSchema,
401: validateUnauthorizedErrorResponse,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
try {
await Abac.getPDPHealth();
return API.v1.success({ available: true, message: 'ABAC_PDP_Health_OK' });
} catch (err) {
return API.v1.failure({ available: false, message: getPdpHealthErrorCode(err) });
}
},
)
.get(
'abac/audit',
{
response: {
200: GETAbacAuditEventsResponseSchema,
400: GenericErrorSchema,
401: validateUnauthorizedErrorResponse,
403: validateUnauthorizedErrorResponse,
},
query: GETAbacAuditEventsQuerySchema,
authRequired: true,
permissionsRequired: ['abac-management', 'view-abac-admin-audit'],
license: ['abac', 'auditing'],
},
async function action() {
const { start, end, actor } = this.queryParams;
const { offset, count } = await getPaginationItems(this.queryParams as Record<string, string | number | null | undefined>);
const { sort } = await this.parseJsonQuery();
const _sort = { ts: sort?.ts ? sort?.ts : -1 };
const { cursor, totalCount } = ServerEvents.findPaginated(
{
...(actor && convertSubObjectsIntoPaths({ actor })),
ts: {
$gte: start ? new Date(start) : new Date(0),
$lte: end ? new Date(end) : new Date(),
},
t: {
$in: [
'abac.attribute.changed',
'abac.object.attribute.changed',
'abac.object.attributes.removed',
'abac.action.performed',
'abac.attribute.store.switched',
],
},
},
{
sort: _sort,
skip: offset,
limit: count,
allowDiskUse: true,
},
);
const [events, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
events: events as (
| IServerEvents['abac.action.performed']
| IServerEvents['abac.attribute.changed']
| IServerEvents['abac.object.attribute.changed']
| IServerEvents['abac.object.attributes.removed']
| IServerEvents['abac.attribute.store.switched']
)[],
count: events.length,
offset,
total,
});
},
);
export type AbacEndpoints = ExtractRoutesFromAPI<typeof abacEndpoints>;
declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends AbacEndpoints {}
}