dynamicscrm-api
Advanced tools
Comparing version 0.0.12 to 0.1.0
656
lib/util.js
/*jslint nomen: true, stupid: true */ | ||
// module dependencies | ||
var https = require('https'); | ||
var url = require('url'); | ||
var xpath = require('xpath'); | ||
var cookie = require('cookie'); | ||
var Cache = require("mem-cache"); | ||
var uuid = require("node-uuid"); | ||
var domParser = new (require('xmldom').DOMParser)(); | ||
var path = require("path"); | ||
var urljs = require("url"); | ||
var fs = require("fs"); | ||
var parseString = require('xml2js').parseString; | ||
var traverse = require('traverse'); | ||
var Serializer = require('./serializer.js'); | ||
var util = require('util'); | ||
var WSTrustFlow = require('../lib/ws-security/wsTrustFlow.js'); | ||
var constants = require("constants"); | ||
var xpath = require('xpath'); | ||
var Cache = require("mem-cache"); | ||
var uuid = require("node-uuid"); | ||
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 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'); | ||
// this class implements all features | ||
@@ -25,3 +25,3 @@ var Util = function (settings) { | ||
var authenticationTypes = ["live_id", "microsoft_online", "federation"]; | ||
var authenticationTypes = ["live_id", "microsoft_online", "federation", "ntlm"]; | ||
@@ -35,2 +35,4 @@ // Arguments validation | ||
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."); } | ||
@@ -42,6 +44,6 @@ //Set default value if authentication type is wrong or invalid | ||
settings.timeout = settings.timeout || 15 * 60 * 1000; // default sessions timeout of 15 minutes in ms | ||
settings.returnJson = true; // default sessions timeout of 15 minutes in ms | ||
settings.returnJson = true; | ||
settings.port = settings.port || (settings.useHttp ? 80 : 443); | ||
var self = this, | ||
defaultUrlSuffix = ".api.crm.dynamics.com", | ||
var defaultUrlSuffix = ".api.crm.dynamics.com", | ||
@@ -58,2 +60,4 @@ getHostname = function () { | ||
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", | ||
SOAPActionBase = "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/", | ||
cacheTokenByAuth = new Cache(settings.timeout), | ||
@@ -69,3 +73,2 @@ cacheAuthByUser = new Cache(settings.timeout), | ||
generateRandom, | ||
getErrorMessage, | ||
renameKey, | ||
@@ -78,2 +81,5 @@ executeSoapPost, | ||
authenticateUsingFederation, | ||
authenticateUsingNTLM, | ||
addSecureOptions, | ||
parseResponse, | ||
@@ -111,2 +117,10 @@ //load templates once | ||
addSecureOptions = function (reqOptions) { | ||
if (!settings.useHttp) { | ||
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.honorCipherOrder = true; | ||
} | ||
}; | ||
fetchEndpoints = function (cb) { | ||
@@ -116,90 +130,67 @@ if (endpoints) { | ||
} | ||
var options = { | ||
host: hostname, | ||
path: organizationPath + "?wsdl", | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
}, | ||
uri: settings.useHttp ? "http://" : "https://" + hostname + ":" + settings.port + organizationPath + "?wsdl", | ||
}; | ||
response = https.get(options, function (res) { | ||
var xml = ''; | ||
res.setEncoding('utf8'); | ||
addSecureOptions(options); | ||
res.on('data', function (chunk) { | ||
xml += chunk; | ||
}); | ||
request(options, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
res.on('end', function () { | ||
var resXml = domParser.parseFromString(xml), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
location, | ||
opts; | ||
var resXml = domParser.parseFromString(body), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
location, | ||
opts; | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
location = xpath.select(importLocationXpath, resXml) | ||
.map(function (attr) { | ||
return attr.value; | ||
})[0]; | ||
location = xpath.select(importLocationXpath, resXml) | ||
.map(function (attr) { | ||
return attr.value; | ||
})[0]; | ||
if (location.length > 0) { | ||
opts = { | ||
host: urljs.parse(location).host, | ||
path: urljs.parse(location).pathname + urljs.parse(location).search, | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
}; | ||
if (location.length > 0) { | ||
opts = { url: location }; | ||
https.get(opts, function (res) { | ||
var xmlImport = '', | ||
resXmlImport, | ||
faultImport, | ||
authenticationType, | ||
issuerAddress, | ||
liveAppliesTo, | ||
identifier, | ||
keyType, | ||
keySize, | ||
requireClientEntropy; | ||
addSecureOptions(opts); | ||
res.setEncoding('utf8'); | ||
request(opts, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
res.on('data', function (chunk) { | ||
xmlImport += chunk; | ||
}); | ||
var resXmlImport, | ||
faultImport, | ||
authenticationType, | ||
issuerAddress, | ||
liveAppliesTo, | ||
identifier, | ||
keyType, | ||
keySize, | ||
requireClientEntropy; | ||
res.on('end', function () { | ||
resXmlImport = domParser.parseFromString(xmlImport); | ||
faultImport = xpath.select(faultTextXpath, resXmlImport); | ||
if (faultImport.length > 0) { return cb(new Error(faultImport.toString()), null); } | ||
resXmlImport = domParser.parseFromString(body); | ||
faultImport = xpath.select(faultTextXpath, resXmlImport); | ||
if (faultImport.length > 0) { return cb(new Error(faultImport.toString()), null); } | ||
authenticationType = xpath.select(authenticationTypeXpath, resXmlImport).toString(); | ||
issuerAddress = xpath.select(issuerAddressXpath, resXmlImport).toString(); | ||
liveAppliesTo = xpath.select(liveAppliesToXpath, resXmlImport).toString(); | ||
identifier = xpath.select("//*[local-name()='Identifier']/text()", resXmlImport).toString(); | ||
keyType = xpath.select("//*[local-name()='KeyType']/text()", resXmlImport).toString(); | ||
keySize = xpath.select("//*[local-name()='KeySize']/text()", resXmlImport).toString(); | ||
requireClientEntropy = (xmlImport.indexOf("RequireClientEntropy") > -1); | ||
authenticationType = xpath.select(authenticationTypeXpath, resXmlImport).toString(); | ||
issuerAddress = xpath.select(issuerAddressXpath, resXmlImport).toString(); | ||
liveAppliesTo = xpath.select(liveAppliesToXpath, resXmlImport).toString(); | ||
identifier = xpath.select("//*[local-name()='Identifier']/text()", resXmlImport).toString(); | ||
keyType = xpath.select("//*[local-name()='KeyType']/text()", resXmlImport).toString(); | ||
keySize = xpath.select("//*[local-name()='KeySize']/text()", resXmlImport).toString(); | ||
requireClientEntropy = (body.indexOf("RequireClientEntropy") > -1); | ||
endpoints = { | ||
AuthenticationType: authenticationType, | ||
IssuerAddress: issuerAddress, | ||
DeviceAddUrl: "https://login.live.com/ppsecure/DeviceAddCredential.srf", | ||
LiveIdAppliesTo: liveAppliesTo, | ||
Identifier: identifier, | ||
KeyType: keyType, | ||
KeySize: keySize, | ||
RequireClientEntropy: requireClientEntropy | ||
}; | ||
return cb(null, endpoints); | ||
}); | ||
}); | ||
} | ||
endpoints = { | ||
AuthenticationType: authenticationType, | ||
IssuerAddress: issuerAddress, | ||
DeviceAddUrl: "https://login.live.com/ppsecure/DeviceAddCredential.srf", | ||
LiveIdAppliesTo: liveAppliesTo, | ||
Identifier: identifier, | ||
KeyType: keyType, | ||
KeySize: keySize, | ||
RequireClientEntropy: requireClientEntropy | ||
}; | ||
return cb(null, endpoints); | ||
}); | ||
}); | ||
response.on('error', function (err) { | ||
return cb(new Error(getErrorMessage(err))); | ||
} | ||
}); | ||
@@ -214,4 +205,3 @@ }; | ||
var username = generateRandom(24, 'aA#'), | ||
password = generateRandom(24, 'aA#'), | ||
req; | ||
password = generateRandom(24, 'aA#'); | ||
@@ -225,45 +215,31 @@ authCreateDeviceMessage = authCreateDeviceMessage | ||
method: 'POST', | ||
host: urljs.parse(options.DeviceAddUrl).host, | ||
path: urljs.parse(options.DeviceAddUrl).pathname, | ||
uri: options.DeviceAddUrl, | ||
body: authCreateDeviceMessage, | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': authCreateDeviceMessage.length | ||
}, | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
} | ||
}; | ||
req = https.request(options, function (res) { | ||
var xml = ''; | ||
res.setEncoding('utf8'); | ||
addSecureOptions(options); | ||
res.on('data', function (chunk) { | ||
xml += chunk; | ||
}); | ||
request(options, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
res.on('end', function () { | ||
var resXml = domParser.parseFromString(xml), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
puid; | ||
var resXml = domParser.parseFromString(body), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
puid; | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
if (fault.length > 0) { return cb(new Error(fault.toString()), null); } | ||
puid = xpath.select("/DeviceAddResponse/puid/text()", resXml).toString(); | ||
puid = xpath.select("/DeviceAddResponse/puid/text()", resXml).toString(); | ||
device = { | ||
deviceUsername : username, | ||
devicePassword : password, | ||
puid : puid | ||
}; | ||
device = { | ||
deviceUsername : username, | ||
devicePassword : password, | ||
puid : puid | ||
}; | ||
return cb(null, device); | ||
}); | ||
return cb(null, device); | ||
}); | ||
req.on('error', function (err) { | ||
return cb(new Error(getErrorMessage(err))); | ||
}); | ||
req.end(authCreateDeviceMessage); | ||
}; | ||
@@ -275,4 +251,3 @@ | ||
cipher = tokensForDeviceCache.get("auth_tokenrequest_device"), | ||
requestOptions, | ||
req; | ||
requestOptions; | ||
@@ -294,42 +269,28 @@ if (cipher) { | ||
method: 'POST', | ||
host: urljs.parse(options.IssuerAddress).host, | ||
path: urljs.parse(options.IssuerAddress).pathname, | ||
uri: options.IssuerAddress, | ||
body: authRequestDeviceTokenMessage, | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': authRequestDeviceTokenMessage.length | ||
}, | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
'Content-Length': Buffer.byteLength(authRequestDeviceTokenMessage) | ||
} | ||
}; | ||
req = https.request(requestOptions, function (res) { | ||
var xml = ''; | ||
res.setEncoding('utf8'); | ||
addSecureOptions(requestOptions); | ||
res.on('data', function (chunk) { | ||
xml += chunk; | ||
}); | ||
request(requestOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
res.on('end', function () { | ||
var resXml = domParser.parseFromString(xml), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
cipherValue; | ||
var resXml = domParser.parseFromString(body), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
cipherValue; | ||
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}; | ||
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}; | ||
tokensForDeviceCache.set("auth_tokenrequest_device", cipher); | ||
tokensForDeviceCache.set("auth_tokenrequest_device", cipher); | ||
return cb(null, cipher); | ||
}); | ||
return cb(null, cipher); | ||
}); | ||
req.on('error', function (err) { | ||
return cb(new Error(getErrorMessage(err))); | ||
}); | ||
req.end(authRequestDeviceTokenMessage); | ||
}; | ||
@@ -354,6 +315,2 @@ | ||
getErrorMessage = function (err) { | ||
return (err.code === 'ENOTFOUND' ? 'Invalid host name' : err.toString()) + '\n' + err.stack; | ||
}; | ||
renameKey = function (objInd, prefixes) { | ||
@@ -369,2 +326,39 @@ var rk = objInd; | ||
parseResponse = function (body, cb) { | ||
var data = body, | ||
prefixes, | ||
data_no_ns, | ||
resXml = domParser.parseFromString(body), | ||
fault = xpath.select(faultTextXpath, resXml); | ||
if (fault.length > 0) { return cb(new Error(fault.toString())); } | ||
if (settings.returnJson) { | ||
parseString(body, {explicitArray: false}, function (err, jsondata) { | ||
if (err) { return cb(err); } | ||
prefixes = []; | ||
//removes namespaces | ||
data_no_ns = traverse(jsondata).map(function () { | ||
if (this.key !== undefined) { | ||
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); | ||
} | ||
this.remove(); | ||
} | ||
} | ||
}); | ||
//removes 'xx:' prefixes | ||
data = deepObjCopy(data_no_ns, prefixes); | ||
cb(null, data); | ||
}); | ||
} else { | ||
cb(null, data); | ||
} | ||
}; | ||
executeSoapPost = function (options, action, template, body, cb) { | ||
@@ -374,98 +368,125 @@ var timeCreated = new Date(), | ||
requestOptions, | ||
req, | ||
soapHeader, | ||
xmlrequestbody, | ||
soapPostMessage, | ||
security; | ||
security, | ||
ntlmOptions, | ||
type1msg, | ||
agent, | ||
reqOptions, | ||
url, | ||
httpHeaders = {}; | ||
soapHeader = soapHeaderMessage | ||
.replace("{action}", action) | ||
.replace("{messageid}", uuid.v4()) | ||
.replace("{crmurl}", organizationServiceEndpoint); | ||
xmlrequestbody = template.replace("{requetbody}", body); | ||
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>'; | ||
if (settings.authType === "ntlm") { | ||
soapPostMessage = soapEnvelopeMessage | ||
.replace("{envelopeNS}", "http://schemas.xmlsoap.org/soap/envelope/") | ||
.replace("{header}", "") | ||
.replace("{body}", xmlrequestbody); | ||
soapHeader = soapHeader.replace("{security}", security); | ||
} else if (options.header) { | ||
soapHeader = soapHeader.replace("{security}", options.header); | ||
} else { | ||
return cb(new Error("Neither token or header found.")); | ||
} | ||
url = (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port + "/" + settings.organizationName + organizationPath + "/web"; | ||
xmlrequestbody = template.replace("{requetbody}", body); | ||
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; | ||
soapPostMessage = soapEnvelopeMessage | ||
.replace("{header}", soapHeader) | ||
.replace("{body}", xmlrequestbody); | ||
ntlmOptions = { | ||
username : options.username || settings.username, | ||
password : options.password || settings.password, | ||
workstation : options.workstation || settings.workstation || '', | ||
domain : options.ntlmDomain || settings.ntlmDomain || '' | ||
}; | ||
requestOptions = { | ||
method: 'POST', | ||
host: hostname, | ||
path: '/XRMServices/2011/Organization.svc', | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': soapPostMessage.length | ||
}, | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
}; | ||
type1msg = ntlm.createType1Message(ntlmOptions); | ||
agent = settings.useHttp ? new Agentkeepalive() : new Agentkeepalive.HttpsAgent(); | ||
req = https.request(requestOptions, function (res) { | ||
var xml = ''; | ||
res.setEncoding('utf8'); | ||
reqOptions = { | ||
method: options.method || "GET", | ||
url: url, | ||
headers: { | ||
Authorization: type1msg, | ||
}, | ||
agent: agent, | ||
timeout : settings.requestTimeout | ||
}; | ||
res.on('data', function (chunk) { | ||
xml += chunk; | ||
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", | ||
url: url, | ||
body: soapPostMessage, | ||
agent: agent, | ||
timeout : settings.requestTimeout, | ||
headers: httpHeaders | ||
}; | ||
addSecureOptions(reqOptions); | ||
request(reqOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
parseResponse(body, cb); | ||
}); | ||
}); | ||
res.on('end', function () { | ||
var resXml = domParser.parseFromString(xml), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
data, | ||
//jsondata, | ||
prefixes, | ||
data_no_ns; | ||
} else { | ||
soapHeader = soapHeaderMessage | ||
.replace("{action}", action) | ||
.replace("{messageid}", uuid.v4()) | ||
.replace("{crmurl}", organizationServiceEndpoint); | ||
if (fault.length > 0) { return cb(new Error(fault.toString())); } | ||
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>'; | ||
data = xml; | ||
if (settings.returnJson) { | ||
parseString(xml, {explicitArray: false}, function (err, jsondata) { | ||
if (err) { return cb(err); } | ||
soapHeader = soapHeader.replace("{security}", security); | ||
} else if (options.header) { | ||
soapHeader = soapHeader.replace("{security}", options.header); | ||
} else { | ||
return cb(new Error("Neither token or header found.")); | ||
} | ||
prefixes = []; | ||
//removes namespaces | ||
data_no_ns = traverse(jsondata).map(function () { | ||
if (this.key !== undefined) { | ||
var pos = this.key.indexOf('xmlns:'), | ||
k = this.key.substring(6, this.key.length) + ':'; | ||
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 (pos > -1 || this.key.indexOf('xmlns') > -1) { | ||
if (prefixes.lastIndexOf(k) === -1) { | ||
prefixes.push(k); | ||
} | ||
this.remove(); | ||
} | ||
} | ||
}); | ||
//removes 'xx:' prefixes | ||
data = deepObjCopy(data_no_ns, prefixes); | ||
cb(null, data); | ||
}); | ||
} else { | ||
cb(null, data); | ||
} | ||
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); | ||
}); | ||
}); | ||
req.on('error', function (err) { | ||
return cb(new Error(getErrorMessage(err))); | ||
}); | ||
req.end(soapPostMessage); | ||
} | ||
}; | ||
@@ -524,34 +545,24 @@ | ||
method: 'POST', | ||
host: host, | ||
path: path, | ||
headers: { 'Content-Length': samlRequest.length }, | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
}, | ||
uri: "https://" + host + path, | ||
body: samlRequest, | ||
headers: { 'Content-Length': Buffer.byteLength(samlRequest) } | ||
}; | ||
req = https.request(options, function (res) { | ||
var xml = ''; | ||
res.setEncoding('utf8'); | ||
addSecureOptions(options); | ||
res.on('data', function (chunk) { | ||
xml += chunk; | ||
}); | ||
request(options, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
res.on('end', function () { | ||
// parses XML | ||
var resXml = domParser.parseFromString(xml), | ||
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()", | ||
fault = xpath.select(exp, resXml); | ||
// search for a fault | ||
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())); | ||
} | ||
return cb(null, resXml); | ||
}); | ||
}); | ||
req.end(samlRequest); | ||
return cb(null, resXml); | ||
}); | ||
}; | ||
@@ -577,4 +588,3 @@ | ||
timeExpires = new Date(timeCreated.getTime() + settings.timeout), | ||
requestOptions, | ||
req; | ||
requestOptions; | ||
@@ -593,54 +603,39 @@ authOptions.cipherValue = result.CipherValue; | ||
method: 'POST', | ||
host: urljs.parse(authOptions.IssuerAddress).host, | ||
path: urljs.parse(authOptions.IssuerAddress).pathname, | ||
uri: authOptions.IssuerAddress, | ||
body: authRequestSTSTokenMessage, | ||
headers: { | ||
'Content-Type': 'application/soap+xml; charset=UTF-8', | ||
'Content-Length': authRequestSTSTokenMessage.length | ||
}, | ||
secureOptions: constants.SSL_OP_NO_TLSv1_2, | ||
ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM', | ||
honorCipherOrder: true | ||
'Content-Length': Buffer.byteLength(authRequestSTSTokenMessage) | ||
} | ||
}; | ||
req = https.request(requestOptions, function (res) { | ||
var xml = ''; | ||
addSecureOptions(requestOptions); | ||
res.setEncoding('utf8'); | ||
request(requestOptions, function (err, res, body) { | ||
if (err) { return cb(err); } | ||
res.on('data', function (chunk) { | ||
xml += chunk; | ||
}); | ||
var resXml = domParser.parseFromString(body), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
fullMessage, | ||
faultDetailsXpath, | ||
faultDetails; | ||
res.on('end', function () { | ||
var resXml = domParser.parseFromString(xml), | ||
fault = xpath.select(faultTextXpath, resXml), | ||
fullMessage, | ||
faultDetailsXpath, | ||
faultDetails; | ||
if (fault.length > 0) { | ||
fullMessage = fault.toString(); | ||
faultDetailsXpath = "//*[local-name()='Fault']/*[local-name()='Detail']"; | ||
faultDetails = xpath.select(faultDetailsXpath, resXml); | ||
if (fault.length > 0) { | ||
fullMessage = fault.toString(); | ||
faultDetailsXpath = "//*[local-name()='Fault']/*[local-name()='Detail']"; | ||
faultDetails = xpath.select(faultDetailsXpath, resXml); | ||
if (faultDetails.length > 0) { | ||
parseString(faultDetails.toString(), {explicitArray: false}, function (err, data) { | ||
if (err) { return cb(err); } | ||
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); | ||
}); | ||
} | ||
} else { | ||
return cb(null, resXml); | ||
fullMessage = fullMessage + ". Details:" + data; | ||
return cb(new Error(fullMessage), null); | ||
}); | ||
} | ||
} else { | ||
return cb(null, resXml); | ||
} | ||
}); | ||
}); | ||
req.on('error', function (err) { | ||
return cb(new Error(getErrorMessage(err))); | ||
}); | ||
req.end(authRequestSTSTokenMessage); | ||
}); | ||
@@ -682,6 +677,35 @@ }); | ||
authenticateUsingNTLM = function (options, cb) { | ||
var authOptions = { | ||
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 || '', | ||
headers: { | ||
"User-Agent": userAgent | ||
} | ||
}; | ||
httpntlm.get(authOptions, function (err, res) { | ||
if (err) { return cb(err); } | ||
if (res.cookies.length === 0) { return cb(Error.create("Invalid Username or Password")); } | ||
var cookies = cookie.parse(res.headers["set-cookie"].join(";")), | ||
authToken = uuid.v4(), | ||
session = { | ||
username: options.username, | ||
password: options.password, | ||
ReqClientId: cookies.ReqClientId | ||
}; | ||
cacheTokenByAuth.set(authToken, session); | ||
cacheAuthByUser.set(options.username, authToken); | ||
return cb(null, {auth: authToken, username: options.username}); | ||
}); | ||
}; | ||
this.Authenticate = function (options, cb) { | ||
var item, | ||
responseXMLCB = function (err, resXml) { | ||
var responseXMLCB = function (err, resXml) { | ||
if (err) { return cb(err); } | ||
@@ -730,2 +754,4 @@ | ||
authenticateUsingFederation(options, federationCB); | ||
} else if (settings.authType === "ntlm") { | ||
authenticateUsingNTLM(options, cb); | ||
} else { //Default Live Id | ||
@@ -792,2 +818,3 @@ authenticateUsingLiveId(options, responseXMLCB); | ||
this.executePost = function (options, action, template, body, cb) { | ||
var authItem; | ||
// handles optional 'options' argument | ||
@@ -809,5 +836,6 @@ if (!cb && typeof options === 'function') { | ||
} else if (options.auth) { | ||
var authItem = cacheTokenByAuth.get(options.auth); | ||
authItem = cacheTokenByAuth.get(options.auth); | ||
options.encryptedData = authItem.token; //For LiveId an MSOnline | ||
options.header = authItem.header; //For Federation | ||
options.ReqClientId = authItem.ReqClientId; //For NTLM | ||
@@ -819,3 +847,3 @@ executeSoapPost(options, action, template, body, cb); | ||
var authItem = cacheTokenByAuth.get(data.auth); | ||
authItem = cacheTokenByAuth.get(data.auth); | ||
options.encryptedData = authItem.token; //For LiveId an MSOnline | ||
@@ -822,0 +850,0 @@ options.header = authItem.header; //For Federation |
{ | ||
"name": "dynamicscrm-api", | ||
"version": "0.0.12", | ||
"version": "0.1.0", | ||
"description": "Pure SOAP module that allows to invoke Microsoft Dynamics CRM Online services", | ||
@@ -38,6 +38,8 @@ "author": "Kidozen <development@kidozen.com>", | ||
"traverse": "0.6.5", | ||
"url": "0.7.9", | ||
"xml2js": "0.4.4", | ||
"xmldom": "0.1.19", | ||
"xpath": "0.0.6" | ||
"xpath": "0.0.6", | ||
"agentkeepalive": "0.1.5", | ||
"httpntlm": "https://github.com/kidozen/node-http-ntlm/tarball/master", | ||
"request": "2.44.0" | ||
}, | ||
@@ -44,0 +46,0 @@ "devDependencies": { |
@@ -29,2 +29,13 @@ /*global describe, before, beforeEach, it */ | ||
authType: "federation" | ||
}, | ||
settingsForNTLMAuth = { | ||
username: "", | ||
password: "", | ||
organizationName: "", | ||
domain: "", | ||
domainUrlSuffix: "", | ||
port: 5555, | ||
useHttp: true, | ||
authType: "ntlm" | ||
}; | ||
@@ -142,3 +153,3 @@ | ||
describe("Office 365 Authentication", function () { | ||
describe("Microsoft Online (Office 365) Authentication", function () { | ||
var settings, | ||
@@ -160,7 +171,6 @@ dynamics; | ||
it("Should authenticate OK", function (done) { | ||
it("Should fail with password expired", function (done) { | ||
dynamics.Authenticate({}, function (err, result) { | ||
assert.ok(!err); | ||
assert.ok(result); | ||
assert.ok(result.auth); | ||
assert.ok(err); | ||
assert.strictEqual(err.message, "The password for the account has expired.\r\n"); | ||
done(); | ||
@@ -198,2 +208,32 @@ }); | ||
describe("NTLM Authentication", function () { | ||
var settings, | ||
dynamics; | ||
beforeEach(function () { | ||
settings = settingsForNTLMAuth; | ||
dynamics = new Dynamics(settings); | ||
}); | ||
it.skip("Should fail with invalid credentials", function (done) { | ||
dynamics.Authenticate({username: "invalid"}, function (err, result) { | ||
assert.ifError(err); | ||
assert.ok(!result); | ||
done(); | ||
}); | ||
}); | ||
it("Should authenticate OK", function (done) { | ||
dynamics.Authenticate({}, function (err, result) { | ||
assert.ifError(err); | ||
assert.ok(result); | ||
assert.ok(result.auth); | ||
assert.ok(result.username); | ||
assert.equal('string', typeof result.auth); | ||
assert.equal(36, result.auth.length); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
var shouldFailDeletionWithInvalidId = function (dynamics, done) { | ||
@@ -348,2 +388,35 @@ var options = {}; | ||
}); | ||
describe("Method execution with NTLM Auth", function () { | ||
var settings, | ||
dynamics; | ||
before(function () { | ||
settings = settingsForNTLMAuth; | ||
dynamics = new Dynamics(settings); | ||
}); | ||
it("Should retrieve one result", function (done) { | ||
dynamics.Authenticate({}, function (err, authData) { | ||
assert.ok(!err, err); | ||
assert.ok(authData); | ||
assert.ok(authData.auth); | ||
var id = "d4ea3aab-8263-e411-9446-22000b4712e7", | ||
logicalName = "contact"; | ||
dynamics.Retrieve({auth: authData.auth, | ||
"EntityName": logicalName, | ||
"ColumnSet": ["fullname"], | ||
"id": id}, function (err2, result2) { | ||
assert.ok(!err2, err2); | ||
assert.ok(result2); | ||
assert.strictEqual(result2.Envelope.Body.RetrieveResponse.RetrieveResult.Id, id); | ||
assert.strictEqual(result2.Envelope.Body.RetrieveResponse.RetrieveResult.LogicalName, logicalName); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
HTTP dependency
Supply chain riskContains a dependency which resolves to a remote HTTP URL which could be used to inject untrusted code and reduce overall package reliability.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
87444
1590
12
1
2
+ Addedagentkeepalive@0.1.5
+ Addedhttpntlm@https://github.com/kidozen/node-http-ntlm/tarball/master
+ Addedagentkeepalive@0.1.5(transitive)
+ Addedbl@0.9.5(transitive)
+ Addedcaseless@0.6.0(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedoauth-sign@0.4.0(transitive)
+ Addedqs@1.2.2(transitive)
+ Addedreadable-stream@1.0.34(transitive)
+ Addedrequest@2.44.0(transitive)
+ Addedstring_decoder@0.10.31(transitive)
+ Addedstringstream@0.0.6(transitive)
- Removedurl@0.7.9
- Removedoauth-sign@0.3.0(transitive)
- Removedpunycode@1.0.0(transitive)
- Removedqs@0.6.6(transitive)
- Removedquerystring@0.1.0(transitive)
- Removedrequest@2.37.0(transitive)
- Removedurl@0.7.9(transitive)
Updatedrequest@2.44.0