Skip to content

Commit

Permalink
Merge branch 'develop' into regression/horizontal-scroll-thread
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] committed Mar 8, 2023
2 parents 0f03230 + 1c7f20a commit 62972fa
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 25 deletions.
7 changes: 7 additions & 0 deletions apps/meteor/app/api/server/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,12 @@ settingsRegistry.addGroup('General', function () {
type: 'boolean',
public: true,
});

// Should enforce the permission on next Major and remove this setting
this.add('API_Apply_permission_view-outside-room_on_users-list', false, {
type: 'boolean',
public: true,
alert: 'This_is_a_deprecated_feature_alert',
});
});
});
4 changes: 4 additions & 0 deletions apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ API.v1.addRoute(
return API.v1.unauthorized();
}

if (settings.get('API_Apply_permission_view-outside-room_on_users-list') && !hasPermission(this.userId, 'view-outside-room')) {
return API.v1.unauthorized();
}

const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();

Expand Down
36 changes: 29 additions & 7 deletions apps/meteor/app/file-upload/server/lib/FileUpload.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Avatars, UserDataFiles, Uploads, Settings } from '@rocket.chat/models';
import { settings } from '../../../settings/server';
import Users from '../../../models/server/models/Users';
import Rooms from '../../../models/server/models/Rooms';
import Subscriptions from '../../../models/server/models/Subscriptions';
import { mime } from '../../../utils/lib/mimeTypes';
import { hasPermission } from '../../../authorization/server/functions/hasPermission';
import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom';
Expand Down Expand Up @@ -431,7 +432,7 @@ export const FileUpload = {
// console.log('upload finished ->', file);
},

