From 43882fa12cab609d316b2a6e8142b2ba7c138bb3 Mon Sep 17 00:00:00 2001 From: Wenjun Che Date: Tue, 2 May 2017 16:46:26 -0400 Subject: [PATCH] RUN-2959: query desktop owner settings from RVM (#122) * RUN-2959: Populate API policy from RVM in Message PreProcessor * RUN-2959: Populate API policy from RVM in Message PreProcessor * RUN-2959: Populate API policy from RVM in Message PreProcessor * RUN-2959: Populate API policy from RVM in Message PreProcessor --- .../api_handlers/api_policy_processor.ts | 160 ++++++++++++++++-- src/browser/core_state.js | 9 + 2 files changed, 156 insertions(+), 13 deletions(-) diff --git a/src/browser/api_protocol/api_handlers/api_policy_processor.ts b/src/browser/api_protocol/api_handlers/api_policy_processor.ts index a910bc7a7..7e9817781 100644 --- a/src/browser/api_protocol/api_handlers/api_policy_processor.ts +++ b/src/browser/api_protocol/api_handlers/api_policy_processor.ts @@ -8,7 +8,25 @@ Please contact OpenFin Inc. at sales@openfin.co to obtain a Commercial License. import {MessagePackage} from '../transport_strategy/api_transport_base'; const coreState = require('../../core_state'); const electronApp = require('electron').app; +const system = require('../../api/system').System; const apiProtocolBase = require('./api_protocol_base'); +const rvmBus = require('../../rvm/rvm_message_bus'); // retrieve permission setting from registry +const configUrlPermissionsMap : { [url: string]: any } = {}; // cached configUrl => permission object, retrieved from RVM + // if a configUrl is mapped to a boolean true, request to RVM is successful + // did not return permissions +const CONFIG_URL_WILDCARD = 'default'; // can set as default for all applications. Checked ONLY IF permissions + // for a particular URL is not defined +const ENABLE_DESKTOP_OWNER_SETTINGS: string = 'enable-desktop-owner-settings'; // RVM adds this to runtime->arguments + // if settings are detected in Registry +const DESKTOP_OWNER_SETTINGS_TIMEOUT: string = 'desktop-owner-settings-timeout'; // timeout for requesting from RVM in ms +let desktopOwnerSettingsTimeout: number = 2000; // in ms +let desktopOwnerSettingEnabled: boolean = false; + +enum POLICY_AUTH_RESULT { + Allowed = 1, + Denied, + NotDefined // config URL not found in policy. Check window options instead +} // actionAPINameMap has all APIs that must be authorized // @todo this map should be set in group policy or some other way @@ -84,7 +102,7 @@ function checkWindowPermissions(apiPath: [string], windowPermissions: any, isChi } level += 1; } - electronApp.vlog(1, `checkWindowPermissions level ${level} value ${lastValue}`); + electronApp.vlog(1, `checkWindowPermissions level ${level}`); if (level === apiPath.length && typeof lastValue === 'boolean') { permitted = !!lastValue; } @@ -93,19 +111,20 @@ function checkWindowPermissions(apiPath: [string], windowPermissions: any, isChi } /** - * Authorize the action for a window sending the action + * Authorize the action for a window sending the action based on window options * * @param windowOpts window options + * @param parentUuid uuid of parent app * @param action in message - * @returns {boolean} true if authorized + * @returns {Promise} resolves true if authorized */ -function authorizeAction(windowOpts: any, parentUuid: string, action: string): boolean { +function authorizeActionFromWindowOptions(windowOpts: any, parentUuid: string, action: string): boolean { electronApp.vlog(1, `authorizeAction ${action} for ${windowOpts.uuid} ${windowOpts.name}`); + const apiPath: [string] = actionAPINameMap[action]; let allowed: boolean = true; if (actionAPINameMap.hasOwnProperty(action)) { // if listed in the map, has to be checked const isChildWindow = windowOpts.uuid !== windowOpts.name; allowed = isChildWindow; - const apiPath: [string] = actionAPINameMap[action]; if (windowOpts && windowOpts.permissions) { allowed = checkWindowPermissions(apiPath, windowOpts.permissions, isChildWindow); } @@ -116,7 +135,8 @@ function authorizeAction(windowOpts: any, parentUuid: string, action: string): b const parentOpts = parentObject._options; if (parentOpts) { electronApp.vlog(1, `authorizeAction checks parent ${parentUuid}`); - allowed = authorizeAction(parentOpts, parentObject.parentUuid, action); + allowed = authorizeActionFromWindowOptions(parentOpts, parentObject.parentUuid, action); + return; } else { electronApp.vlog(1, `authorizeAction missing parent options ${parentUuid}`); } @@ -127,8 +147,58 @@ function authorizeAction(windowOpts: any, parentUuid: string, action: string): b return allowed; } +/** + * Authorize the action for a window sending the action based on policies supplied by RVM + * + * @param windowOpts + * @param action + * @returns {Promise} + * @returns {Promise} resolves with POLICY_AUTH_RESULT + */ +function authorizeActionFromPolicy(windowOpts: any, action: string): Promise { + electronApp.vlog(1, `authorizeActionFromPolicy ${action} for ${windowOpts.uuid} ${windowOpts.name}`); + const apiPath: [string] = actionAPINameMap[action]; + return new Promise((resolve, reject) => { + if (desktopOwnerSettingEnabled === true) { + const configUrl = coreState.getConfigUrlByUuid(windowOpts.uuid); + if (configUrl) { + electronApp.vlog(1, `authorizeActionFromPolicy checking with config url ${configUrl}`); + requestAppPermissions(configUrl).then((resultByUrl: any) => { + if (resultByUrl.permissions) { + resolve(checkWindowPermissions(apiPath, resultByUrl.permissions, false) ? + POLICY_AUTH_RESULT.Allowed : POLICY_AUTH_RESULT.Denied); + } else { // check default permissions defined with CONFIG_URL_WILDCARD + electronApp.vlog(1, `authorizeActionFromPolicy checking with RVM ${CONFIG_URL_WILDCARD}`); + requestAppPermissions(CONFIG_URL_WILDCARD).then((resultByDefault: any) => { + if (resultByDefault.permissions) { + resolve(checkWindowPermissions(apiPath, resultByDefault.permissions, false) ? POLICY_AUTH_RESULT.Allowed : + POLICY_AUTH_RESULT.Denied); + } else { + resolve(POLICY_AUTH_RESULT.NotDefined); // config URL not defined in policy + } + }).catch((error: any) => { + electronApp.vlog(1, `authorizeActionFromPolicy query for permissions failed ${CONFIG_URL_WILDCARD}`); + reject(false); + }); + } + }).catch((error: any) => { + electronApp.vlog(1, `authorizeActionFromPolicy query for permissions failed ${configUrl}`); + reject(false); + }); + } else { + electronApp.vlog(1, 'authorizeActionFromPolicy configUrl not defined'); + resolve(POLICY_AUTH_RESULT.NotDefined); // config URL not defined in policy + } + } else { + electronApp.vlog(1, `authorizeActionFromPolicy desktopOwnerSettingEnabled ${desktopOwnerSettingEnabled}`); + resolve(POLICY_AUTH_RESULT.NotDefined); // config URL not defined in policy + } + }); +} + /** * Message pre-processor + * * @param msg message package to check * @param next function to call if ok to proceed */ @@ -144,20 +214,84 @@ function apiPolicyPreProcessor(msg: MessagePackage, next: () => void): void { const appObject = coreState.getAppObjByUuid(identity.uuid); // parentUuid for child windows is uuid of the app const parentUuid = identity.uuid === identity.name ? appObject.parentUuid : identity.uuid; - if (!authorizeAction(coreState.getWindowOptionsById(originWindow.id), parentUuid, action)) { - electronApp.vlog(1, `apiPolicyPreProcessor rejecting ${action} from ${identity.uuid} ${identity.name}`); + authorizeActionFromPolicy(coreState.getWindowOptionsById(originWindow.id), action).then((result: POLICY_AUTH_RESULT) => { + if (result === POLICY_AUTH_RESULT.Allowed) { + next(); + } else if (result === POLICY_AUTH_RESULT.Denied) { + electronApp.vlog(1, `apiPolicyPreProcessor rejecting from policy ${action} from ${identity.uuid} ${identity.name}`); + nack('Rejected, action is not authorized'); + } else { + if (authorizeActionFromWindowOptions(coreState.getWindowOptionsById(originWindow.id), parentUuid, action)) { + next(); + } else { + electronApp.vlog(1, `apiPolicyPreProcessor rejecting from win opts ${action} from ${identity.uuid} ` + + `${identity.name}`); + nack('Rejected, action is not authorized'); + } + } + }).catch(() => { + electronApp.vlog(1, `apiPolicyPreProcessor rejecting from error ${action} from ${identity.uuid} ${identity.name}`); nack('Rejected, action is not authorized'); - return; - } + }); + } else { + electronApp.vlog(1, `apiPolicyPreProcessor missing origin window ${action} from ${identity.uuid} ${identity.name}`); + next(); } + } else { + next(); } + } else { + next(); } - next(); } -if (electronApp.getCommandLineArguments().includes('--enable-strict-api-permissions')) { - electronApp.log('info', 'Installing API policy PreProcessor'); +/** + * Get application permissions from cache or RVM + * + * @param configUrl url of startup manifest + * @returns {Promise} resolves with permissions defined in application assets; + * reject if request to RVM failed + */ +function requestAppPermissions(configUrl: string): Promise { + electronApp.vlog(1, `requestAppPermissions ${configUrl} `); + return new Promise((resolve, reject) => { + if (configUrlPermissionsMap[configUrl]) { + electronApp.vlog(1, `requestAppPermissions cached ${configUrl} `); + resolve(configUrlPermissionsMap[configUrl]); + } else { + rvmBus.send('application', { + action: 'get-desktop-owner-settings', + sourceUrl: configUrl + }, (rvmResponse: any) => { + electronApp.vlog(1, `requestAppPermissions from RVM ${JSON.stringify(rvmResponse)} `); + if (rvmResponse.payload && rvmResponse.payload.success === true && + rvmResponse.payload.payload) { + if (rvmResponse.payload.payload.permissions) { + configUrlPermissionsMap[configUrl] = {permissions: rvmResponse.payload.payload.permissions}; + // cache it + } else { + // if permissions is missing, startup URL is not defined in desktop-owner-settings. Not an error + configUrlPermissionsMap[configUrl] = {}; + } + resolve(configUrlPermissionsMap[configUrl]); + } else { + system.log('error', `requestAppPermissions from RVM failed ${JSON.stringify(rvmResponse)}`); + reject(rvmResponse); // false indicates request to RVM failed + } + }, desktopOwnerSettingsTimeout / 1000); + } + }); +} + +if (coreState.argo['enable-strict-api-permissions']) { + electronApp.log('info', `Installing API policy PreProcessor ${JSON.stringify(coreState.getStartManifest())}`); apiProtocolBase.getDefaultRequestHandler().addPreProcessor(apiPolicyPreProcessor); + desktopOwnerSettingEnabled = !!coreState.argo[ENABLE_DESKTOP_OWNER_SETTINGS]; + electronApp.vlog(1, `desktopOwnerSettingEnabled ${desktopOwnerSettingEnabled}`); + if (desktopOwnerSettingEnabled === true && coreState.argo[DESKTOP_OWNER_SETTINGS_TIMEOUT]) { + desktopOwnerSettingsTimeout = Number(coreState.argo[DESKTOP_OWNER_SETTINGS_TIMEOUT]); + electronApp.vlog(1, `desktopOwnerSettingsTimeout ${desktopOwnerSettingsTimeout}`); + } } export {apiPolicyPreProcessor}; diff --git a/src/browser/core_state.js b/src/browser/core_state.js index 22a982e5b..635445620 100644 --- a/src/browser/core_state.js +++ b/src/browser/core_state.js @@ -182,6 +182,14 @@ function getUuidBySourceUrl(sourceUrl) { return app && app.appObj && app.appObj.uuid; } +function getConfigUrlByUuid(uuid) { + let app = appByUuid(uuid); + while (app && app.appObj && app.appObj.parentUuid) { + app = appByUuid(app.appObj.parentUuid); + } + return app && app._configUrl; +} + function setAppObj(appId, appObj) { const app = getAppById(appId); @@ -506,6 +514,7 @@ module.exports = { getAppObj, getAppObjByUuid, getUuidBySourceUrl, + getConfigUrlByUuid, getAppRunningState, getAppRestartingState, getChildrenByApp,