jsharmony-cms
Version:
929 lines (790 loc) • 38 kB
JavaScript
/*
Copyright 2021 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var Helper = require('jsharmony/Helper');
var HelperFS = require('jsharmony/HelperFS');
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var async = require('async');
var crypto = require('crypto');
var urlparser = require('url');
var wclib = require('jsharmony/WebConnect');
var wc = new wclib.WebConnect();
module.exports = exports = function(module, funcs){
var exports = {};
var _t = module._t;
var pendingHostRequests = {};
exports.parseTemplateVariables = function(deploymentType, dbcontext, site_id, site_config, template_variables, deployment_target_publish_config, callback){
var cms = module;
if(!deploymentType || !_.includes(['editor','publish'], deploymentType)) return callback(new Error('Invalid deployment type'));
Helper.execif(!site_config,
function(f){
funcs.getSiteConfig(dbcontext, site_id, { continueOnConfigError: true }, function(err, siteConfig){
if(err) return callback(err);
site_config = siteConfig || {};
return f();
});
},
function(){
var defaultParams = {
timestamp: (Date.now()).toString(),
url_prefix: deployment_target_publish_config.url_prefix
};
template_variables = funcs.mergeTemplateVariables(deploymentType, defaultParams, cms.Config.template_variables, site_config.template_variables, template_variables);
return callback(null, template_variables);
}
);
};
exports.mergeTemplateVariables = function(deploymentType, orig){
if(!orig) orig = {};
for(var i=2;i<arguments.length;i++){
var item = arguments[i];
if(!item) continue;
for(var key in item){
var val = item[key];
if(val && (val.editor || val.publish)){
if((deploymentType=='editor') && ('editor' in val)) orig[key] = val.editor;
else if((deploymentType=='publish') && ('publish' in val)) orig[key] = val.publish;
else if(!(key in orig)) orig[key] = '';
}
else orig[key] = val;
}
}
return orig;
};
exports.getTemplateVariables = function(target_site_id, dbcontext, deploymentType, default_variables, override_variables, options, callback){
options = _.extend({ }, options);
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var sql = '';
var sql_ptypes = [];
var sql_params = {};
if(target_site_id=='current')
sql = 'select v_my_site.site_id, v_my_site.deployment_target_template_variables, deployment_target_publish_config from {schema}.v_my_site left outer join {schema}.deployment_target on deployment_target.deployment_target_id = v_my_site.deployment_target_id where v_my_site.site_id={schema}.my_current_site_id()';
else {
sql = 'select site_id from {schema}.v_my_site where site_id=@site_id';
sql_ptypes = [dbtypes.BigInt];
sql_params = { site_id: target_site_id };
}
appsrv.ExecRow(dbcontext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if (!rslt || !rslt.length || !rslt[0]) return callback(new Error('Site not checked out'));
var deployment_target_template_variables_str = rslt[0].deployment_target_template_variables || '';
var deployment_target_publish_config_str = rslt[0].deployment_target_publish_config || '';
var site_id = rslt[0].site_id || null;
var template_variables = _.extend({}, default_variables);
if(deployment_target_template_variables_str){
try{
template_variables = _.extend(template_variables, JSON.parse(deployment_target_template_variables_str));
}
catch(ex){
return callback(new Error('Error reading deployment_target_template_variables. Please make sure the JSON syntax is correct'));
}
}
var deployment_target_publish_config = {};
try{
deployment_target_publish_config = funcs.parseDeploymentTargetPublishConfig(site_id, deployment_target_publish_config_str, deploymentType);
}
catch(ex){
return callback(new Error('Error reading deployment_target_publish_config: '+ex.toString()));
}
template_variables = _.extend(template_variables, override_variables);
funcs.parseTemplateVariables(deploymentType, dbcontext, site_id, undefined, template_variables, deployment_target_publish_config, function(err, parsed_template_variables){
if(err) return callback(err);
return callback(null, parsed_template_variables, deployment_target_publish_config);
});
});
};
exports.parseDeploymentTargetPublishConfig = function(site_id, deployment_target_publish_config_str, deploymentType){
var cms = module;
deployment_target_publish_config_str = deployment_target_publish_config_str || '';
var rslt = {};
try{
if(deployment_target_publish_config_str) rslt = _.extend(rslt, JSON.parse(deployment_target_publish_config_str));
}
catch(ex){
throw new Error('Deployment Target deployment_target_publish_config has invalid JSON syntax');
}
for(var key in cms.Config.deployment_target_publish_config){
if((key == 's3_config')||(key == 'ftp_config')) rslt[key] = _.extend({}, cms.Config.deployment_target_publish_config[key], rslt[key]);
else if(!(key in rslt)) rslt[key] = cms.Config.deployment_target_publish_config[key];
}
if(Helper.isNullUndefined(rslt.url_prefix)) rslt.url_prefix = '/';
if(deploymentType=='editor') rslt.url_prefix = '/';
return rslt;
};
exports.replaceTemplateVariables = function(template_variables, content){
if(!content) return content;
if(!template_variables) return content;
content = content.toString();
var orig_content = content;
for(var key in template_variables){
if(key == template_variables[key]) continue;
var val = template_variables[key];
if(Helper.isNullUndefined(val)) val = '';
content = Helper.ReplaceAll(content, '%%%' + key + '%%%', val);
}
if(orig_content != content) return funcs.replaceTemplateVariables(template_variables, content);
return content;
};
exports.getAccessKey = function(dbcontext, deployment_target_id, server_url, options, callback){
options = _.extend({ timestamp: null }, options);
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var deployment_target_client_salt = null;
async.waterfall([
function(key_cb){
var sql = 'select deployment_target_client_salt from {schema}.deployment_target where deployment_target_id=@deployment_target_id';
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { deployment_target_id: deployment_target_id };
appsrv.ExecRow(dbcontext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if(err) return key_cb(err);
if(!rslt || !rslt.length || !rslt[0]) return key_cb(new Error('Deployment target not found'));
deployment_target_client_salt = rslt[0].deployment_target_client_salt || null;
return key_cb();
});
},
function(key_cb){
if(deployment_target_client_salt) return key_cb();
//Generate salt if not defined
deployment_target_client_salt = crypto.randomBytes(16).toString('hex');
var sql = 'update {schema}.deployment_target set deployment_target_client_salt=@deployment_target_client_salt where deployment_target_id=@deployment_target_id';
var sql_ptypes = [dbtypes.BigInt, dbtypes.VarChar(256)];
var sql_params = { deployment_target_id: deployment_target_id, deployment_target_client_salt: deployment_target_client_salt };
appsrv.ExecCommand(dbcontext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if(err) return key_cb(err);
return key_cb();
});
},
function(key_cb){
deployment_target_client_salt = (deployment_target_client_salt||'').toString().toLowerCase();
if(deployment_target_client_salt.length != 32) return key_cb(new Error('Invalid deployment_target_client_salt'));
//Generate access_key
var domain_hash = crypto.createHash('sha256').update(deployment_target_client_salt + '-' + server_url.toLowerCase() + (options.timestamp ? '-' + options.timestamp.toString() : '')).digest('hex').toLowerCase();
var access_key = '';
for(var i=0;i<8;i++){
var salt_part = parseInt(deployment_target_client_salt.substr(i*4,4), 16);
var domain_part = parseInt(domain_hash.substr(i*4,4), 16);
access_key += Helper.pad((salt_part ^ domain_part).toString(16).toLowerCase(), '0', 4);
}
access_key += domain_hash;
return callback(null, access_key);
},
], callback);
};
exports.req_deployment_target_regenerate_access_key = function(req, res, next){
var verb = req.method.toLowerCase();
if (!req.body) req.body = {};
var Q = req.query;
var P = req.body;
var appsrv = this;
var jsh = module.jsh;
var dbtypes = appsrv.DB.types;
var model = jsh.getModel(req, module.namespace + 'Site_Deployment_Target_IntegrationCode');
if (!Helper.hasModelAction(req, model, 'U')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
var deployment_target_id = req.params.deployment_target_id||'';
if(!deployment_target_id) return next();
if(deployment_target_id.toString() != parseInt(deployment_target_id).toString()) return Helper.GenError(req, res, -4, 'Invalid Parameters');
var sql = 'select deployment_target_id from {schema}.deployment_target where deployment_target_id=@deployment_target_id';
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { deployment_target_id: deployment_target_id };
appsrv.ExecRow(req._DBContext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt[0]) return Helper.GenError(req, res, -99999, 'Invalid Deployment Target ID');
var deployment_target = rslt[0];
deployment_target_id = deployment_target.deployment_target_id;
if (verb == 'post') {
//Validate parameters
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var deployment_target_client_salt = crypto.randomBytes(16).toString('hex');
sql = 'update {schema}.deployment_target set deployment_target_client_salt=@deployment_target_client_salt where deployment_target_id=@deployment_target_id';
sql_ptypes = [dbtypes.BigInt, dbtypes.VarChar(256)];
sql_params = { deployment_target_id: deployment_target_id, deployment_target_client_salt: deployment_target_client_salt };
appsrv.ExecRow(req._DBContext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
return res.send(JSON.stringify({ _success: 1 }));
});
}
else {
return next();
}
});
};
exports.req_deployment_target_public_key = function (req, res, next) {
var verb = req.method.toLowerCase();
if (!req.body) req.body = {};
var Q = req.query;
var P = req.body;
var appsrv = this;
var jsh = module.jsh;
var dbtypes = appsrv.DB.types;
var model = jsh.getModel(req, module.namespace + 'Site_Deployment_Target_Key');
if (req.query && (req.query.source=='js')) req.jsproxyid = 'cmsfiledownloader';
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
var deployment_target_id = req.params.deployment_target_id||'';
if(!deployment_target_id) return next();
if(deployment_target_id.toString() != parseInt(deployment_target_id).toString()) return Helper.GenError(req, res, -4, 'Invalid Parameters');
var sql = 'select deployment_target_id from {schema}.deployment_target where deployment_target_id=@deployment_target_id';
appsrv.ExecRow(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt], { deployment_target_id: deployment_target_id }, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt[0]) return Helper.GenError(req, res, -99999, 'Invalid Deployment Target ID');
var deployment_target = rslt[0];
deployment_target_id = deployment_target.deployment_target_id;
//Validate parameters
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, ['|source','&format'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if(!_.includes(['pem','openssh'], Q.format)) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
//Keep certExt separate from Q.format for security
var certExt = 'pem';
if(Q.format=='openssh') certExt = 'openssh';
if (verb == 'get') {
//Check if file exists
var fpath = path.join(jsh.Config.datadir,'deployment_target',deployment_target_id.toString(), 'key.public.'+certExt);
fs.exists(fpath, function(exists){
Helper.execif(!exists, function(f){
//If not, generate file
funcs.generate_deployment_target_key(deployment_target_id, function(err){
if(err) return Helper.GenError(req, res, -99999, err.toString());
return f();
});
},
function(){
HelperFS.outputFile(req, res, fpath, deployment_target_id.toString()+'.key.public.'+certExt);
});
});
}
else {
return next();
}
});
};
exports.req_deployment_target_private_key = function (req, res, next) {
var verb = req.method.toLowerCase();
if (!req.body) req.body = {};
var Q = req.query;
var P = req.body;
var appsrv = this;
var jsh = module.jsh;
var dbtypes = appsrv.DB.types;
var model = jsh.getModel(req, module.namespace + 'Site_Deployment_Target_Key');
if (req.query && (req.query.source=='js')) req.jsproxyid = 'cmsfiledownloader';
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
var deployment_target_id = req.params.deployment_target_id;
if(!deployment_target_id) return next();
if(deployment_target_id.toString() != parseInt(deployment_target_id).toString()) return Helper.GenError(req, res, -4, 'Invalid Parameters');
var sql = 'select deployment_target_id from {schema}.deployment_target where deployment_target_id=@deployment_target_id';
appsrv.ExecRow(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt], { deployment_target_id: deployment_target_id }, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt[0]) return Helper.GenError(req, res, -99999, 'Invalid Deployment Target ID');
var deployment_target = rslt[0];
deployment_target_id = deployment_target.deployment_target_id;
if (verb == 'get') {
//Validate parameters
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, ['|source'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
//Check if file exists
var fpath = path.join(jsh.Config.datadir,'deployment_target',deployment_target_id.toString(), 'key.private.pem');
fs.exists(fpath, function(exists){
Helper.execif(!exists, function(f){
//If not, generate file
funcs.generate_deployment_target_key(deployment_target_id, function(err){
if(err) return Helper.GenError(req, res, -99999, err.toString());
return f();
});
},
function(){
HelperFS.outputFile(req, res, fpath, deployment_target_id.toString()+'.key.private.pem');
});
});
}
else if(verb == 'post'){
//Validate parameters
if (!appsrv.ParamCheck('P', P, ['&private_key'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var deploymentTargetPath = path.join(jsh.Config.datadir,'deployment_target',deployment_target_id.toString());
var privateKeyPath = path.join(deploymentTargetPath, 'key.private.pem');
var privateKey = null;
async.waterfall([
//Get uploaded file
function(upload_cb){
var fname = path.basename(P.private_key);
var file_ext = path.extname(fname).toLowerCase(); //Get extension
if (file_ext != '.pem') { return Helper.GenError(req, res, -32, 'File extension is not supported.'); }
var uploadFilePath = path.join(jsh.Config.datadir, 'temp', req._DBContext, fname);
HelperFS.getFileStats(req, res, uploadFilePath, function (err, stat) {
if (err) { return Helper.GenError(req, res, -33, 'Key file not found.'); }
fs.readFile(uploadFilePath, 'utf8', function(err, data){
if(err) return upload_cb(err);
privateKey = data;
if(!data) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
return upload_cb(null);
});
});
},
//Validate key
function(upload_cb){
validatePrivateKey(privateKey, function(err, newPrivateKey){
if(err) return upload_cb(err);
privateKey = newPrivateKey;
return upload_cb();
});
},
//Create deployment target folder if it does not exist
function(upload_cb){
HelperFS.createFolderIfNotExists(deploymentTargetPath, upload_cb);
},
//Write new private key to disk
function(upload_cb){
fs.writeFile(privateKeyPath, privateKey, { encoding: 'utf8', mode: 0o600 }, upload_cb);
},
//Delete pem public key if it exists
function(upload_cb){
HelperFS.tryUnlink(path.join(deploymentTargetPath, 'key.public.pem'), upload_cb);
},
//Delete openssh public key if it exists
function(upload_cb){
HelperFS.tryUnlink(path.join(deploymentTargetPath, 'key.public.openssh'), upload_cb);
},
//Regenerate public key
function(upload_cb){
funcs.generate_deployment_target_key(deployment_target_id, upload_cb);
},
], function(err){
if(err) return Helper.GenError(req, res, -99999, err.toString());
return res.send(JSON.stringify({ _success: 1 }));
});
}
else {
return next();
}
});
};
function createPrivateKey(cb){
crypto.generateKeyPair('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
}, function(err, newPublicKey, newPrivateKey){
if(err) return cb(err);
validatePrivateKey(newPrivateKey, function(err, processedPrivateKey){
if(err) return cb(err);
return cb(err, newPublicKey, processedPrivateKey);
});
});
}
function createPublicKey(privateKey, cb){
var forge = require('node-forge');
forge.options.usePureJavaScript = true;
var newPublicKey = null;
try{
var pkiPrivateKey = forge.pki.privateKeyFromPem(privateKey);
newPublicKey = forge.pki.publicKeyToPem(forge.pki.setRsaPublicKey(pkiPrivateKey.n, pkiPrivateKey.e));
}
catch(err){
return cb(err);
}
if(!newPublicKey) return cb(new Error('Could not generate public key - invalid private key'));
return cb(null, newPublicKey);
}
function createOpensshKey(publicKey, cb){
var forge = require('node-forge');
forge.options.usePureJavaScript = true;
var opensshKey = null;
try{
var pkiPublicKey = forge.pki.publicKeyFromPem(publicKey);
opensshKey = forge.ssh.publicKeyToOpenSSH(pkiPublicKey);
}
catch(err){
return cb(err);
}
return cb(null, opensshKey);
}
function validatePrivateKey(privateKey, cb){
var forge = require('node-forge');
forge.options.usePureJavaScript = true;
var newPrivateKey = null;
try{
var pkiPrivateKey = forge.pki.privateKeyFromPem(privateKey);
newPrivateKey = forge.pki.privateKeyToPem(pkiPrivateKey);
}
catch(err){
return cb(err);
}
if(!newPrivateKey) return cb(new Error('Invalid private key'));
return cb(null, newPrivateKey);
}
exports.generate_deployment_target_key = function(deployment_target_id, callback){
var jsh = module.jsh;
var deploymentTargetPath = path.join(jsh.Config.datadir,'deployment_target',deployment_target_id.toString());
var privateKeyPath = path.join(deploymentTargetPath, 'key.private.pem');
var publicKeyPath = path.join(deploymentTargetPath, 'key.public.pem');
var publicOpensshKeyPath = path.join(deploymentTargetPath, 'key.public.openssh');
var privateKey = null;
var publicKey = null;
async.waterfall([
//Create deployment target folder if it does not exist
function(gen_cb){
HelperFS.createFolderIfNotExists(deploymentTargetPath, gen_cb);
},
//Check if private key exists
function(gen_cb){
fs.exists(privateKeyPath, function(exists){
if(!exists) return gen_cb();
fs.readFile(privateKeyPath, 'utf8', function(err, content){
if(err) return gen_cb(err);
privateKey = content;
return gen_cb();
});
});
},
//Check if public key exists
function(gen_cb){
fs.exists(publicKeyPath, function(exists){
if(!exists) return gen_cb();
fs.readFile(publicKeyPath, 'utf8', function(err, content){
if(err) return gen_cb(err);
publicKey = content;
return gen_cb();
});
});
},
//Generate public + private keys if the private key does not exist
function(gen_cb){
if(privateKey) return gen_cb();
createPrivateKey(function(err, newPublicKey, newPrivateKey){
if(err) return gen_cb(err);
fs.writeFile(privateKeyPath, newPrivateKey, { encoding: 'utf8', mode: 0o600 }, function(err){
if(err) return gen_cb(err);
privateKey = newPrivateKey;
fs.writeFile(publicKeyPath, newPublicKey, { encoding: 'utf8', mode: 0o600 }, function(err){
if(err) return gen_cb(err);
publicKey = newPublicKey;
return gen_cb();
});
});
});
},
//Generate public key if the private key does not exist
function(gen_cb){
if(publicKey) return gen_cb();
createPublicKey(privateKey, function(err, newPublicKey){
if(err) return gen_cb(err);
fs.writeFile(publicKeyPath, newPublicKey, { encoding: 'utf8', mode: 0o600 }, function(err){
if(err) return gen_cb(err);
publicKey = newPublicKey;
return gen_cb();
});
});
},
//Generate OpenSSH public key if it does not exist
function(gen_cb){
fs.exists(publicOpensshKeyPath, function(exists){
if(exists) return gen_cb();
createOpensshKey(publicKey, function(err, opensshKey){
if(err) return gen_cb(err);
fs.writeFile(publicOpensshKeyPath, opensshKey, { encoding: 'utf8', mode: 0o600 }, gen_cb);
});
});
},
], callback);
};
exports.req_parse_deployment_target_url = function(req, res, next){
var verb = req.method.toLowerCase();
var Q = req.query;
var P = req.body;
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var model = jsh.getModel(req, module.namespace + 'Site_Deployment_Target');
if (!Helper.hasModelAction(req, model, 'BU')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
if (verb == 'post'){
if (!appsrv.ParamCheck('Q', Q, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('P', P, ['&url'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var url = P.url;
var parsed_url = funcs.parse_deployment_target_url(url);
res.end(JSON.stringify({
'_success': 1,
'urlparts': parsed_url,
}));
return;
}
else return next();
};
exports.parse_deployment_target_url = function(url){
url = url || '';
var lurl = url.toLowerCase();
if((lurl.indexOf('git_ssh:')==0)||(lurl.indexOf('git_https:')==0)) url = url.substr(4);
var parsed_url = null;
try{
parsed_url = urlparser.parse(url);
}
catch(ex){
/* Do nothing */
}
var username = null;
var password = null;
if(parsed_url){
if(parsed_url.path && (parsed_url.path.indexOf('/:')==0)){
if(parsed_url.protocol && (parsed_url.protocol.toLowerCase()=='ssh:')){
parsed_url.path = ':' + parsed_url.path.substr(2);
parsed_url.pathname = ':' + parsed_url.pathname.substr(2);
}
else {
parsed_url.path = '/' + parsed_url.path.substr(2);
parsed_url.pathname = '/' + parsed_url.pathname.substr(2);
}
}
var auth = parsed_url.auth;
if(parsed_url.auth){
var idx = auth.indexOf(':');
if(idx>=0){
username = auth.substr(0, idx);
password = auth.substr(idx+1);
}
else username = auth;
}
}
return (parsed_url ? {
protocol: parsed_url.protocol,
auth: parsed_url.auth,
hostname: parsed_url.hostname,
port: parsed_url.port,
path: parsed_url.path,
username: username,
password: password,
} : null);
};
exports.req_deployment_target_defaults = function(req, res, next){
var verb = req.method.toLowerCase();
var Q = req.query;
var P = req.body;
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var model = jsh.getModel(req, module.namespace + 'Site_Deployment_Target');
if (!Helper.hasModelAction(req, model, 'BU')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
if (verb == 'get'){
if (!appsrv.ParamCheck('Q', Q, ['&site_id'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var site_id = Q.site_id;
if(site_id.toString() != parseInt(site_id).toString()) return Helper.GenError(req, res, -4, 'Invalid Parameters');
site_id = parseInt(Q.site_id);
funcs.getTemplateVariables(site_id , req._DBContext, 'publish', {}, {}, { }, function(err, template_variables, deployment_target_publish_config){
if(err) return Helper.GenError(req, res, -99999, err.toString());
//Get site_default_page_filename
var sql = 'select site_default_page_filename from {schema}.site where site_id=@site_id';
appsrv.ExecRow(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt], { site_id: site_id }, function (err, rslt) {
if (err) return Helper.GenError(req, res, -99999, err.toString());
if (!rslt || !rslt.length || !rslt[0]) { Helper.GenError(req, res, -3, 'Site not found'); return; }
var site_default_page_filename = rslt[0].site_default_page_filename;
funcs.getSiteConfig(req._DBContext, site_id, { continueOnConfigError: true }, function(err, siteConfig){
if(err) return Helper.GenError(req, res, -99999, err.toString());
res.end(JSON.stringify({
_success: 1,
template_variables: template_variables,
deployment_target_publish_config: deployment_target_publish_config,
site_default_page_filename: site_default_page_filename,
redirect_listing_path: siteConfig.redirect_listing_path,
}));
});
});
});
return;
}
else return next();
};
exports.deployment_target_queue_onSubscribe = function(cb, req, res, queueid){
var jsh = module.jsh;
if(!queueid) return cb();
queueid = queueid.toString();
if(queueid in jsh.Config.queues) return cb();
else if(queueid.indexOf('deployment_host_publish_')==0){
//Add queue
jsh.Log.info('Adding deployment host publish queue: '+queueid);
jsh.Config.queues[queueid] = {
actions: 'BIUD',
roles: { 'CMSHOST': '*' }
};
return cb();
}
else if(queueid.indexOf('deployment_host_request_')==0){
//Add queue
jsh.Log.info('Adding deployment host request queue: '+queueid);
jsh.Config.queues[queueid] = {
actions: 'BIUD',
roles: { 'CMSHOST': '*' }
};
return cb();
}
else return cb();
};
exports.deployment_target_downloadTemplates = function(host_id, deployment_id, urls, requestOptions, cb){
var request_message = {
deployment_id: deployment_id,
op: 'download_templates',
urls: urls
};
funcs.deployment_target_host_requestSend(host_id, request_message, requestOptions, function(err, body){
if(err) err = 'Error downloading templates: '+err.toString();
return cb(err, body && body.urls);
});
};
exports.deployment_target_host_sendQueue = function(queue_name, host_id, msg, callback, retry){
var MAX_RETRIES = 15;
var RETRY_SLEEP = 2000;
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
retry = retry || 0;
if(typeof msg == 'undefined') msg = null;
if(!_.isString(msg)) msg = JSON.stringify(msg);
var notifications = appsrv.SendQueue(queue_name + '_' + host_id, msg);
if(notifications) return callback();
if(retry >= MAX_RETRIES) return callback(new Error('CMS Deployment Host "'+host_id+'" not connected. Please verify jsharmony-cms-host is running on the target machine'));
setTimeout(function(){
funcs.deployment_target_host_sendQueue(queue_name, host_id, msg, callback, retry+1);
}, RETRY_SLEEP);
};
exports.deployment_target_host_requestSend = function(host_id, request_message, options, cb){
options = _.extend({ timeout: 60 }, options);
var jsh = module.jsh;
//Notify deployment host
var queue_name = 'deployment_host_request';
var requestId = (new Date()).getTime().toString() + '_' + Math.round(Math.random()*1000000000).toString();
var msg = {
id: requestId,
body: request_message,
};
jsh.Log.info('CMSHOST: Sending request '+requestId+' to '+host_id);
funcs.deployment_target_host_sendQueue(queue_name, host_id, msg, function(err){
if(err) return cb(err);
pendingHostRequests[requestId] = {
queue_name: queue_name,
host_id: host_id,
cb: cb,
timer: null,
};
//Clear callback after timeout
pendingHostRequests[requestId].timer = setTimeout(function(){
if(requestId in pendingHostRequests){
delete pendingHostRequests[requestId];
cb(new Error('Timeout during request to CMS Deployment Host "'+host_id+'". Please verify jsharmony-cms-host is running on the target machine'));
}
}, (options.timeout||0) * 1000);
});
};
exports.deployment_host_response = function (req, res, next) {
var verb = req.method.toLowerCase();
var Q = req.query;
var P = req.body;
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
if(!req.params || !req.params.request_id) return next();
var requestId = req.params.request_id;
if (!Helper.HasRole(req, 'CMSHOST')) { Helper.GenError(req, res, -11, 'Invalid Access'); return; }
if (verb == 'post'){
//Validate parameters
if (!appsrv.ParamCheck('P', P, ['|body','|error'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
jsh.Log.info('CMSHOST: Received response '+requestId);
var body = {};
if(P.body){
try{
body = JSON.parse(P.body);
}
catch(ex){
return Helper.GenError(req, res, -9, 'Error parsing body: '+ex.toString());
}
}
if(!(requestId in pendingHostRequests)){
return Helper.GenError(req, res, -9, 'Request timed out or closed');
}
var request = pendingHostRequests[requestId];
delete pendingHostRequests[requestId];
clearTimeout(request.timer);
try{
request.cb(P.error||null, body);
}
catch(ex){
return Helper.GenError(req, res, -9, ex.toString());
}
res.end(JSON.stringify({ '_success': 1 }));
return;
}
else return next();
};
exports.webRequestGate = function(publish_path, publish_config, f){ // f(addWebRequest, performWebRequests, gate, downloadTemplates)
var host_id = '';
if(publish_path && (publish_path.indexOf('cmshost://')==0)) host_id = publish_path.substr(10);
var downloadTemplates = host_id && publish_config && publish_config.cmshost_config && publish_config.cmshost_config.download_remote_templates;
var webRequestURLs = [];
var webRequestContent = {};
Helper.gate(function(gate){
var addWebRequest = function(url, callback){
if(!_.includes(webRequestURLs, url)) webRequestURLs.push(url);
var op = gate.addOp(url, callback);
op.waitForGate(function(){
if(downloadTemplates){
var content = webRequestContent[url];
if(typeof content == 'undefined') return op.done(new Error('Remote Template not resolved by deployment host: '+url));
op.params[1](null, null, content, op.done);
}
else {
wc.req(op.params[0], 'GET', {}, { 'Accept': 'text/html' }, undefined, function(err, res, templateContent){
if(err){
if((err.code=='DEPTH_ZERO_SELF_SIGNED_CERT')||(err.code=='SELF_SIGNED_CERT_IN_CHAIN')||(err.message=='self signed certificate')){
err.message = 'Self-signed certificate found for URL: '+op.params[0] + ' :: Select the "Ignore certificate errors when downloading remote templates" option in the Deployment Target configuration to allow self-signed certificates.';
}
}
op.params[1](err, res, templateContent, op.done);
}, { rejectUnauthorized: !publish_config.ignore_remote_template_certificate });
}
});
return op;
};
var performWebRequests = function(callback){
gate.waitForOps(function(){
Helper.execif(downloadTemplates && webRequestURLs.length,
function(f){
var requestOptions = {};
if('remote_timeout' in publish_config.cmshost_config) requestOptions.timeout = publish_config.cmshost_config.remote_timeout;
funcs.deployment_target_downloadTemplates(host_id, null, webRequestURLs, requestOptions, function(err, templates){
if(err) return callback(err);
if(templates) _.extend(webRequestContent, templates);
return f();
});
},
function(){
gate.open();
gate.waitForDone(callback);
}
);
});
};
f(addWebRequest, performWebRequests, gate, downloadTemplates);
}, {
open: !downloadTemplates,
//log: function(txt){ console.log(txt); },
});
};
return exports;
};