requestCanAccessFiles({ headers = {}, query = {} }) {
requestCanAccessFiles({ headers = {}, query = {} }, file) {
if (!settings.get('FileUpload_ProtectFiles')) {
return true;
}
Expand All @@ -446,16 +447,37 @@ export const FileUpload = {
rc_room_type = cookie.get('rc_room_type', headers.cookie);
}

const isAuthorizedByCookies = rc_uid && rc_token && Users.findOneByIdAndLoginToken(rc_uid, rc_token);
const isAuthorizedByHeaders =
headers['x-user-id'] && headers['x-auth-token'] && Users.findOneByIdAndLoginToken(headers['x-user-id'], headers['x-auth-token']);
const isAuthorizedByRoom =
const isAuthorizedByRoom = () =>
rc_room_type && roomCoordinator.getRoomDirectives(rc_room_type)?.canAccessUploadedFile({ rc_uid, rc_rid, rc_token });
const isAuthorizedByJWT =
const isAuthorizedByJWT = () =>
settings.get('FileUpload_Enable_json_web_token_for_files') &&
token &&
isValidJWT(token, settings.get('FileUpload_json_web_token_secret_for_files'));
return isAuthorizedByCookies || isAuthorizedByHeaders || isAuthorizedByRoom || isAuthorizedByJWT;

if (isAuthorizedByRoom() || isAuthorizedByJWT()) {
return true;
}

const uid = rc_uid || headers['x-user-id'];
const authToken = rc_token || headers['x-auth-token'];

const user = uid && authToken && Users.findOneByIdAndLoginToken(uid, authToken, { fields: { _id: 1 } });

if (!user) {
return false;
}

if (!settings.get('FileUpload_Restrict_to_room_members') || !file?.rid) {
return true;
}

const subscription = Subscriptions.findOneByRoomIdAndUserId(file.rid, user._id, { fields: { _id: 1 } });

if (subscription) {
return true;
}

return false;
},
addExtensionTo(file) {
if (mime.lookup(file.name) === file.type) {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/file-upload/server/lib/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ WebApp.connectHandlers.use(FileUpload.getPath(), async function (req, res, next)
const file = await Uploads.findOneById(match[1]);

if (file) {
if (!FileUpload.requestCanAccessFiles(req)) {
if (!FileUpload.requestCanAccessFiles(req, file)) {
res.writeHead(403);
return res.end();
}
Expand Down
8 changes: 8 additions & 0 deletions apps/meteor/app/file-upload/server/startup/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ settingsRegistry.addGroup('FileUpload', function () {
i18nDescription: 'FileUpload_ProtectFilesDescription',
});

this.add('FileUpload_Restrict_to_room_members', false, {
type: 'boolean',
enableQuery: {
_id: 'FileUpload_ProtectFiles',
value: true,
},
});

this.add('FileUpload_RotateImages', true, {
type: 'boolean',
public: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { InputElementDispatchAction } from '@rocket.chat/apps-engine/definition/
import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer';
import { useDebouncedCallback, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { kitContext } from '@rocket.chat/fuselage-ui-kit';
import type { Block } from '@rocket.chat/ui-kit';
import type { Dispatch, SyntheticEvent, ContextType } from 'react';
import React, { memo, useState, useEffect, useReducer } from 'react';

Expand Down Expand Up @@ -46,7 +47,7 @@ const useValues = (view: IUIKitSurface): [any, Dispatch<any>] => {
}));

const initializer = useMutableCallback(() => {
const filterInputFields = (block: IBlock): boolean => {
const filterInputFields = (block: IBlock | Block): boolean => {
if (isInputBlock(block)) {
return true;
}
Expand All @@ -60,7 +61,7 @@ const useValues = (view: IUIKitSurface): [any, Dispatch<any>] => {
return false;
};

const mapElementToState = (block: IBlock): InputFieldStateTuple | InputFieldStateTuple[] => {
const mapElementToState = (block: IBlock | Block): InputFieldStateTuple | InputFieldStateTuple[] => {
if (isInputBlock(block)) {
const { element, blockId } = block;
return [element.actionId, { value: element.initialValue, blockId } as FieldState];
Expand Down
17 changes: 10 additions & 7 deletions apps/meteor/ee/server/apps/orchestrator.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,16 @@ export class AppServerOrchestrator {
.get()
// We reduce everything to a promise chain so it runs sequentially
.reduce(
(control, app) => control.then(async () => (await canEnableApp(app.getStorageItem())) && this.getManager().enable(app.getID())),
(control, app) =>
control.then(async () => {
const canEnable = await canEnableApp(app.getStorageItem());

if (canEnable) {
return this.getManager().loadOne(app.getID());
}

this._rocketchatLogger.warn(`App "${app.getInfo().name}" can't be enabled due to CE limits.`);
}),
Promise.resolve(),
);

Expand Down Expand Up @@ -320,9 +329,3 @@ settings.watch('Apps_Logs_TTL', (value) => {

model.resetTTLIndex(expireAfterSeconds);
});

Meteor.startup(function _appServerOrchestrator() {
Apps.initialize();

Apps.load();
});
9 changes: 9 additions & 0 deletions apps/meteor/ee/server/apps/startup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Meteor } from 'meteor/meteor';

import { Apps } from './orchestrator';

Meteor.startup(function _appServerOrchestrator() {
Apps.initialize();

Apps.load();
});
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/startup/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import '../apps';
import '../apps/startup';
import './audit';
import './deviceManagement';
import './engagementDashboard';
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@
"@react-pdf/renderer": "^3.1.3",
"@rocket.chat/agenda": "workspace:^",
"@rocket.chat/api-client": "workspace:^",
"@rocket.chat/apps-engine": "1.37.0-alpha.157",
"@rocket.chat/apps-engine": "1.37.0-alpha.164",
"@rocket.chat/cas-validate": "workspace:^",
"@rocket.chat/core-services": "workspace:^",
"@rocket.chat/core-typings": "workspace:^",
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@
"API_Allow_Infinite_Count_Description": "Should calls to the REST API be allowed to return everything in one call?",
"API_Analytics": "Analytics",
"API_CORS_Origin": "CORS Origin",
"API_Apply_permission_view-outside-room_on_users-list": "Apply permission `view-outside-room` to api `users.list`",
"API_Apply_permission_view-outside-room_on_users-list_Description": "Temporary setting to enforce permission. Will be removed on next Major release within the change to always enforce the permission",
"API_Default_Count": "Default Count",
"API_Default_Count_Description": "The default count for REST API results if the consumer did not provided any.",
"API_Drupal_URL": "Drupal Server URL",
Expand Down Expand Up @@ -2200,6 +2202,8 @@
"FileUpload_Disabled": "File uploads are disabled.",
"FileUpload_Enable_json_web_token_for_files": "Enable Json Web Tokens protection to file uploads",
"FileUpload_Enable_json_web_token_for_files_description": "Appends a JWT to uploaded files urls",
"FileUpload_Restrict_to_room_members": "Restrict files to rooms' members",
"FileUpload_Restrict_to_room_members_Description": "Restrict the access of files uploaded on rooms to the rooms' members only",
"FileUpload_Enabled": "File Uploads Enabled",
"FileUpload_Enabled_Direct": "File Uploads Enabled in Direct Messages ",
"FileUpload_Error": "File Upload Error",
Expand Down
36 changes: 36 additions & 0 deletions apps/meteor/tests/end-to-end/api/01-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@ describe('[Users]', function () {
describe('[/users.list]', () => {
let user;
let deactivatedUser;
let user2;
let user2Credentials;

before((done) => {
const createDeactivatedUser = async () => {
Expand Down Expand Up @@ -700,6 +702,19 @@ describe('[Users]', function () {

after((done) => clearCustomFields(done));

before(async () => {
user2 = await createTestUser();
user2Credentials = await loginTestUser(user2);
});

after(async () => {
await deleteTestUser(user2);
user2 = undefined;

await updatePermission('view-outside-room', ['admin', 'owner', 'moderator', 'user']);
await updateSetting('API_Apply_permission_view-outside-room_on_users-list', false);
});

it('should query all users in the system', (done) => {
request
.get(api('users.list'))
Expand Down Expand Up @@ -796,6 +811,27 @@ describe('[Users]', function () {
})
.end(done);
});

it('should query all users in the system when logged as normal user and `view-outside-room` not granted', async () => {
await updatePermission('view-outside-room', ['admin']);
await request
.get(api('users.list'))
.set(user2Credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
});
});

it('should not query users when logged as normal user, `view-outside-room` not granted and temp setting enabled', async () => {
await updatePermission('view-outside-room', ['admin']);
await updateSetting('API_Apply_permission_view-outside-room_on_users-list', true);

await request.get(api('users.list')).set(user2Credentials).expect('Content-Type', 'application/json').expect(403);
});
});

describe('[/users.setAvatar]', () => {
Expand Down
91 changes: 91 additions & 0 deletions apps/meteor/tests/end-to-end/api/09-rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,50 @@ import { sendSimpleMessage } from '../../data/chat.helper';
import { createUser } from '../../data/users.helper';
import { IS_EE } from '../../e2e/config/constants';

function createTestUser() {
return new Promise((resolve) => {
const username = `user.test.${Date.now()}`;
const email = `${username}@rocket.chat`;
request
.post(api('users.create'))
.set(credentials)
.send({ email, name: username, username, password, joinDefaultChannels: false })
.end((err, res) => resolve(res.body.user));
});
}

function loginTestUser(user) {
return new Promise((resolve, reject) => {
request
.post(api('login'))
.send({
user: user.username,
password,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
const userCredentials = {};
userCredentials['X-Auth-Token'] = res.body.data.authToken;
userCredentials['X-User-Id'] = res.body.data.userId;
resolve(userCredentials);
})
.end((err) => (err ? reject(err) : resolve()));
});
}

function deleteTestUser(user) {
return new Promise((resolve) => {
request
.post(api('users.delete'))
.set(credentials)
.send({
userId: user._id,
})
.end(resolve);
});
}

describe('[Rooms]', function () {
this.retries(0);

Expand Down Expand Up @@ -76,7 +120,24 @@ describe('[Rooms]', function () {

describe('/rooms.upload', () => {
let testChannel;
let user;
let userCredentials;

before(async () => {
user = await createTestUser();
userCredentials = await loginTestUser(user);
});

after(async () => {
await deleteTestUser(user);
user = undefined;

await updateSetting('FileUpload_Restrict_to_room_members', false);
await updateSetting('FileUpload_ProtectFiles', true);
});

const testChannelName = `channel.test.upload.${Date.now()}-${Math.random()}`;

it('create an channel', (done) => {
createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
testChannel = res.body.channel;
Expand Down Expand Up @@ -124,6 +185,8 @@ describe('[Rooms]', function () {
})
.end(done);
});

let fileUrl;
it('upload a file to room', (done) => {
request
.post(api(`rooms.upload/${testChannel._id}`))
Expand All @@ -138,9 +201,37 @@ describe('[Rooms]', function () {
expect(res.body).to.have.nested.property('message.rid', testChannel._id);
expect(res.body).to.have.nested.property('message.file._id', message.file._id);
expect(res.body).to.have.nested.property('message.file.type', message.file.type);
fileUrl = `/file-upload/${message.file._id}/${message.file.name}`;
})
.end(done);
});

it('should be able to get the file', async () => {
await request.get(fileUrl).set(credentials).expect('Content-Type', 'image/png').expect(200);
});

it('should be able to get the file when no access to the room', async () => {
await request.get(fileUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200);
});

it('should not be able to get the file when no access to the room if setting blocks', async () => {
await updateSetting('FileUpload_Restrict_to_room_members', true);
await request.get(fileUrl).set(userCredentials).expect(403);
});

it('should be able to get the file if member and setting blocks outside access', async () => {
await updateSetting('FileUpload_Restrict_to_room_members', true);
await request.get(fileUrl).set(credentials).expect('Content-Type', 'image/png').expect(200);
});

it('should not be able to get the file without credentials', async () => {
await request.get(fileUrl).attach('file', imgURL).expect(403);
});

it('should be able to get the file without credentials if setting allows', async () => {
await updateSetting('FileUpload_ProtectFiles', false);
await request.get(fileUrl).expect('Content-Type', 'image/png').expect(200);
});
});

describe('/rooms.favorite', () => {
Expand Down

0 comments on commit 62972fa

Please sign in to comment.