Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #552 from RocketChat/release/1.35.0
Browse files Browse the repository at this point in the history
  • Loading branch information
d-gubert authored Oct 13, 2022
2 parents d4c479f + d720d6d commit 5b1a885
Show file tree
Hide file tree
Showing 32 changed files with 676 additions and 243 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ jspm_packages
/lib

.DS_Store
.idea/
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tabWidth": 4,
"useTabs": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 160
}
23 changes: 22 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/apps-engine",
"version": "1.34.0",
"version": "1.35.0",
"description": "The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.",
"main": "index",
"typings": "index",
Expand Down Expand Up @@ -91,7 +91,8 @@
"lodash.clonedeep": "^4.5.0",
"semver": "^5.7.1",
"stack-trace": "0.0.10",
"uuid": "^3.4.0"
"uuid": "^3.4.0",
"vm2": "^3.9.11"
},
"nyc": {
"include": [
Expand Down
24 changes: 24 additions & 0 deletions src/definition/accessors/IRoomRead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,28 @@ export interface IRoomRead {
* @returns the room
*/
getDirectByUsernames(usernames: Array<string>): Promise<IRoom>;

/**
* Get a list of the moderators of a given room
*
* @param roomId the room's id
* @returns a list of the users with the moderator role in the room
*/
getModerators(roomId: string): Promise<Array<IUser>>;

/**
* Get a list of the owners of a given room
*
* @param roomId the room's id
* @returns a list of the users with the owner role in the room
*/
getOwners(roomId: string): Promise<Array<IUser>>;

/**
* Get a list of the leaders of a given room
*
* @param roomId the room's id
* @returns a list of the users with the leader role in the room
*/
getLeaders(roomId: string): Promise<Array<IUser>>;
}
2 changes: 1 addition & 1 deletion src/definition/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/apps-ts-definition",
"version": "1.33.0-alpha",
"version": "1.35.0-alpha",
"description": "Contains the TypeScript definitions for the Rocket.Chat Applications.",
"main": "index.js",
"typings": "index",
Expand Down
3 changes: 2 additions & 1 deletion src/server/AppManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { IMarketplaceInfo } from './marketplace';
import { DisabledApp } from './misc/DisabledApp';
import { defaultPermissions } from './permissions/AppPermissions';
import { ProxiedApp } from './ProxiedApp';
import { AppsEngineEmptyRuntime } from './runtime/AppsEngineEmptyRuntime';
import { AppLogStorage, AppMetadataStorage, IAppStorageItem } from './storage';
import { AppSourceStorage } from './storage/AppSourceStorage';

Expand Down Expand Up @@ -240,7 +241,7 @@ export class AppManager {
app.getLogger().error(e);
this.logStorage.storeEntries(app.getID(), app.getLogger());

const prl = new ProxiedApp(this, item, app, () => '');
const prl = new ProxiedApp(this, item, app, new AppsEngineEmptyRuntime(app));
this.apps.set(item.id, prl);
aff.setApp(prl);
}
Expand Down
39 changes: 15 additions & 24 deletions src/server/ProxiedApp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as vm from 'vm';

import { IAppAccessors, ILogger } from '../definition/accessors';
import { App } from '../definition/App';
import { AppStatus } from '../definition/AppStatus';
Expand All @@ -10,23 +8,27 @@ import { AppManager } from './AppManager';
import { NotEnoughMethodArgumentsError } from './errors';
import { AppConsole } from './logging';
import { AppLicenseValidationResult } from './marketplace/license';
import { Utilities } from './misc/Utilities';
import { AppsEngineRuntime } from './runtime/AppsEngineRuntime';
import { IAppStorageItem } from './storage';

export const ROCKETCHAT_APP_EXECUTION_PREFIX = '$RocketChat_App$';

export class ProxiedApp implements IApp {
private previousStatus: AppStatus;

private latestLicenseValidationResult: AppLicenseValidationResult;

constructor(private readonly manager: AppManager,
private storageItem: IAppStorageItem,
private readonly app: App,
private readonly customRequire: (mod: string) => {}) {
constructor(
private readonly manager: AppManager,
private storageItem: IAppStorageItem,
private readonly app: App,
private readonly runtime: AppsEngineRuntime,
) {
this.previousStatus = storageItem.status;
}

public getRuntime(): AppsEngineRuntime {
return this.runtime;
}

public getApp(): App {
return this.app;
}
Expand All @@ -51,12 +53,6 @@ export class ProxiedApp implements IApp {
return typeof (this.app as any)[method] === 'function';
}

public makeContext(data: object): vm.Context {
return Utilities.buildDefaultAppContext(Object.assign({}, {
require: this.customRequire,
}, data));
}

public setupLogger(method: AppMethod): AppConsole {
const logger = new AppConsole(method);
// Set the logger to our new one
Expand All @@ -65,13 +61,6 @@ export class ProxiedApp implements IApp {
return logger;
}

public runInContext(codeToRun: string, context: vm.Context): any {
return vm.runInContext(codeToRun, context, {
timeout: 1000,
filename: `${ ROCKETCHAT_APP_EXECUTION_PREFIX }_${ this.getName() }.ts`,
});
}

public async call(method: AppMethod, ...args: Array<any>): Promise<any> {
if (typeof (this.app as any)[method] !== 'function') {
throw new Error(`The App ${this.app.getName()} (${this.app.getID()}`
Expand All @@ -89,8 +78,10 @@ export class ProxiedApp implements IApp {

let result;
try {
// tslint:disable-next-line:max-line-length
result = await this.runInContext(`app.${method}.apply(app, args)`, this.makeContext({ app: this.app, args })) as Promise<any>;
result = await this.runtime.runInSandbox(
`module.exports = app.${method}.apply(app, args)`,
{ app: this.app, args },
);
logger.debug(`'${method}' was successfully called! The result is:`, result);
} catch (e) {
logger.error(e);
Expand Down
14 changes: 13 additions & 1 deletion src/server/accessors/RoomRead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IUser } from '../../definition/users';
import { RoomBridge } from '../bridges';

export class RoomRead implements IRoomRead {
constructor(private roomBridge: RoomBridge, private appId: string) { }
constructor(private roomBridge: RoomBridge, private appId: string) {}

public getById(id: string): Promise<IRoom> {
return this.roomBridge.doGetById(id, this.appId);
Expand Down Expand Up @@ -35,4 +35,16 @@ export class RoomRead implements IRoomRead {
public getDirectByUsernames(usernames: Array<string>): Promise<IRoom> {
return this.roomBridge.doGetDirectByUsernames(usernames, this.appId);
}

public getModerators(roomId: string): Promise<Array<IUser>> {
return this.roomBridge.doGetModerators(roomId, this.appId);
}

public getOwners(roomId: string): Promise<Array<IUser>> {
return this.roomBridge.doGetOwners(roomId, this.appId);
}

public getLeaders(roomId: string): Promise<Array<IUser>> {
return this.roomBridge.doGetLeaders(roomId, this.appId);
}
}
59 changes: 47 additions & 12 deletions src/server/bridges/RoomBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ export abstract class RoomBridge extends BaseBridge {
}
}

public async doCreateDiscussion(room: IRoom, parentMessage: IMessage | undefined,
reply: string | undefined, members: Array<string>, appId: string): Promise<string> {
public async doCreateDiscussion(
room: IRoom,
parentMessage: IMessage | undefined,
reply: string | undefined,
members: Array<string>,
appId: string,
): Promise<string> {
if (this.hasWritePermission(appId)) {
return this.createDiscussion(room, parentMessage, reply, members, appId);
}
Expand All @@ -68,6 +73,24 @@ export abstract class RoomBridge extends BaseBridge {
}
}

public async doGetModerators(roomId: string, appId: string): Promise<Array<IUser>> {
if (this.hasReadPermission(appId)) {
return this.getModerators(roomId, appId);
}
}

public async doGetOwners(roomId: string, appId: string): Promise<Array<IUser>> {
if (this.hasReadPermission(appId)) {
return this.getOwners(roomId, appId);
}
}

public async doGetLeaders(roomId: string, appId: string): Promise<Array<IUser>> {
if (this.hasReadPermission(appId)) {
return this.getLeaders(roomId, appId);
}
}

protected abstract create(room: IRoom, members: Array<string>, appId: string): Promise<string>;
protected abstract getById(roomId: string, appId: string): Promise<IRoom>;
protected abstract getByName(roomName: string, appId: string): Promise<IRoom>;
Expand All @@ -76,19 +99,29 @@ export abstract class RoomBridge extends BaseBridge {
protected abstract getDirectByUsernames(usernames: Array<string>, appId: string): Promise<IRoom | undefined>;
protected abstract getMembers(roomId: string, appId: string): Promise<Array<IUser>>;
protected abstract update(room: IRoom, members: Array<string>, appId: string): Promise<void>;
protected abstract createDiscussion(room: IRoom, parentMessage: IMessage | undefined,
reply: string | undefined, members: Array<string>, appId: string): Promise<string>;
protected abstract createDiscussion(
room: IRoom,
parentMessage: IMessage | undefined,
reply: string | undefined,
members: Array<string>,
appId: string,
): Promise<string>;
protected abstract delete(room: string, appId: string): Promise<void>;
protected abstract getModerators(roomId: string, appId: string): Promise<Array<IUser>>;
protected abstract getOwners(roomId: string, appId: string): Promise<Array<IUser>>;
protected abstract getLeaders(roomId: string, appId: string): Promise<Array<IUser>>;

private hasWritePermission(appId: string): boolean {
if (AppPermissionManager.hasPermission(appId, AppPermissions.room.write)) {
return true;
}

AppPermissionManager.notifyAboutError(new PermissionDeniedError({
appId,
missingPermissions: [AppPermissions.room.write],
}));
AppPermissionManager.notifyAboutError(
new PermissionDeniedError({
appId,
missingPermissions: [AppPermissions.room.write],
}),
);

return false;
}
Expand All @@ -98,10 +131,12 @@ export abstract class RoomBridge extends BaseBridge {
return true;
}

AppPermissionManager.notifyAboutError(new PermissionDeniedError({
appId,
missingPermissions: [AppPermissions.room.read],
}));
AppPermissionManager.notifyAboutError(
new PermissionDeniedError({
appId,
missingPermissions: [AppPermissions.room.read],
}),
);

return false;
}
Expand Down
32 changes: 16 additions & 16 deletions src/server/compiler/AppCompiler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as path from 'path';
import * as vm from 'vm';

import { App } from '../../definition/App';
import { AppMethod } from '../../definition/metadata';
import { AppAccessors } from '../accessors';
import { AppManager } from '../AppManager';
import { MustContainFunctionError, MustExtendAppError } from '../errors';
import { MustContainFunctionError } from '../errors';
import { AppConsole } from '../logging';
import { Utilities } from '../misc/Utilities';
import { ProxiedApp } from '../ProxiedApp';
import { getRuntime } from '../runtime';
import { buildCustomRequire } from '../runtime/require';
import { IAppStorageItem } from '../storage';
import { IParseAppPackageResult } from './IParseAppPackageResult';

Expand All @@ -29,31 +29,30 @@ export class AppCompiler {
`Could not find the classFile (${ storage.info.classFile }) file.`);
}

const exports = {};
const customRequire = Utilities.buildCustomRequire(files, storage.info.id);
const context = Utilities.buildDefaultAppContext({ require: customRequire, exports, process: {}, console });
const Runtime = getRuntime();

const script = new vm.Script(files[path.normalize(storage.info.classFile)]);
const result = script.runInContext(context);
const customRequire = buildCustomRequire(files, storage.info.id);
const result = Runtime.runCode(files[path.normalize(storage.info.classFile)], {
require: customRequire,
});

if (typeof result !== 'function') {
// tslint:disable-next-line:max-line-length
throw new Error(`The App's main class for ${ storage.info.name } is not valid ("${ storage.info.classFile }").`);
}

const appAccessors = new AppAccessors(manager, storage.info.id);
const logger = new AppConsole(AppMethod._CONSTRUCTOR);
const rl = vm.runInNewContext('new App(info, rcLogger, appAccessors);', Utilities.buildDefaultAppContext({
const rl = Runtime.runCode('exports.app = new App(info, rcLogger, appAccessors);', {
rcLogger: logger,
info: storage.info,
App: result,
process: {},
appAccessors,
}), { timeout: 1000, filename: `App_${ storage.info.nameSlug }.js` });
}, { timeout: 1000, filename: `App_${ storage.info.nameSlug }.js` });

if (!(rl instanceof App)) {
throw new MustExtendAppError();
}
// TODO: app is importing the Class App internally so it's not same object to compare. Need to find a way to make this test
// if (!(rl instanceof App)) {
// throw new MustExtendAppError();
// }

if (typeof rl.getName !== 'function') {
throw new MustContainFunctionError(storage.info.classFile, 'getName');
Expand All @@ -79,7 +78,8 @@ export class AppCompiler {
throw new MustContainFunctionError(storage.info.classFile, 'getRequiredApiVersion');
}

const app = new ProxiedApp(manager, storage, rl as App, customRequire);
// TODO: Fix this type cast from to any to the right one
const app = new ProxiedApp(manager, storage, rl as App, new Runtime(rl as App, customRequire as any));

manager.getLogStorage().storeEntries(app.getID(), logger);

Expand Down
Loading

0 comments on commit 5b1a885

Please sign in to comment.