use resource pattern in apps routes

this makes it easy to implement access control in route handlers
This commit is contained in:
Girish Ramakrishnan
2020-03-29 17:11:10 -07:00
parent f1975d8f2b
commit 1da37b66d8
3 changed files with 754 additions and 845 deletions

View File

@@ -4,16 +4,16 @@ exports = module.exports = {
getApp: getApp,
getApps: getApps,
getAppIcon: getAppIcon,
installApp: installApp,
uninstallApp: uninstallApp,
restoreApp: restoreApp,
install: install,
uninstall: uninstall,
restore: restore,
importApp: importApp,
backupApp: backupApp,
updateApp: updateApp,
backup: backup,
update: update,
getLogs: getLogs,
getLogStream: getLogStream,
listBackups: listBackups,
repairApp: repairApp,
repair: repair,
setAccessRestriction: setAccessRestriction,
setLabel: setLabel,
@@ -31,16 +31,18 @@ exports = module.exports = {
setLocation: setLocation,
setDataDir: setDataDir,
stopApp: stopApp,
startApp: startApp,
restartApp: restartApp,
stop: stop,
start: start,
restart: restart,
exec: exec,
execWebSocket: execWebSocket,
cloneApp: cloneApp,
clone: clone,
uploadFile: uploadFile,
downloadFile: downloadFile
downloadFile: downloadFile,
load: load
};
var apps = require('../apps.js'),
@@ -55,16 +57,24 @@ var apps = require('../apps.js'),
util = require('util'),
WebSocket = require('ws');
function getApp(req, res, next) {
function load(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.get(req.params.id, function (error, app) {
apps.get(req.params.id, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, apps.removeInternalFields(app)));
req.resource = result;
next();
});
}
function getApp(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
next(new HttpSuccess(200, apps.removeInternalFields(req.resource)));
}
function getApps(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
@@ -78,16 +88,16 @@ function getApps(req, res, next) {
}
function getAppIcon(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
apps.getIconPath(req.params.id, { original: req.query.original }, function (error, iconPath) {
apps.getIconPath(req.resource, { original: req.query.original }, function (error, iconPath) {
if (error) return next(BoxError.toHttpError(error));
res.sendFile(iconPath);
});
}
function installApp(req, res, next) {
function install(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
const data = req.body;
@@ -151,11 +161,11 @@ function installApp(req, res, next) {
function setAccessRestriction(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
apps.setAccessRestriction(req.params.id, req.body.accessRestriction, auditSource.fromRequest(req), function (error) {
apps.setAccessRestriction(req.resource, req.body.accessRestriction, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -164,11 +174,11 @@ function setAccessRestriction(req, res, next) {
function setLabel(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.label !== 'string') return next(new HttpError(400, 'label must be a string'));
apps.setLabel(req.params.id, req.body.label, auditSource.fromRequest(req), function (error) {
apps.setLabel(req.resource, req.body.label, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -177,12 +187,12 @@ function setLabel(req, res, next) {
function setTags(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (!Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array'));
if (req.body.tags.some((t) => typeof t !== 'string')) return next(new HttpError(400, 'tags array must contain strings'));
apps.setTags(req.params.id, req.body.tags, auditSource.fromRequest(req), function (error) {
apps.setTags(req.resource, req.body.tags, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -191,11 +201,11 @@ function setTags(req, res, next) {
function setIcon(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (req.body.icon !== null && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon is null or a base-64 image string'));
apps.setIcon(req.params.id, req.body.icon, auditSource.fromRequest(req), function (error) {
apps.setIcon(req.resource, req.body.icon, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -204,11 +214,11 @@ function setIcon(req, res, next) {
function setMemoryLimit(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
apps.setMemoryLimit(req.params.id, req.body.memoryLimit, auditSource.fromRequest(req), function (error, result) {
apps.setMemoryLimit(req.resource, req.body.memoryLimit, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -217,11 +227,11 @@ function setMemoryLimit(req, res, next) {
function setCpuShares(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.cpuShares !== 'number') return next(new HttpError(400, 'cpuShares is not a number'));
apps.setCpuShares(req.params.id, req.body.cpuShares, auditSource.fromRequest(req), function (error, result) {
apps.setCpuShares(req.resource, req.body.cpuShares, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -230,11 +240,11 @@ function setCpuShares(req, res, next) {
function setAutomaticBackup(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
apps.setAutomaticBackup(req.params.id, req.body.enable, auditSource.fromRequest(req), function (error) {
apps.setAutomaticBackup(req.resource, req.body.enable, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -243,11 +253,11 @@ function setAutomaticBackup(req, res, next) {
function setAutomaticUpdate(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
apps.setAutomaticUpdate(req.params.id, req.body.enable, auditSource.fromRequest(req), function (error) {
apps.setAutomaticUpdate(req.resource, req.body.enable, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -256,13 +266,13 @@ function setAutomaticUpdate(req, res, next) {
function setReverseProxyConfig(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (req.body.robotsTxt !== null && typeof req.body.robotsTxt !== 'string') return next(new HttpError(400, 'robotsTxt is not a string'));
if (req.body.csp !== null && typeof req.body.csp !== 'string') return next(new HttpError(400, 'csp is not a string'));
apps.setReverseProxyConfig(req.params.id, req.body, auditSource.fromRequest(req), function (error) {
apps.setReverseProxyConfig(req.resource, req.body, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -271,14 +281,14 @@ function setReverseProxyConfig(req, res, next) {
function setCertificate(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (req.body.key !== null && typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string'));
if (req.body.cert !== null && typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
if (req.body.cert && !req.body.key) return next(new HttpError(400, 'key must be provided'));
if (!req.body.cert && req.body.key) return next(new HttpError(400, 'cert must be provided'));
apps.setCertificate(req.params.id, req.body, auditSource.fromRequest(req), function (error) {
apps.setCertificate(req.resource, req.body, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -287,12 +297,12 @@ function setCertificate(req, res, next) {
function setEnvironment(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (!req.body.env || typeof req.body.env !== 'object') return next(new HttpError(400, 'env must be an object'));
if (Object.keys(req.body.env).some((key) => typeof req.body.env[key] !== 'string')) return next(new HttpError(400, 'env must contain values as strings'));
apps.setEnvironment(req.params.id, req.body.env, auditSource.fromRequest(req), function (error, result) {
apps.setEnvironment(req.resource, req.body.env, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -301,11 +311,11 @@ function setEnvironment(req, res, next) {
function setDebugMode(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (req.body.debugMode !== null && typeof req.body.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object'));
apps.setDebugMode(req.params.id, req.body.debugMode, auditSource.fromRequest(req), function (error, result) {
apps.setDebugMode(req.resource, req.body.debugMode, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -314,12 +324,12 @@ function setDebugMode(req, res, next) {
function setMailbox(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (req.body.mailboxName !== null && typeof req.body.mailboxName !== 'string') return next(new HttpError(400, 'mailboxName must be a string'));
if (typeof req.body.mailboxDomain !== 'string') return next(new HttpError(400, 'mailboxDomain must be a string'));
apps.setMailbox(req.params.id, req.body.mailboxName, req.body.mailboxDomain, auditSource.fromRequest(req), function (error, result) {
apps.setMailbox(req.resource, req.body.mailboxName, req.body.mailboxDomain, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -328,7 +338,7 @@ function setMailbox(req, res, next) {
function setLocation(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.body.location !== 'string') return next(new HttpError(400, 'location must be string')); // location may be an empty string
if (!req.body.domain) return next(new HttpError(400, 'domain is required'));
@@ -343,7 +353,7 @@ function setLocation(req, res, next) {
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
apps.setLocation(req.params.id, req.body, auditSource.fromRequest(req), function (error, result) {
apps.setLocation(req.resource, req.body, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -352,20 +362,20 @@ function setLocation(req, res, next) {
function setDataDir(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (req.body.dataDir !== null && typeof req.body.dataDir !== 'string') return next(new HttpError(400, 'dataDir must be a string'));
apps.setDataDir(req.params.id, req.body.dataDir, auditSource.fromRequest(req), function (error, result) {
apps.setDataDir(req.resource, req.body.dataDir, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function repairApp(req, res, next) {
function repair(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
const data = req.body;
@@ -379,22 +389,22 @@ function repairApp(req, res, next) {
if (!data.dockerImage || typeof data.dockerImage !== 'string') return next(new HttpError(400, 'dockerImage must be a string'));
}
apps.repair(req.params.id, data, auditSource.fromRequest(req), function (error, result) {
apps.repair(req.resource, data, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function restoreApp(req, res, next) {
function restore(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var data = req.body;
if (!data.backupId || typeof data.backupId !== 'string') return next(new HttpError(400, 'backupId must be non-empty string'));
apps.restore(req.params.id, data.backupId, auditSource.fromRequest(req), function (error, result) {
apps.restore(req.resource, data.backupId, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -403,7 +413,7 @@ function restoreApp(req, res, next) {
function importApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var data = req.body;
@@ -425,16 +435,16 @@ function importApp(req, res, next) {
}
}
apps.importApp(req.params.id, data, auditSource.fromRequest(req), function (error, result) {
apps.importApp(req.resource, data, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function cloneApp(req, res, next) {
function clone(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var data = req.body;
@@ -445,66 +455,66 @@ function cloneApp(req, res, next) {
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
apps.clone(req.params.id, data, req.user, auditSource.fromRequest(req), function (error, result) {
apps.clone(req.resource, data, req.user, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(201, { id: result.id, taskId: result.taskId }));
});
}
function backupApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
function backup(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
apps.backup(req.params.id, function (error, result) {
apps.backup(req.resource, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function uninstallApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
function uninstall(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
apps.uninstall(req.params.id, auditSource.fromRequest(req), function (error, result) {
apps.uninstall(req.resource, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function startApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
function start(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
apps.start(req.params.id, auditSource.fromRequest(req), function (error, result) {
apps.start(req.resource, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function stopApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
function stop(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
apps.stop(req.params.id, auditSource.fromRequest(req), function (error, result) {
apps.stop(req.resource, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function restartApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
function restart(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
apps.restart(req.params.id, auditSource.fromRequest(req), function (error, result) {
apps.restart(req.resource, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function updateApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
function update(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
var data = req.body;
@@ -523,7 +533,7 @@ function updateApp(req, res, next) {
data.appStoreId = appStoreId;
data.manifest = manifest;
apps.update(req.params.id, req.body, auditSource.fromRequest(req), function (error, result) {
apps.update(req.resource, req.body, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -533,7 +543,7 @@ function updateApp(req, res, next) {
// this route is for streaming logs
function getLogStream(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number'));
@@ -548,7 +558,7 @@ function getLogStream(req, res, next) {
format: 'json'
};
apps.getLogs(req.params.id, options, function (error, logStream) {
apps.getLogs(req.resource, options, function (error, logStream) {
if (error) return next(BoxError.toHttpError(error));
res.writeHead(200, {
@@ -570,7 +580,7 @@ function getLogStream(req, res, next) {
}
function getLogs(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10;
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number'));
@@ -581,7 +591,7 @@ function getLogs(req, res, next) {
format: req.query.format || 'json'
};
apps.getLogs(req.params.id, options, function (error, logStream) {
apps.getLogs(req.resource, options, function (error, logStream) {
if (error) return next(BoxError.toHttpError(error));
res.writeHead(200, {
@@ -617,7 +627,7 @@ function demuxStream(stream, stdin) {
}
function exec(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var cmd = null;
if (req.query.cmd) {
@@ -633,7 +643,7 @@ function exec(req, res, next) {
var tty = req.query.tty === 'true' ? true : false;
apps.exec(req.params.id, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
apps.exec(req.resource, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
if (error) return next(BoxError.toHttpError(error));
if (req.headers['upgrade'] !== 'tcp') return next(new HttpError(404, 'exec requires TCP upgrade'));
@@ -655,7 +665,7 @@ function exec(req, res, next) {
}
function execWebSocket(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var cmd = null;
if (req.query.cmd) {
@@ -671,7 +681,7 @@ function execWebSocket(req, res, next) {
var tty = req.query.tty === 'true' ? true : false;
apps.exec(req.params.id, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
apps.exec(req.resource, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
if (error) return next(BoxError.toHttpError(error));
req.clearTimeout();
@@ -701,7 +711,7 @@ function execWebSocket(req, res, next) {
}
function listBackups(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
var page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));
@@ -709,7 +719,7 @@ function listBackups(req, res, next) {
var perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));
apps.listBackups(page, perPage, req.params.id, function (error, result) {
apps.listBackups(req.resource, page, perPage, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { backups: result }));
@@ -717,12 +727,12 @@ function listBackups(req, res, next) {
}
function uploadFile(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
if (!req.files.file) return next(new HttpError(400, 'file must be provided as multipart'));
apps.uploadFile(req.params.id, req.files.file.path, req.query.file, function (error) {
apps.uploadFile(req.resource, req.files.file.path, req.query.file, function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, {}));
@@ -730,11 +740,11 @@ function uploadFile(req, res, next) {
}
function downloadFile(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.resource, 'object');
if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
apps.downloadFile(req.params.id, req.query.file, function (error, stream, info) {
apps.downloadFile(req.resource, req.query.file, function (error, stream, info) {
if (error) return next(BoxError.toHttpError(error));
var headers = {