deep-package-manager
Version:
DEEP Package Manager
360 lines (297 loc) • 8.26 kB
JavaScript
/**
* Created by AlexanderC on 2/16/16.
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LambdaProxyHandler = undefined;
var _deepCore = require('deep-core');
var _deepCore2 = _interopRequireDefault(_deepCore);
var _S3Driver = require('../../S3Driver');
var _ApiDriver = require('../../ApiDriver');
var _awsSdk = require('aws-sdk');
var _awsSdk2 = _interopRequireDefault(_awsSdk);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class LambdaProxyHandler extends _deepCore2.default.AWS.Lambda.Runtime {
/**
* @param {*} args
*/
constructor(...args) {
super(...args);
this._principalId = null;
}
/**
* @todo override it in lambda handler
* @example putObj,readObj,hasObj,deleteObj
*/
get _storageMethod() {
throw new Error(`You should override _storageMethod getter in your implementation
(allowed values: putObj, readObj, hasObj, deleteObj)`);
}
/**
* @todo override it in lambda handler
*
* @private
*/
get _principalRuleDbModelName() {
return 'RegistryPrincipalRule';
}
/**
* @todo override it in lambda handler
*
* @private
*/
get _principalDbFieldName() {
return 'PrincipalId';
}
/**
* @todo override it in lambda handler
*
* @private
*/
get _moduleDbFieldName() {
return 'AllowedModules';
}
/**
* @returns {Object}
* @private
*/
get _dbModel() {
return this.kernel.get('db').get(this._principalRuleDbModelName);
}
/**
* @returns {Cache|*}
* @private
*/
get _cache() {
return this.kernel.get('cache');
}
/**
* @returns {String}
* @private
*/
get _rulesCacheKey() {
return `deep-registry-${this._principalRuleDbModelName}-${this._principalDbFieldName}-${this._principalId}`;
}
/**
* @param {String} moduleName
* @param {Function} cb
* @private
*/
_isModuleOperationAllowed(moduleName, cb) {
// assuming no custom auth lambda triggered
if (this._principalId === 'ANON') {
cb(null, true);
return;
}
let cacheKey = this._rulesCacheKey;
this._cache.has(cacheKey, (error, entryExists) => {
if (error) {
cb(error, null);
return;
}
if (entryExists) {
this._cache.get(cacheKey, (error, rawData) => {
try {
cb(null, this._matchModuleOperation(moduleName, JSON.parse(rawData)));
} catch (error) {
// invalidate it async
this._cache.invalidate(cacheKey);
cb(error, null);
}
});
} else {
this._dbModel.findAllBy(this._principalDbFieldName, this._principalId, (error, data) => {
if (error) {
cb(error, null);
return;
}
let principalEntries = data.Items || [];
try {
// persist it async
this._cache.set(cacheKey, JSON.stringify(principalEntries), LambdaProxyHandler.CACHE_TTL);
} catch (error) {
console.debug('Unable to set cache: ', error);
}
cb(null, this._matchModuleOperation(moduleName, principalEntries));
});
}
});
}
/**
* @param {String} moduleName
* @param {Object[]} principalEntries
* @returns {Boolean}
* @private
*/
_matchModuleOperation(moduleName, principalEntries) {
for (let i in principalEntries) {
if (!principalEntries.hasOwnProperty(i)) {
continue;
}
let principalEntry = principalEntries[i];
let allowedModules = principalEntry.hasOwnProperty(this._moduleDbFieldName) ? principalEntry[principalEntry] || [] : [];
for (let j in allowedModules) {
if (!allowedModules.hasOwnProperty(j)) {
continue;
}
let rule = LambdaProxyHandler._parseModuleRule(allowedModules[j]);
if (LambdaProxyHandler._isOpModAllowed(moduleName, this._storageMethod, rule)) {
return true;
}
}
}
return false;
}
/**
* @param {String} mod
* @param {String} op
* @param {{module: *, operation: *}} rule
* @returns {Boolean}
* @private
*/
static _isOpModAllowed(mod, op, rule) {
let modMatched = rule.module === '*' || rule.module === mod;
let opMatched = rule.operation === '*' || rule.operation === LambdaProxyHandler.OP_RW || ['hasObj', 'readObj'].indexOf(op) !== -1 && rule.operation === LambdaProxyHandler.OP_R || ['putObj', 'deleteObj'].indexOf(op) !== -1 && rule.operation === LambdaProxyHandler.OP_W;
return modMatched && opMatched;
}
/**
* @param {String} rule
* @returns {{module: *, operation: *}}
* @private
*/
static _parseModuleRule(rule) {
let parts = rule.split(':');
let module = parts[0];
let rawOperation = parts.length > 1 ? parts[1] : '*';
let operation = null;
if (rawOperation === '*') {
operation = LambdaProxyHandler.OP_RW;
} else {
if (rawOperation.indexOf('r') !== -1) {
operation = LambdaProxyHandler.OP_R;
}
if (rawOperation.indexOf('w') !== -1) {
operation = operation ? LambdaProxyHandler.OP_RW : LambdaProxyHandler.OP_W;
}
}
return { module, operation };
}
/**
* @param {Object} requestData
*/
handle(requestData) {
this._principalId = requestData.principalId;
let proxyData = requestData.payload;
new _deepCore2.default.Runtime.Sandbox(() => {
let moduleName = LambdaProxyHandler._extractModuleNameFromObjPath(proxyData.objPath);
this._isModuleOperationAllowed(moduleName, (error, isAllowed) => {
if (error || !isAllowed) {
this.createResponse({
error: error || new Error(`Access denied on module '${moduleName}' for '${this._principalId}'/${this._storageMethod}`),
data: null
}).send();
return;
}
let storage = this._registryStorage;
let args = [proxyData.objPath];
if (proxyData.hasOwnProperty('data')) {
args.push(_ApiDriver.ApiDriver._decodeResponseData(this._storageMethod, proxyData.data));
}
args.push((error, data) => {
this.createResponse({
error,
data: _ApiDriver.ApiDriver._encodeResponseData(this._storageMethod, data)
}).send();
});
storage[this._storageMethod](...args);
});
}).fail(error => {
this.createResponse({
error,
data: null
}).send();
}).run();
}
/**
* @param {String} objPath
* @returns {String|null}
* @private
*/
static _extractModuleNameFromObjPath(objPath) {
let matches = objPath.match(/^\/?([^\/]+)(?:\/.*)?$/gi);
if (matches && matches.length === 2) {
return matches[1].toString();
}
return null;
}
/**
* @returns {String|null|*}
*/
get principalId() {
return this._principalId;
}
/**
* @returns {Function}
*/
get validationSchema() {
return Joi => {
return Joi.object().keys({
principalId: Joi.string().required(),
payload: Joi.object().keys({
objPath: Joi.string().required(),
data: Joi.string().optional()
})
});
};
}
/**
* @returns {S3RegistryStorage|S3Driver|*}
* @private
*/
get _registryStorage() {
let config = this._registryConfig;
let bucket = config.bucket || this.kernel.config.buckets.private.name;
let prefix = config.prefix || '';
return new _S3Driver.S3Driver(this._registryS3, bucket, prefix);
}
/**
* @returns {AWS.S3|*}
*/
get _registryS3() {
return new _awsSdk2.default.S3(this._registryConfig.aws);
}
/**
* @returns {Object|*}
* @private
*/
get _registryConfig() {
return this.kernel.microservice().parameters.registry || {};
}
/**
* @returns {String}
*/
static get OP_R() {
return 'read';
}
/**
* @returns {String}
*/
static get OP_W() {
return 'write';
}
/**
* @returns {String}
*/
static get OP_RW() {
return 'read/write';
}
/**
* @returns {Number}
*/
static get CACHE_TTL() {
return 60;
}
}
exports.LambdaProxyHandler = LambdaProxyHandler;