diff --git a/.changeset/tough-eggs-camp.md b/.changeset/tough-eggs-camp.md new file mode 100644 index 0000000000000..10616da2b6675 --- /dev/null +++ b/.changeset/tough-eggs-camp.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/apps-engine": minor +--- + +Enables specifying hidden settings that are enabled to be accessed through the apps-engine in the permission list. diff --git a/apps/meteor/app/apps/server/bridges/settings.ts b/apps/meteor/app/apps/server/bridges/settings.ts index b3e6aaff32a47..5a5b1d902d4a9 100644 --- a/apps/meteor/app/apps/server/bridges/settings.ts +++ b/apps/meteor/app/apps/server/bridges/settings.ts @@ -1,4 +1,5 @@ -import type { IAppServerOrchestrator } from '@rocket.chat/apps'; +import { Apps, type IAppServerOrchestrator } from '@rocket.chat/apps'; +import type { IReadSettingPermission } from '@rocket.chat/apps-engine/definition/permissions/IPermission'; import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { ServerSettingBridge } from '@rocket.chat/apps-engine/server/bridges/ServerSettingBridge'; import { Settings } from '@rocket.chat/models'; @@ -21,11 +22,12 @@ export class AppSettingBridge extends ServerSettingBridge { protected async getOneById(id: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is getting the setting by id ${id}.`); - if (!(await this.isReadableById(id, appId))) { + const setting = await this.getReadableSettingById(id, appId); + if (!setting) { throw new Error(`The setting "${id}" is not readable.`); } - return this.orch.getConverters()?.get('settings').convertById(id); + return setting; } protected async hideGroup(name: string, appId: string): Promise { @@ -50,6 +52,39 @@ export class AppSettingBridge extends ServerSettingBridge { return Boolean(setting && !setting.secret); } + protected async getReadableSettingById(id: string, appId: string): Promise { + this.orch.debugLog(`The app ${appId} is checking if it can read the setting ${id}`); + const app = Apps.self?.getManager().getOneById(appId); + if (!app) { + this.orch.debugLog(`The app ${appId} is not found.`); + return null; + } + + const { permissions } = app.getInfo(); + if (!permissions) { + this.orch.debugLog(`The app ${appId} has no configured permissions.`); + return null; + } + + const readSettingsPermission = permissions.find((perm) => perm.name === 'server-setting.read'); + if (!readSettingsPermission) { + this.orch.debugLog(`The app ${appId} has no server-setting.read permission.`); + return null; + } + + const readSettings = readSettingsPermission as IReadSettingPermission; + // If the setting is in the hiddenSettings list (defined within the permission), then it can bypass the hidden flag. + // If not, then it must be a non-hidden setting. This is to allow apps to read hidden settings if they have the permission to do so. + const setting = readSettings.hiddenSettings?.includes(id) ? await Settings.findOneById(id) : await Settings.findOneNotHiddenById(id); + + if (!setting) { + this.orch.debugLog(`The setting ${id} is not found.`); + return null; + } + + return this.orch.getConverters()?.get('settings').convertToApp(setting); + } + protected async updateOne(setting: ISetting & { id: string }, appId: string): Promise { this.orch.debugLog(`The App ${appId} is updating the setting ${setting.id} .`); diff --git a/apps/meteor/app/apps/server/converters/settings.js b/apps/meteor/app/apps/server/converters/settings.js index da3e075deb678..07b790cb7c592 100644 --- a/apps/meteor/app/apps/server/converters/settings.js +++ b/apps/meteor/app/apps/server/converters/settings.js @@ -7,7 +7,7 @@ export class AppSettingsConverter { } async convertById(settingId) { - const setting = await Settings.findOneNotHiddenById(settingId); + const setting = await Settings.findOneById(settingId); return this.convertToApp(setting); } diff --git a/apps/meteor/server/settings/setup-wizard.ts b/apps/meteor/server/settings/setup-wizard.ts index 13d76a6070d92..a63b4cfe49c66 100644 --- a/apps/meteor/server/settings/setup-wizard.ts +++ b/apps/meteor/server/settings/setup-wizard.ts @@ -1223,6 +1223,13 @@ export const createSetupWSettings = () => secret: true, }); + await this.add('Omnigateway_Url', 'https://omni-gateway.rocket.chat', { + type: 'string', + hidden: true, + secret: true, + readonly: true, + }); + await this.add('Cloud_Service_Agree_PrivacyTerms', false, { type: 'boolean', }); diff --git a/packages/apps-engine/src/definition/permissions/IPermission.ts b/packages/apps-engine/src/definition/permissions/IPermission.ts index af36c2f63624a..8618d40afeef5 100644 --- a/packages/apps-engine/src/definition/permissions/IPermission.ts +++ b/packages/apps-engine/src/definition/permissions/IPermission.ts @@ -10,3 +10,7 @@ export interface INetworkingPermission extends IPermission { export interface IWorkspaceTokenPermission extends IPermission { scopes: Array; } + +export interface IReadSettingPermission extends IPermission { + hiddenSettings: Array; +} diff --git a/packages/apps-engine/src/server/permissions/AppPermissions.ts b/packages/apps-engine/src/server/permissions/AppPermissions.ts index fe368668b32be..80f74838b95aa 100644 --- a/packages/apps-engine/src/server/permissions/AppPermissions.ts +++ b/packages/apps-engine/src/server/permissions/AppPermissions.ts @@ -1,4 +1,4 @@ -import type { INetworkingPermission, IPermission, IWorkspaceTokenPermission } from '../../definition/permissions/IPermission'; +import type { INetworkingPermission, IPermission, IReadSettingPermission, IWorkspaceTokenPermission } from '../../definition/permissions/IPermission'; /** * @description @@ -33,7 +33,7 @@ export const AppPermissions = { registerButtons: { name: 'ui.registerButtons' }, }, setting: { - read: { name: 'server-setting.read' }, + read: { name: 'server-setting.read', hiddenSettings: [] } as IReadSettingPermission, write: { name: 'server-setting.write' }, }, room: {