Skip to content
This repository has been archived by the owner on Oct 25, 2023. It is now read-only.

feat: Allow to enable the access to the system shell via server features #38

Merged
merged 5 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions lib/commands/execute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { exec } from 'teen_process';
import _ from 'lodash';
import log from '../logger';
import { fs, tempDir } from 'appium-support';
import path from 'path';

const SYSTEM_SHELL_FEATURE = 'system_shell';

const commands = {};

/**
* @typedef {Object} ExecOptions
* @property {?string} interpreter - Full path to the command line interpreter binary.
* The current interpreter (`$SHELL`) or `/bin/bash` is used by default
* @property {?boolean} throwOnFail [false] - Whether to throw an exception if
* the given script has returned non-zero exit code
* @property {?number} timeout [20000] - The default timeout for the script execution.
* @property {?Object} env [process.env] - Additional environment variables for
* the shell script
*/

/**
* @typedef {Object} ExecResult
* @property {string} stdout - Script stdout
* @property {string} stderr - Script stderr
* @property {number} code - Script return code. It will never be other
* than zero if `throwOnFail` option is set to `true`
*/

/**
* Executes the given shell script if the `system_shell`
* server feature is enabled. The command blocks until
* the script finishes its execution or its timeout expires.
*
* @param {!string} script - The actual shell script to execute.
* This should be a valid script snippet.
* @param {?ExecOptions} args
* @return {ExecResult} - The result of the script execution
* @throws {Error} If there was a problem during command execution
*/
commands.execute = async function execute (script, args) {
this.ensureFeatureEnabled(SYSTEM_SHELL_FEATURE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we add this feature flag in readme like other modules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm sometimes being lazy with documenting all the stuff. I've tried to provide accurate docstrings in the source though ;)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mykola-mokhnach can you please provide an example of how to actually execute a simple shell command once this feature has been enabled when starting appium server? im unsure how to use with my driver? I'm using C# fyi. Appreciate any help. Thanks :)


if (!_.isEmpty(script)) {
log.errorAndThrow(`The 'script' argument cannot be empty`);
}

let opts = {};
if (_.isArray(args) && _.isPlainObject(args[0])) {
opts = args[0];
} else if (_.isPlainObject(args)) {
opts = args;
}
const {
interpreter = process.env.SHELL || '/bin/bash',
throwOnFail = false,
timeout,
env,
} = opts;

const tmpRoot = await tempDir.openDir();
try {
const tmpScriptPath = path.resolve(tmpRoot, 'appium.sh');
await fs.writeFile(tmpScriptPath, script, 'utf8');
log.debug(`Executing script using '${interpreter}' shell interpreter:`);
const execOpts = {};
if (_.isInteger(timeout)) {
log.debug(`- Timeout: ${timeout}ms`);
execOpts.timeout = timeout;
}
if (!_.isEmpty(env)) {
log.debug(`- Environment: ${JSON.stringify(env)}`);
execOpts.env = Object.assign({}, process.env, env);
}
log.debug(script);
// TODO: Add some perf measurement here?
const {stdout, stderr} = await exec(interpreter, [tmpScriptPath], execOpts);
return {
stdout,
stderr,
code: 0,
};
} catch (e) {
if (_.has(e, 'code')) {
const {stdout, stderr, code} = e;
// Do not throw if the script return code is not zero
log.debug(`The script has returned non-zero exit code ${code}`);
if (stderr) {
log.debug(`Stderr: ${stderr}`);
}
if (!throwOnFail) {
return {stdout, stderr, code};
}
}
throw e;
} finally {
await fs.rimraf(tmpRoot);
}
};

export { commands };
export default commands;
11 changes: 11 additions & 0 deletions lib/commands/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import executeCmds from './execute';

const commands = {};
Object.assign(
commands,
executeCmds,
// add other command types here
);

export { commands };
export default commands;
13 changes: 6 additions & 7 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { BaseDriver } from 'appium-base-driver';
import { system } from 'appium-support';
import { AppiumForMac, DEFAULT_A4M_HOST} from './appium-for-mac';
import logger from './logger';
import commands from './commands/index';
import _ from 'lodash';

/* eslint-disable no-useless-escape */
const NO_PROXY_LIST = [
Expand All @@ -14,6 +16,10 @@ class MacDriver extends BaseDriver {
super(opts, shouldValidateCaps);
this.jwpProxyActive = false;
this.opts.address = opts.address || DEFAULT_A4M_HOST;

for (let [cmd, fn] of _.toPairs(commands)) {
MacDriver.prototype[cmd] = fn;
}
}

async createSession (...args) {
Expand Down Expand Up @@ -57,13 +63,6 @@ class MacDriver extends BaseDriver {
await super.deleteSession();
}

async execute (script, args) {
if (!this.relaxedSecurityEnabled) {
logger.errorAndThrow(`Appium server must have relaxed security flag set in order to run any shell commands`);
}
return await this.a4mDriver.sendCommand(`/session/${this.sessionId}/execute`, 'POST', {script, args});
}

proxyActive () {
// we always have an active proxy to the AppiumForMac server
return true;
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
"dependencies": {
"@babel/runtime": "^7.0.0",
"appium-base-driver": "^5.0.0",
"appium-support": "^2.6.0",
"appium-support": "^2.36.0",
"asyncbox": "^2.3.1",
"bluebird": "^3.5.1",
"lodash": "^4.17.4",
"source-map-support": "^0.5.5",
"teen_process": "^1.7.0",
"teen_process": "^1.15.0",
"yargs": "^15.0.1"
},
"scripts": {
Expand Down