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
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
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
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
This commit is contained in:
159
apps/meteor/server/api/lib/parseJsonQuery.ts
Normal file
159
apps/meteor/server/api/lib/parseJsonQuery.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import ejson from 'ejson';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { clean } from './cleanQuery';
|
||||
import { isValidQuery } from './isValidQuery';
|
||||
import { isPlainObject } from '../../../lib/utils/isPlainObject';
|
||||
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { apiDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { API } from '../api';
|
||||
import type { GenericRouteExecutionContext } from '../definition';
|
||||
|
||||
const pathAllowConf = {
|
||||
'/api/v1/users.list': ['$or', '$regex', '$and'],
|
||||
'def': ['$or', '$and', '$regex'],
|
||||
};
|
||||
|
||||
export async function parseJsonQuery(api: GenericRouteExecutionContext): Promise<{
|
||||
sort: Record<string, 1 | -1>;
|
||||
/**
|
||||
* @deprecated To access "fields" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
|
||||
*/
|
||||
fields: Record<string, 0 | 1>;
|
||||
/**
|
||||
* @deprecated To access "query" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
|
||||
*/
|
||||
query: Record<string, unknown>;
|
||||
}> {
|
||||
const { userId = '', response, route, logger } = api;
|
||||
const isUsersRoute = route.includes('/v1/users.');
|
||||
const canViewFullOtherUserInfo = isUsersRoute && (await hasPermissionAsync(userId, 'view-full-other-user-info'));
|
||||
const params = isPlainObject(api.queryParams) ? api.queryParams : {};
|
||||
const queryFields = Array.isArray(api.queryFields) ? (api.queryFields as string[]) : [];
|
||||
const queryOperations = Array.isArray(api.queryOperations) ? (api.queryOperations as string[]) : [];
|
||||
|
||||
let sort;
|
||||
if (typeof params?.sort === 'string') {
|
||||
try {
|
||||
sort = JSON.parse(params.sort);
|
||||
Object.entries(sort).forEach(([key, value]) => {
|
||||
if (value !== 1 && value !== -1) {
|
||||
throw new Meteor.Error('error-invalid-sort-parameter', `Invalid sort parameter: ${key}`, {
|
||||
helperMethod: 'parseJsonQuery',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger.warn({
|
||||
msg: 'Invalid sort parameter provided',
|
||||
sort: params.sort,
|
||||
err: e,
|
||||
});
|
||||
throw new Meteor.Error('error-invalid-sort', `Invalid sort parameter provided: \"${params.sort}\"`, {
|
||||
helperMethod: 'parseJsonQuery',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isUnsafeQueryParamsAllowed = process.env.ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS?.toUpperCase() === 'TRUE';
|
||||
const messageGenerator = ({ endpoint, version, parameter }: { endpoint: string; version: string; parameter: string }): string =>
|
||||
`The usage of the "${parameter}" parameter in endpoint "${endpoint}" breaks the security of the API and can lead to data exposure. It has been deprecated and will be removed in the version ${version}.`;
|
||||
|
||||
let fields: Record<string, 0 | 1> | undefined;
|
||||
if (typeof params?.fields === 'string' && isUnsafeQueryParamsAllowed) {
|
||||
try {
|
||||
apiDeprecationLogger.parameter(route, 'fields', '9.0.0', response, messageGenerator);
|
||||
fields = JSON.parse(params.fields) as Record<string, 0 | 1>;
|
||||
Object.entries(fields).forEach(([key, value]) => {
|
||||
if (value !== 1 && value !== 0) {
|
||||
throw new Meteor.Error('error-invalid-sort-parameter', `Invalid fields parameter: ${key}`, {
|
||||
helperMethod: 'parseJsonQuery',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger.warn({
|
||||
msg: 'Invalid fields parameter provided',
|
||||
fields: params.fields,
|
||||
err: e,
|
||||
});
|
||||
throw new Meteor.Error('error-invalid-fields', `Invalid fields parameter provided: \"${params.fields}\"`, {
|
||||
helperMethod: 'parseJsonQuery',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the user's selected fields only contains ones which their role allows
|
||||
if (typeof fields === 'object') {
|
||||
let nonSelectableFields = Object.keys(API.v1.defaultFieldsToExclude);
|
||||
if (isUsersRoute) {
|
||||
nonSelectableFields = nonSelectableFields.concat(
|
||||
Object.keys(canViewFullOtherUserInfo ? API.v1.limitedUserFieldsToExcludeIfIsPrivilegedUser : API.v1.limitedUserFieldsToExclude),
|
||||
);
|
||||
}
|
||||
|
||||
Object.keys(fields).forEach((k) => {
|
||||
if (nonSelectableFields.includes(k) || nonSelectableFields.includes(k.split(API.v1.fieldSeparator)[0])) {
|
||||
fields && delete fields[k as keyof typeof fields];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Limit the fields by default
|
||||
fields = Object.assign({}, fields, API.v1.defaultFieldsToExclude);
|
||||
if (isUsersRoute) {
|
||||
if (canViewFullOtherUserInfo) {
|
||||
fields = Object.assign(fields, API.v1.limitedUserFieldsToExcludeIfIsPrivilegedUser);
|
||||
} else {
|
||||
fields = Object.assign(fields, API.v1.limitedUserFieldsToExclude);
|
||||
}
|
||||
}
|
||||
|
||||
let query: Record<string, any> = {};
|
||||
if (typeof params?.query === 'string' && isUnsafeQueryParamsAllowed) {
|
||||
apiDeprecationLogger.parameter(route, 'query', '9.0.0', response, messageGenerator);
|
||||
try {
|
||||
query = ejson.parse(params.query);
|
||||
query = clean(query, pathAllowConf.def);
|
||||
} catch (e) {
|
||||
logger.warn({
|
||||
msg: 'Invalid query parameter provided',
|
||||
query: params.query,
|
||||
err: e,
|
||||
});
|
||||
throw new Meteor.Error('error-invalid-query', `Invalid query parameter provided: \"${params.query}\"`, {
|
||||
helperMethod: 'parseJsonQuery',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the user has permission to query the fields they are
|
||||
if (typeof query === 'object') {
|
||||
let nonQueryableFields = Object.keys(API.v1.defaultFieldsToExclude);
|
||||
|
||||
if (isUsersRoute) {
|
||||
if (canViewFullOtherUserInfo) {
|
||||
nonQueryableFields = nonQueryableFields.concat(Object.keys(API.v1.limitedUserFieldsToExcludeIfIsPrivilegedUser));
|
||||
} else {
|
||||
nonQueryableFields = nonQueryableFields.concat(Object.keys(API.v1.limitedUserFieldsToExclude));
|
||||
}
|
||||
}
|
||||
|
||||
const containsQueryFields = queryFields.length > 0;
|
||||
if (containsQueryFields && !isValidQuery(query, queryFields, queryOperations ?? pathAllowConf.def)) {
|
||||
throw new Meteor.Error('error-invalid-query', isValidQuery.errors.join('\n'));
|
||||
}
|
||||
|
||||
Object.keys(query).forEach((k) => {
|
||||
if (nonQueryableFields.includes(k) || nonQueryableFields.includes(k.split(API.v1.fieldSeparator)[0])) {
|
||||
query && delete query[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sort,
|
||||
fields,
|
||||
query,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user