dynamicscrm-api
Advanced tools
Comparing version 0.1.0 to 0.1.1
545
lib/util.js
/*jslint nomen: true, stupid: true */ | ||
// module dependencies | ||
var xpath = require('xpath'); | ||
var xpath = require("xpath"); | ||
var Cache = require("mem-cache"); | ||
var uuid = require("node-uuid"); | ||
var domParser = new (require('xmldom').DOMParser)(); | ||
var domParser = new (require("xmldom").DOMParser)(); | ||
var fs = require("fs"); | ||
var parseString = require('xml2js').parseString; | ||
var traverse = require('traverse'); | ||
var Serializer = require('./serializer.js'); | ||
var WSTrustFlow = require('../lib/ws-security/wsTrustFlow.js'); | ||
var parseString = require("xml2js").parseString; | ||
var traverse = require("traverse"); | ||
var Serializer = require("./serializer.js"); | ||
var WSTrustFlow = require("../lib/ws-security/wsTrustFlow.js"); | ||
var constants = require("constants"); | ||
var cookie = require("cookie"); | ||
var httpntlm = require("httpntlm"); | ||
var ntlm = require("httpntlm/ntlm.js"); | ||
var Agentkeepalive = require("agentkeepalive"); | ||
var request = require("request"); | ||
var kidoConnector = require("kido-connector"); | ||
var cookie = require('cookie'); | ||
var httpntlm = require('httpntlm'); | ||
var ntlm = require('httpntlm/ntlm.js'); | ||
var Agentkeepalive = require('agentkeepalive'); | ||
var request = require('request'); | ||
// this class implements all features | ||
// this class implements all features | ||
var Util = function (settings) { | ||
@@ -28,16 +28,16 @@ "use strict"; | ||
// Arguments validation | ||
if (!settings || typeof settings !== "object") { throw new Error("'settings' argument must be an object instance."); } | ||
if (!settings.domain || typeof settings.domain !== "string") { throw new Error("'settings.domain' property is a required string."); } | ||
if (settings.domainUrlSuffix && typeof settings.domainUrlSuffix !== "string") { throw new Error("'settings.domainUrlSuffix' must be string."); } | ||
if (settings.timeout && typeof settings.timeout !== "number") { throw new Error("'settings.timeout' property must be a number."); } | ||
if (settings.username && typeof settings.username !== "string") { throw new Error("'settings.username' property must be a string."); } | ||
if (settings.password && typeof settings.password !== "string") { throw new Error("'settings.password' property must be a string."); } | ||
if (settings.port && typeof settings.port !== "number") { throw new Error("'settings.port' property must be a number."); } | ||
if (settings.organizationName && typeof settings.organizationName !== "string") { throw new Error("'settings.organizationName' property must be a string."); } | ||
if (!settings || typeof settings !== "object") throw new Error("'settings' argument must be an object instance."); | ||
if (!settings.domain || typeof settings.domain !== "string") throw new Error("'settings.domain' property is a required string."); | ||
if (settings.domainUrlSuffix && typeof settings.domainUrlSuffix !== "string") throw new Error("'settings.domainUrlSuffix' must be string."); | ||
if (settings.timeout && typeof settings.timeout !== "number") throw new Error("'settings.timeout' property must be a number."); | ||
if (settings.username && typeof settings.username !== "string") throw new Error("'settings.username' property must be a string."); | ||
if (settings.password && typeof settings.password !== "string") throw new Error("'settings.password' property must be a string."); | ||
if (settings.port && typeof settings.port !== "number") throw new Error("'settings.port' property must be a number."); | ||
if (settings.organizationName && typeof settings.organizationName !== "string") throw new Error("'settings.organizationName' property must be a string."); | ||
//Set default value if authentication type is wrong or invalid | ||
if (!settings.authType || typeof settings.authType !== "string" || authenticationTypes.indexOf(settings.authType) === -1) { settings.authType = "live_id"; } | ||
if (!settings.authType || typeof settings.authType !== "string" || authenticationTypes.indexOf(settings.authType) === -1) settings.authType = "live_id"; | ||
// Sets default arguments values | ||
settings.timeout = settings.timeout || 15 * 60 * 1000; // default sessions timeout of 15 minutes in ms | ||
settings.timeout = settings.timeout || 15 * 60 * 1000; // default sessions timeout of 15 minutes in ms | ||
settings.returnJson = true; | ||
@@ -49,5 +49,4 @@ settings.port = settings.port || (settings.useHttp ? 80 : 443); | ||
getHostname = function () { | ||
if (settings.domainUrlSuffix) { | ||
return settings.domain + settings.domainUrlSuffix; | ||
} | ||
if (settings.domainUrlSuffix) return settings.domain + settings.domainUrlSuffix; | ||
return settings.domain + defaultUrlSuffix; | ||
@@ -58,3 +57,3 @@ }, | ||
organizationPath = "/XRMServices/2011/Organization.svc", | ||
organizationServiceEndpoint = 'https://' + hostname + organizationPath, | ||
organizationServiceEndpoint = "https://" + hostname + organizationPath, | ||
userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36", | ||
@@ -111,3 +110,3 @@ SOAPActionBase = "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/", | ||
defaultCb = function (err) { | ||
if (err) { throw err; } | ||
if (err) throw err; | ||
}; | ||
@@ -118,3 +117,3 @@ | ||
reqOptions.secureOptions = constants.SSL_OP_NO_TLSv1_2; | ||
reqOptions.ciphers = 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM'; | ||
reqOptions.ciphers = "ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM"; | ||
reqOptions.honorCipherOrder = true; | ||
@@ -125,5 +124,3 @@ } | ||
fetchEndpoints = function (cb) { | ||
if (endpoints) { | ||
return cb(null, endpoints); | ||
} | ||
if (endpoints) return cb(null, endpoints); | ||
@@ -137,3 +134,3 @@ var options = { | ||
request(options, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -145,3 +142,3 @@ var resXml = domParser.parseFromString(body), | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
if (fault.length > 0) return cb(new Error(fault.toString()), null); | ||
@@ -159,3 +156,3 @@ location = xpath.select(importLocationXpath, resXml) | ||
request(opts, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -174,3 +171,3 @@ var resXmlImport, | ||
faultImport = xpath.select(faultTextXpath, resXmlImport); | ||
if (faultImport.length > 0) { return cb(new Error(faultImport.toString()), null); } | ||
if (faultImport.length > 0) return cb(new Error(faultImport.toString()), null); | ||
@@ -202,8 +199,6 @@ authenticationType = xpath.select(authenticationTypeXpath, resXmlImport).toString(); | ||
loadOrRegisterDevice = function (options, cb) { | ||
if (device) { | ||
return cb(null, device); | ||
} | ||
if (device) return cb(null, device); | ||
var username = generateRandom(24, 'aA#'), | ||
password = generateRandom(24, 'aA#'); | ||
var username = generateRandom(24, "aA#"), | ||
password = generateRandom(24, "aA#"); | ||
@@ -216,8 +211,8 @@ authCreateDeviceMessage = authCreateDeviceMessage | ||
options = { | ||
method: 'POST', | ||
method: "POST", | ||
uri: options.DeviceAddUrl, | ||
body: authCreateDeviceMessage, | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': authCreateDeviceMessage.length | ||
"Content-Type": "application/soap+xml; charset=UTF-8", | ||
"Content-Length": authCreateDeviceMessage.length | ||
} | ||
@@ -229,3 +224,3 @@ }; | ||
request(options, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -236,3 +231,3 @@ var resXml = domParser.parseFromString(body), | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
if (fault.length > 0) return cb(new Error(fault.toString()), null); | ||
@@ -242,5 +237,5 @@ puid = xpath.select("/DeviceAddResponse/puid/text()", resXml).toString(); | ||
device = { | ||
deviceUsername : username, | ||
devicePassword : password, | ||
puid : puid | ||
deviceUsername: username, | ||
devicePassword: password, | ||
puid: puid | ||
}; | ||
@@ -258,5 +253,3 @@ | ||
if (cipher) { | ||
return cb(null, cipher); | ||
} | ||
if (cipher) return cb(null, cipher); | ||
@@ -273,8 +266,8 @@ authRequestDeviceTokenMessage = authRequestDeviceTokenMessage | ||
requestOptions = { | ||
method: 'POST', | ||
method: "POST", | ||
uri: options.IssuerAddress, | ||
body: authRequestDeviceTokenMessage, | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': Buffer.byteLength(authRequestDeviceTokenMessage) | ||
"Content-Type": "application/soap+xml; charset=UTF-8", | ||
"Content-Length": Buffer.byteLength(authRequestDeviceTokenMessage) | ||
} | ||
@@ -286,3 +279,3 @@ }; | ||
request(requestOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -293,6 +286,6 @@ var resXml = domParser.parseFromString(body), | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
if (fault.length > 0) return cb(new Error(fault.toString()), null); | ||
cipherValue = xpath.select("//*[local-name()='RequestedSecurityToken' and namespace-uri()='http://schemas.xmlsoap.org/ws/2005/02/trust']/*[name()='EncryptedData']/*[name()='CipherData']/*[name()='CipherValue']/text()", resXml).toString(); | ||
cipher = {CipherValue : cipherValue}; | ||
cipher = {CipherValue: cipherValue}; | ||
@@ -306,14 +299,13 @@ tokensForDeviceCache.set("auth_tokenrequest_device", cipher); | ||
generateRandom = function (length, chars) { | ||
var mask = '', | ||
result = '', | ||
var mask = "", | ||
result = "", | ||
i; | ||
if (chars.indexOf('a') > -1) { mask += 'abcdefghijklmnopqrstuvwxyz'; } | ||
if (chars.indexOf('A') > -1) { mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; } | ||
if (chars.indexOf('#') > -1) { mask += '0123456789'; } | ||
if (chars.indexOf('!') > -1) { mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; } | ||
if (chars.indexOf("a") > -1) mask += "abcdefghijklmnopqrstuvwxyz"; | ||
if (chars.indexOf("A") > -1) mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||
if (chars.indexOf("#") > -1) mask += "0123456789"; | ||
if (chars.indexOf("!") > -1) mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; | ||
for (i = length; i > 0; i = i - 1) { | ||
for (i = length; i > 0; i = i - 1) | ||
result += mask[Math.round(Math.random() * (mask.length - 1))]; | ||
} | ||
@@ -326,5 +318,3 @@ return result; | ||
prefixes.forEach(function (p) { | ||
if (objInd.indexOf(p) === 0) { | ||
rk = objInd.replace(p, ''); | ||
} | ||
if (objInd.indexOf(p) === 0) rk = objInd.replace(p, ""); | ||
}); | ||
@@ -341,7 +331,7 @@ return rk; | ||
if (fault.length > 0) { return cb(new Error(fault.toString())); } | ||
if (fault.length > 0) return cb(new Error(fault.toString())); | ||
if (settings.returnJson) { | ||
if (settings.returnJson) | ||
parseString(body, {explicitArray: false}, function (err, jsondata) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -352,9 +342,8 @@ prefixes = []; | ||
if (this.key !== undefined) { | ||
var pos = this.key.indexOf('xmlns:'), | ||
k = this.key.substring(6, this.key.length) + ':'; | ||
var pos = this.key.indexOf("xmlns:"), | ||
k = this.key.substring(6, this.key.length) + ":"; | ||
if (pos > -1 || this.key.indexOf('xmlns') > -1) { | ||
if (prefixes.lastIndexOf(k) === -1) { | ||
prefixes.push(k); | ||
} | ||
if (pos > -1 || this.key.indexOf("xmlns") > -1) { | ||
if (prefixes.lastIndexOf(k) === -1) prefixes.push(k); | ||
this.remove(); | ||
@@ -368,79 +357,60 @@ } | ||
}); | ||
} else { | ||
cb(null, data); | ||
} | ||
else cb(null, data); | ||
}; | ||
executeSoapPost = function (options, action, template, body, cb) { | ||
var timeCreated = new Date(), | ||
timeExpires = new Date(timeCreated.getTime() + 5 * 60000), | ||
requestOptions, | ||
soapHeader, | ||
xmlrequestbody, | ||
soapPostMessage, | ||
security, | ||
ntlmOptions, | ||
type1msg, | ||
agent, | ||
reqOptions, | ||
url, | ||
httpHeaders = {}; | ||
kidoConnector.isHostAllowed(hostname, function (err, allowed) { | ||
if (err) return cb(err); | ||
if (!allowed) return cb(new Error("The hostname is not allowed")); | ||
xmlrequestbody = template.replace("{requetbody}", body); | ||
var timeCreated = new Date(), | ||
timeExpires = new Date(timeCreated.getTime() + 5 * 60000), | ||
requestOptions, | ||
soapHeader, | ||
xmlrequestbody, | ||
soapPostMessage, | ||
security, | ||
ntlmOptions, | ||
type1msg, | ||
agent, | ||
reqOptions, | ||
url, | ||
httpHeaders = {}; | ||
if (settings.authType === "ntlm") { | ||
soapPostMessage = soapEnvelopeMessage | ||
.replace("{envelopeNS}", "http://schemas.xmlsoap.org/soap/envelope/") | ||
.replace("{header}", "") | ||
.replace("{body}", xmlrequestbody); | ||
xmlrequestbody = template.replace("{requetbody}", body); | ||
url = (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port + "/" + settings.organizationName + organizationPath + "/web"; | ||
if (settings.authType === "ntlm") { | ||
soapPostMessage = soapEnvelopeMessage | ||
.replace("{envelopeNS}", "http://schemas.xmlsoap.org/soap/envelope/") | ||
.replace("{header}", "") | ||
.replace("{body}", xmlrequestbody); | ||
httpHeaders.cookie = "ReqClientId=" + options.ReqClientId; | ||
httpHeaders.SOAPAction = SOAPActionBase + action; | ||
httpHeaders['Content-Length'] = Buffer.byteLength(soapPostMessage); | ||
httpHeaders['Content-Type'] = "text/xml; charset=utf-8"; | ||
httpHeaders.Accept = 'application/xml, text/xml, */*'; | ||
httpHeaders["User-Agent"] = userAgent; | ||
url = (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port + "/" + settings.organizationName + organizationPath + "/web"; | ||
ntlmOptions = { | ||
username : options.username || settings.username, | ||
password : options.password || settings.password, | ||
workstation : options.workstation || settings.workstation || '', | ||
domain : options.ntlmDomain || settings.ntlmDomain || '' | ||
}; | ||
httpHeaders.cookie = "ReqClientId=" + options.ReqClientId; | ||
httpHeaders.SOAPAction = SOAPActionBase + action; | ||
httpHeaders["Content-Length"] = Buffer.byteLength(soapPostMessage); | ||
httpHeaders["Content-Type"] = "text/xml; charset=utf-8"; | ||
httpHeaders.Accept = "application/xml, text/xml, */*"; | ||
httpHeaders["User-Agent"] = userAgent; | ||
type1msg = ntlm.createType1Message(ntlmOptions); | ||
agent = settings.useHttp ? new Agentkeepalive() : new Agentkeepalive.HttpsAgent(); | ||
ntlmOptions = { | ||
username: options.username || settings.username, | ||
password: options.password || settings.password, | ||
workstation: options.workstation || settings.workstation || "", | ||
domain: options.ntlmDomain || settings.ntlmDomain || "" | ||
}; | ||
reqOptions = { | ||
method: options.method || "GET", | ||
url: url, | ||
headers: { | ||
Authorization: type1msg, | ||
}, | ||
agent: agent, | ||
timeout : settings.requestTimeout | ||
}; | ||
type1msg = ntlm.createType1Message(ntlmOptions); | ||
agent = settings.useHttp ? new Agentkeepalive() : new Agentkeepalive.HttpsAgent(); | ||
addSecureOptions(reqOptions); | ||
request(reqOptions, function (err, res) { | ||
if (err) { return cb(err); } | ||
if (!res.headers['www-authenticate']) { | ||
return cb(new Error('www-authenticate not found on response of second request')); | ||
} | ||
var type2msg = ntlm.parseType2Message(res.headers['www-authenticate']), | ||
type3msg = ntlm.createType3Message(type2msg, ntlmOptions); | ||
httpHeaders.Authorization = type3msg; | ||
reqOptions = { | ||
method : "POST", | ||
method: options.method || "GET", | ||
url: url, | ||
body: soapPostMessage, | ||
headers: { | ||
Authorization: type1msg, | ||
}, | ||
agent: agent, | ||
timeout : settings.requestTimeout, | ||
headers: httpHeaders | ||
timeout: settings.requestTimeout | ||
}; | ||
@@ -450,53 +420,73 @@ | ||
request(reqOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
request(reqOptions, function (err, res) { | ||
if (err) return cb(err); | ||
if (!res.headers["www-authenticate"]) return cb(new Error("www-authenticate not found on response of second request")); | ||
parseResponse(body, cb); | ||
}); | ||
}); | ||
var type2msg = ntlm.parseType2Message(res.headers["www-authenticate"]), | ||
type3msg = ntlm.createType3Message(type2msg, ntlmOptions); | ||
} else { | ||
soapHeader = soapHeaderMessage | ||
.replace("{action}", action) | ||
.replace("{messageid}", uuid.v4()) | ||
.replace("{crmurl}", organizationServiceEndpoint); | ||
httpHeaders.Authorization = type3msg; | ||
if (options.encryptedData) { | ||
security = '<wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">\ | ||
<u:Timestamp u:Id="_0">\ | ||
<u:Created>' + timeCreated.toISOString() + '</u:Created>\ | ||
<u:Expires>' + timeExpires.toISOString() + '</u:Expires>\ | ||
</u:Timestamp>' + options.encryptedData + '</wsse:Security>'; | ||
reqOptions = { | ||
method: "POST", | ||
url: url, | ||
body: soapPostMessage, | ||
agent: agent, | ||
timeout: settings.requestTimeout, | ||
headers: httpHeaders | ||
}; | ||
soapHeader = soapHeader.replace("{security}", security); | ||
} else if (options.header) { | ||
soapHeader = soapHeader.replace("{security}", options.header); | ||
addSecureOptions(reqOptions); | ||
request(reqOptions, function (err, res, body) { | ||
if (err) return cb(err); | ||
parseResponse(body, cb); | ||
}); | ||
}); | ||
} else { | ||
return cb(new Error("Neither token or header found.")); | ||
} | ||
soapHeader = soapHeaderMessage | ||
.replace("{action}", action) | ||
.replace("{messageid}", uuid.v4()) | ||
.replace("{crmurl}", organizationServiceEndpoint); | ||
url = (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port + organizationPath; | ||
soapPostMessage = soapEnvelopeMessage | ||
.replace("{envelopeNS}", "http://www.w3.org/2003/05/soap-envelope") | ||
.replace("{header}", soapHeader) | ||
.replace("{body}", xmlrequestbody); | ||
if (options.encryptedData) { | ||
security = '<wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">\ | ||
<u:Timestamp u:Id="_0">\ | ||
<u:Created>' + timeCreated.toISOString() + "</u:Created>\ | ||
<u:Expires>" + timeExpires.toISOString() + "</u:Expires>\ | ||
</u:Timestamp>" + options.encryptedData + "</wsse:Security>"; | ||
httpHeaders['Content-Type'] = 'application/soap+xml; charset=UTF-8'; | ||
httpHeaders['Content-Length'] = Buffer.byteLength(soapPostMessage); | ||
soapHeader = soapHeader.replace("{security}", security); | ||
requestOptions = { | ||
method: 'POST', | ||
uri: url, | ||
body: soapPostMessage, | ||
headers: httpHeaders | ||
}; | ||
} else if (options.header) soapHeader = soapHeader.replace("{security}", options.header); | ||
addSecureOptions(requestOptions); | ||
else return cb(new Error("Neither token or header found.")); | ||
request(requestOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
url = (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port + organizationPath; | ||
soapPostMessage = soapEnvelopeMessage | ||
.replace("{envelopeNS}", "http://www.w3.org/2003/05/soap-envelope") | ||
.replace("{header}", soapHeader) | ||
.replace("{body}", xmlrequestbody); | ||
parseResponse(body, cb); | ||
}); | ||
} | ||
httpHeaders["Content-Type"] = "application/soap+xml; charset=UTF-8"; | ||
httpHeaders["Content-Length"] = Buffer.byteLength(soapPostMessage); | ||
requestOptions = { | ||
method: "POST", | ||
uri: url, | ||
body: soapPostMessage, | ||
headers: httpHeaders | ||
}; | ||
addSecureOptions(requestOptions); | ||
request(requestOptions, function (err, res, body) { | ||
if (err) return cb(err); | ||
parseResponse(body, cb); | ||
}); | ||
} | ||
}); | ||
}; | ||
@@ -509,22 +499,14 @@ | ||
if (typeof dupeObj === 'object') { | ||
if (dupeObj.length) { | ||
retObj = []; | ||
} | ||
if (typeof dupeObj === "object") { | ||
if (dupeObj.length) retObj = []; | ||
for (objInd in dupeObj) { | ||
if (dupeObj.hasOwnProperty(objInd)) { | ||
rk = renameKey(objInd, pfxs); | ||
if (typeof dupeObj[objInd] === 'object') { | ||
retObj[rk] = deepObjCopy(dupeObj[objInd], pfxs); | ||
} else if (typeof dupeObj[objInd] === 'string') { | ||
retObj[rk] = dupeObj[objInd]; | ||
} else if (typeof dupeObj[objInd] === 'number') { | ||
retObj[rk] = dupeObj[objInd]; | ||
} else if (typeof dupeObj[objInd] === 'boolean') { | ||
if (dupeObj[rk]) { | ||
retObj[objInd] = true; | ||
} else { | ||
retObj[objInd] = false; | ||
} | ||
} | ||
if (typeof dupeObj[objInd] === "object") retObj[rk] = deepObjCopy(dupeObj[objInd], pfxs); | ||
else if (typeof dupeObj[objInd] === "string") retObj[rk] = dupeObj[objInd]; | ||
else if (typeof dupeObj[objInd] === "number") retObj[rk] = dupeObj[objInd]; | ||
else if (typeof dupeObj[objInd] === "boolean") | ||
if (dupeObj[rk]) retObj[objInd] = true; | ||
else retObj[objInd] = false; | ||
} | ||
@@ -555,6 +537,6 @@ } | ||
options = { | ||
method: 'POST', | ||
method: "POST", | ||
uri: "https://" + host + path, | ||
body: samlRequest, | ||
headers: { 'Content-Length': Buffer.byteLength(samlRequest) } | ||
headers: { "Content-Length": Buffer.byteLength(samlRequest) } | ||
}; | ||
@@ -565,3 +547,3 @@ | ||
request(options, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -571,8 +553,6 @@ var resXml = domParser.parseFromString(body), | ||
// search for a fault | ||
exp = ['S:Envelope', 'S:Body', 'S:Fault', 'S:Detail', 'psf:error', 'psf:internalerror', 'psf:text'].map(name).join("") + "/text()", | ||
exp = ["S:Envelope", "S:Body", "S:Fault", "S:Detail", "psf:error", "psf:internalerror", "psf:text"].map(name).join("") + "/text()", | ||
fault = xpath.select(exp, resXml); | ||
if (fault.length > 0) { | ||
return cb(new Error(fault.toString())); | ||
} | ||
if (fault.length > 0) return cb(new Error(fault.toString())); | ||
@@ -587,3 +567,3 @@ return cb(null, resXml); | ||
fetchEndpoints(function (err, result) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -594,7 +574,7 @@ authOptions = result; | ||
loadOrRegisterDevice(authOptions, function (err, result) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
authOptions.DeviceInfo = result; | ||
getTokenUsingDeviceId(authOptions, function (err, result) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -616,8 +596,8 @@ var timeCreated = new Date(), | ||
requestOptions = { | ||
method: 'POST', | ||
method: "POST", | ||
uri: authOptions.IssuerAddress, | ||
body: authRequestSTSTokenMessage, | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': Buffer.byteLength(authRequestSTSTokenMessage) | ||
"Content-Type": "application/soap+xml; charset=UTF-8", | ||
"Content-Length": Buffer.byteLength(authRequestSTSTokenMessage) | ||
} | ||
@@ -629,3 +609,3 @@ }; | ||
request(requestOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -638,19 +618,15 @@ var resXml = domParser.parseFromString(body), | ||
if (fault.length > 0) { | ||
fullMessage = fault.toString(); | ||
faultDetailsXpath = "//*[local-name()='Fault']/*[local-name()='Detail']"; | ||
faultDetails = xpath.select(faultDetailsXpath, resXml); | ||
if (fault.length <= 0) return cb(null, resXml); | ||
if (faultDetails.length > 0) { | ||
parseString(faultDetails.toString(), {explicitArray: false}, function (err, data) { | ||
if (err) { return cb(err); } | ||
fullMessage = fault.toString(); | ||
faultDetailsXpath = "//*[local-name()='Fault']/*[local-name()='Detail']"; | ||
faultDetails = xpath.select(faultDetailsXpath, resXml); | ||
fullMessage = fullMessage + ". Details:" + data; | ||
return cb(new Error(fullMessage), null); | ||
}); | ||
} | ||
} else { | ||
return cb(null, resXml); | ||
} | ||
if (faultDetails.length > 0) | ||
parseString(faultDetails.toString(), {explicitArray: false}, function (err, data) { | ||
if (err) return cb(err); | ||
fullMessage = fullMessage + ". Details:" + data; | ||
return cb(new Error(fullMessage), null); | ||
}); | ||
}); | ||
@@ -664,3 +640,3 @@ }); | ||
fetchEndpoints(function (err, wsdlInfo) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
var wstrustFlowOptions, | ||
@@ -671,8 +647,7 @@ flow, | ||
if (wsdlInfo.KeyType === keyTypeUnsupported) { | ||
if (wsdlInfo.KeyType === keyTypeUnsupported) | ||
wsdlInfo.KeyType = "http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey"; | ||
} | ||
wstrustFlowOptions = { | ||
wstrustEndpoint: identifier + '/2005/usernamemixed', | ||
wstrustEndpoint: identifier + "/2005/usernamemixed", | ||
username: authOptions.username, | ||
@@ -688,3 +663,3 @@ password: authOptions.password, | ||
flow.getWSSecurityHeader(function (err, header) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -699,6 +674,6 @@ return cb(null, header); | ||
url: (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port, | ||
username : options.username || settings.username, | ||
password : options.password || settings.password, | ||
workstation : options.workstation || settings.workstation || '', | ||
domain : options.ntlmDomain || settings.ntlmDomain || '', | ||
username: options.username || settings.username, | ||
password: options.password || settings.password, | ||
workstation: options.workstation || settings.workstation || "", | ||
domain: options.ntlmDomain || settings.ntlmDomain || "", | ||
@@ -711,4 +686,4 @@ headers: { | ||
httpntlm.get(authOptions, function (err, res) { | ||
if (err) { return cb(err); } | ||
if (res.cookies.length === 0) { return cb(Error.create("Invalid Username or Password")); } | ||
if (err) return cb(err); | ||
if (res.cookies.length === 0) return cb(Error.create("Invalid Username or Password")); | ||
@@ -730,51 +705,53 @@ var cookies = cookie.parse(res.headers["set-cookie"].join(";")), | ||
this.Authenticate = function (options, cb) { | ||
var responseXMLCB = function (err, resXml) { | ||
if (err) { return cb(err); } | ||
kidoConnector.isHostAllowed(hostname, function (err, allowed) { | ||
if (err) return cb(err); | ||
if (!allowed) return cb(new Error("The hostname is not allowed")); | ||
var token = xpath.select("//*[local-name()='EncryptedData']", resXml).toString(), | ||
authToken = uuid.v4(), | ||
authItem = {token: token}; | ||
var responseXMLCB = function (err, resXml) { | ||
if (err) return cb(err); | ||
cacheTokenByAuth.set(authToken, authItem); | ||
cacheAuthByUser.set(options.username, authToken); | ||
return cb(null, {auth: authToken}); | ||
}, | ||
var token = xpath.select("//*[local-name()='EncryptedData']", resXml).toString(), | ||
authToken = uuid.v4(), | ||
authItem = {token: token}; | ||
federationCB = function (err, header) { | ||
if (err) { return cb(err); } | ||
cacheTokenByAuth.set(authToken, authItem); | ||
cacheAuthByUser.set(options.username, authToken); | ||
return cb(null, {auth: authToken}); | ||
}, | ||
var authToken = uuid.v4(), | ||
authItem = {header: header}; | ||
federationCB = function (err, header) { | ||
if (err) return cb(err); | ||
cacheTokenByAuth.set(authToken, authItem); | ||
cacheAuthByUser.set(options.username, authToken); | ||
return cb(null, {auth: authToken}); | ||
}; | ||
var authToken = uuid.v4(), | ||
authItem = {header: header}; | ||
// handles optional 'options' argument | ||
if (!cb && typeof options === 'function') { | ||
cb = options; | ||
options = {}; | ||
} | ||
cacheTokenByAuth.set(authToken, authItem); | ||
cacheAuthByUser.set(options.username, authToken); | ||
return cb(null, {auth: authToken}); | ||
}; | ||
// sets default values | ||
cb = cb || defaultCb; | ||
options = options || {}; | ||
// handles optional 'options' argument | ||
if (!cb && typeof options === "function") { | ||
cb = options; | ||
options = {}; | ||
} | ||
// validates arguments values | ||
if (typeof options !== 'object') { return cb(new Error("'options' argument is missing or invalid.")); } | ||
// sets default values | ||
cb = cb || defaultCb; | ||
options = options || {}; | ||
// Validates username and password | ||
options.username = options.username || settings.username; | ||
options.password = options.password || settings.password; | ||
// validates arguments values | ||
if (typeof options !== "object") return cb(new Error("'options' argument is missing or invalid.")); | ||
if (settings.authType === "microsoft_online") { | ||
authenticateUsingMicrosoftOnline(options, responseXMLCB); | ||
} else if (settings.authType === "federation") { | ||
authenticateUsingFederation(options, federationCB); | ||
} else if (settings.authType === "ntlm") { | ||
authenticateUsingNTLM(options, cb); | ||
} else { //Default Live Id | ||
authenticateUsingLiveId(options, responseXMLCB); | ||
} | ||
// Validates username and password | ||
options.username = options.username || settings.username; | ||
options.password = options.password || settings.password; | ||
if (settings.authType === "microsoft_online") authenticateUsingMicrosoftOnline(options, responseXMLCB); | ||
else if (settings.authType === "federation") authenticateUsingFederation(options, federationCB); | ||
else if (settings.authType === "ntlm") authenticateUsingNTLM(options, cb); | ||
//Default Live Id | ||
else authenticateUsingLiveId(options, responseXMLCB); | ||
}); | ||
}; | ||
@@ -840,3 +817,3 @@ | ||
// handles optional 'options' argument | ||
if (!cb && typeof options === 'function') { | ||
if (!cb && typeof options === "function") { | ||
cb = options; | ||
@@ -849,9 +826,6 @@ options = {}; | ||
options = options || {}; | ||
if (!options || typeof options !== 'object') { | ||
return cb(new Error("'options' argument is missing or invalid.")); | ||
} | ||
if (!options || typeof options !== "object") return cb(new Error("'options' argument is missing or invalid.")); | ||
if (options.encryptedData || options.header) { | ||
executeSoapPost(options, action, template, body, cb); | ||
} else if (options.auth) { | ||
if (options.encryptedData || options.header) executeSoapPost(options, action, template, body, cb); | ||
else if (options.auth) { | ||
authItem = cacheTokenByAuth.get(options.auth); | ||
@@ -863,5 +837,5 @@ options.encryptedData = authItem.token; //For LiveId an MSOnline | ||
executeSoapPost(options, action, template, body, cb); | ||
} else { | ||
} else | ||
this.Authenticate(options, function (err, data) { | ||
if (err) { return cb(err); } | ||
if (err) return cb(err); | ||
@@ -874,3 +848,2 @@ authItem = cacheTokenByAuth.get(data.auth); | ||
}); | ||
} | ||
}; | ||
@@ -877,0 +850,0 @@ }; |
{ | ||
"name": "dynamicscrm-api", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Pure SOAP module that allows to invoke Microsoft Dynamics CRM Online services", | ||
@@ -43,3 +43,4 @@ "author": "Kidozen <development@kidozen.com>", | ||
"httpntlm": "https://github.com/kidozen/node-http-ntlm/tarball/master", | ||
"request": "2.44.0" | ||
"request": "2.44.0", | ||
"kido-connector": "^1.1.1" | ||
}, | ||
@@ -46,0 +47,0 @@ "devDependencies": { |
/*global describe, before, beforeEach, it */ | ||
var assert = require('assert'); | ||
var Dynamics = require('../index.js'); | ||
var assert = require("assert"); | ||
var Dynamics = require("../index.js"); | ||
var settingsForMicrosoftOnlineAuth = { | ||
username : "", | ||
password : "", | ||
organizationid : "", | ||
domain : "", | ||
username: "", | ||
password: "", | ||
organizationid: "", | ||
domain: "", | ||
domainUrlSuffix: "", | ||
@@ -16,5 +16,5 @@ authType: "microsoft_online" //Office365 | ||
settingsForLiveIdAuth = { | ||
username : "", | ||
password : "", | ||
organizationid : "", | ||
username: "", | ||
password: "", | ||
organizationid: "", | ||
domain: "", | ||
@@ -25,4 +25,4 @@ authType: "live_id" | ||
settingForFederationAuth = { | ||
username : "", | ||
password : "", | ||
username: "", | ||
password: "", | ||
domain: "", | ||
@@ -144,2 +144,15 @@ domainUrlSuffix: "", | ||
it("Should fail with unallowed host running on hub", function (done) { | ||
process.env.RUNNING_ON = "hub"; | ||
var connector = new Dynamics({domain: "127.0", domainUrlSuffix: ".0.1"}); | ||
connector.Authenticate({username: "invalid"}, function (err, result) { | ||
assert.ok(err); | ||
assert.ok(!result); | ||
assert.strictEqual(err.message, "The hostname is not allowed"); | ||
process.env.RUNNING_ON = ""; | ||
done(); | ||
}); | ||
}); | ||
it("Should authenticate OK", function (done) { | ||
@@ -175,3 +188,3 @@ dynamics.Authenticate({}, function (err, result) { | ||
assert.ok(err); | ||
assert.strictEqual(err.message, "The password for the account has expired.\r\n"); | ||
assert.strictEqual(err.message, "The entered and stored passwords do not match.\r\n"); | ||
done(); | ||
@@ -232,3 +245,3 @@ }); | ||
assert.ok(result.username); | ||
assert.equal('string', typeof result.auth); | ||
assert.equal("string", typeof result.auth); | ||
assert.equal(36, result.auth.length); | ||
@@ -242,4 +255,4 @@ done(); | ||
var options = {}; | ||
options.EntityName = 'lead'; | ||
options.id = '0f993360-d987-43f7-8995-ab5ffb50a43f'; | ||
options.EntityName = "lead"; | ||
options.id = "0f993360-d987-43f7-8995-ab5ffb50a43f"; | ||
@@ -263,5 +276,5 @@ dynamics.Authenticate({}, function (err, authData) { | ||
var options = {}; | ||
options.LogicalName = 'lead'; | ||
options.Attributes = [ { key: 'lastname', value : 'Doe'}, | ||
{ key: 'firstname', value : 'John'}]; | ||
options.LogicalName = "lead"; | ||
options.Attributes = [ { key: "lastname", value: "Doe"}, | ||
{ key: "firstname", value: "John"}]; | ||
@@ -279,3 +292,3 @@ dynamics.Authenticate({}, function (err, authData) { | ||
options = {}; | ||
options.EntityName = 'lead'; | ||
options.EntityName = "lead"; | ||
options.id = result2.Envelope.Body.CreateResponse.CreateResult; | ||
@@ -315,9 +328,8 @@ options.auth = authData.auth; | ||
if (entities.length) { //It's an array | ||
assert.ok(entities[0].LogicalName === 'account'); | ||
assert.ok(entities[0].LogicalName === "account"); | ||
assert.equal(entities[0].Attributes | ||
.KeyValuePairOfstringanyType.length, 2); //Entity with 2 attributes, accountid and name | ||
} else { | ||
assert.ok(entities.LogicalName === 'account'); | ||
} | ||
} else assert.ok(entities.LogicalName === "account"); | ||
done(); | ||
@@ -350,3 +362,3 @@ }); | ||
describe("Method execution with MicrosoftOnline Auth", function () { | ||
describe.skip("Method execution with MicrosoftOnline Auth", function () { | ||
var settings, | ||
@@ -410,3 +422,3 @@ dynamics; | ||
var id = "d4ea3aab-8263-e411-9446-22000b4712e7", | ||
var id = "41658D20-C986-E411-9446-22000B4712E7".toLowerCase(), | ||
logicalName = "contact"; | ||
@@ -413,0 +425,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
87880
13
1564
11
+ Addedkido-connector@^1.1.1
+ Addedasync@0.2.10(transitive)
+ Addedaws-sign@0.2.10.3.0(transitive)
+ Addedboom@0.3.8(transitive)
+ Addedcolors@0.6.2(transitive)
+ Addedcookie-jar@0.2.00.3.0(transitive)
+ Addedcryptiles@0.1.3(transitive)
+ Addedcycle@1.0.3(transitive)
+ Addedeyes@0.1.8(transitive)
+ Addedforever-agent@0.2.0(transitive)
+ Addedform-data@0.0.10(transitive)
+ Addedhawk@0.10.21.0.0(transitive)
+ Addedhoek@0.7.6(transitive)
+ Addedipaddr.js@0.1.6(transitive)
+ Addedjson-stringify-safe@3.0.05.0.0(transitive)
+ Addedkido-connector@1.1.3(transitive)
+ Addedmoment@2.5.1(transitive)
+ Addednode-uuid@1.4.1(transitive)
+ Addedoauth-sign@0.2.00.3.0(transitive)
+ Addedobject-hash@0.3.0(transitive)
+ Addedpkginfo@0.3.1(transitive)
+ Addedqs@0.5.60.6.6(transitive)
+ Addedrequest@2.16.62.25.0(transitive)
+ Addedsimple-errors@1.0.0(transitive)
+ Addedsntp@0.1.4(transitive)
+ Addedstack-trace@0.0.10(transitive)
+ Addedtunnel-agent@0.2.00.3.0(transitive)
+ Addedwinston@0.7.2(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)