UNPKG

dynamicscrm-api

Version:

Pure SOAP module that allows to invoke Microsoft Dynamics CRM Online services

808 lines (650 loc) 33.9 kB
/*jslint nomen: true, stupid: true */ // module dependencies 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"); var kidoConnector = require("kido-connector"); // this class implements all features var Util = function (settings) { "use strict"; var authenticationTypes = ["live_id", "microsoft_online", "federation", "ntlm"]; // 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."); //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"; // Sets default arguments values settings.timeout = settings.timeout || 15 * 60 * 1000; // default sessions timeout of 15 minutes in ms settings.returnJson = true; settings.port = settings.port || (settings.useHttp ? 80 : 443); var defaultUrlSuffix = ".api.crm.dynamics.com", getHostname = function () { if (settings.domainUrlSuffix) return settings.domain + settings.domainUrlSuffix; return settings.domain + defaultUrlSuffix; }, hostname = getHostname(), organizationPath = "/XRMServices/2011/Organization.svc", 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), cacheAuthByUser = new Cache(settings.timeout), tokensForDeviceCache = new Cache(settings.timeout), endpoints, device, serializer = new Serializer(), fetchEndpoints, loadOrRegisterDevice, getTokenUsingDeviceId, generateRandom, renameKey, executeSoapPost, deepObjCopy, defaultCb, authenticateUsingMicrosoftOnline, authenticateUsingLiveId, authenticateUsingFederation, authenticateUsingNTLM, addSecureOptions, parseResponse, //load templates once microsoftOnlineSaml = fs.readFileSync(__dirname + "/templates/microsoft_online_saml.xml").toString(), authCreateDeviceMessage = fs.readFileSync(__dirname + "/templates/auth_create_device.xml").toString(), authRequestDeviceTokenMessage = fs.readFileSync(__dirname + "/templates/auth_tokenrequest_device.xml").toString(), authRequestSTSTokenMessage = fs.readFileSync(__dirname + "/templates/auth_tokenrequest_liveid.xml").toString(), soapEnvelopeMessage = fs.readFileSync(__dirname + "/templates/soapMessage.xml").toString(), soapHeaderMessage = fs.readFileSync(__dirname + "/templates/soapHeader.xml").toString(), apiRetrieveMultipleMessage = fs.readFileSync(__dirname + "/templates/api_retrievemultiple.xml").toString(), apiRetrieveMessage = fs.readFileSync(__dirname + "/templates/api_retrieve.xml").toString(), apiCreateMessage = fs.readFileSync(__dirname + "/templates/api_create.xml").toString(), apiUpdateMessage = fs.readFileSync(__dirname + "/templates/api_update.xml").toString(), apiDeleteMessage = fs.readFileSync(__dirname + "/templates/api_delete.xml").toString(), apiExecuteMessage = fs.readFileSync(__dirname + "/templates/api_execute.xml").toString(), apiAssociateMessage = fs.readFileSync(__dirname + "/templates/api_asociate.xml").toString(), apiDisassociateMessage = fs.readFileSync(__dirname + "/templates/api_disassociate.xml").toString(), faultTextXpath = "//*[local-name()='Fault']/*[local-name()='Reason']/*[local-name()='Text']/text()", importLocationXpath = "//*[local-name()='import' and namespace-uri()='http://schemas.xmlsoap.org/wsdl/']/@location", authenticationTypeXpath = "//*[local-name()='Authentication' and namespace-uri()='http://schemas.microsoft.com/xrm/2011/Contracts/Services']/text()", issuerAddressXpath = "//*[local-name()='SignedSupportingTokens']/*[local-name()='Policy']/*[local-name()='IssuedToken']/*[local-name()='Issuer']/*[local-name()='Address']/text()", liveAppliesToXpath = "//*[local-name()='LiveIdAppliesTo']/text()"; /* * Default callback function, it only throws an exception if an error was received. */ defaultCb = function (err) { if (err) throw err; }; 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) { if (endpoints) return cb(null, endpoints); var options = { uri: settings.useHttp ? "http://" : "https://" + hostname + ":" + settings.port + organizationPath + "?wsdl", }; addSecureOptions(options); request(options, function (err, res, body) { if (err) return cb(err); var resXml = domParser.parseFromString(body), fault = xpath.select(faultTextXpath, resXml), location, opts; if (fault.length > 0) return cb(new Error(fault.toString()), null); location = xpath.select(importLocationXpath, resXml) .map(function (attr) { return attr.value; })[0]; if (location.length > 0) { opts = { url: location }; addSecureOptions(opts); request(opts, function (err, res, body) { if (err) return cb(err); var resXmlImport, faultImport, authenticationType, issuerAddress, liveAppliesTo, identifier, keyType, keySize, requireClientEntropy; 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 = (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); }); } }); }; loadOrRegisterDevice = function (options, cb) { if (device) return cb(null, device); var username = generateRandom(24, "aA#"), password = generateRandom(24, "aA#"); authCreateDeviceMessage = authCreateDeviceMessage .replace("{newguid}", uuid.v4()) .replace("{username}", username) .replace("{password}", password); options = { method: "POST", uri: options.DeviceAddUrl, body: authCreateDeviceMessage, headers: { "Content-Type": "application/soap+xml; charset=UTF-8", "Content-Length": authCreateDeviceMessage.length } }; addSecureOptions(options); request(options, function (err, res, body) { if (err) return cb(err); var resXml = domParser.parseFromString(body), fault = xpath.select(faultTextXpath, resXml), puid; if (fault.length > 0) return cb(new Error(fault.toString()), null); puid = xpath.select("/DeviceAddResponse/puid/text()", resXml).toString(); device = { deviceUsername: username, devicePassword: password, puid: puid }; return cb(null, device); }); }; getTokenUsingDeviceId = function (options, cb) { var timeCreated = new Date(), timeExpires = new Date(timeCreated.getTime() + settings.timeout), cipher = tokensForDeviceCache.get("auth_tokenrequest_device"), requestOptions; if (cipher) return cb(null, cipher); authRequestDeviceTokenMessage = authRequestDeviceTokenMessage .replace("{messageuuid}", uuid.v4()) .replace("{timeCreated}", timeCreated.toISOString()) .replace("{timeExpires}", timeExpires.toISOString()) .replace("{issuer}", options.IssuerAddress) .replace("{liveIdAppliesTo}", options.LiveIdAppliesTo) .replace("{deviceUsername}", options.DeviceInfo.deviceUsername) .replace("{devicePassword}", options.DeviceInfo.devicePassword); requestOptions = { method: "POST", uri: options.IssuerAddress, body: authRequestDeviceTokenMessage, headers: { "Content-Type": "application/soap+xml; charset=UTF-8", "Content-Length": Buffer.byteLength(authRequestDeviceTokenMessage) } }; addSecureOptions(requestOptions); request(requestOptions, function (err, res, body) { if (err) return cb(err); var resXml = domParser.parseFromString(body), fault = xpath.select(faultTextXpath, resXml), cipherValue; 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}; tokensForDeviceCache.set("auth_tokenrequest_device", cipher); return cb(null, cipher); }); }; generateRandom = function (length, chars) { 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 += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; for (i = length; i > 0; i = i - 1) result += mask[Math.round(Math.random() * (mask.length - 1))]; return result; }; renameKey = function (objInd, prefixes) { var rk = objInd; prefixes.forEach(function (p) { if (objInd.indexOf(p) === 0) rk = objInd.replace(p, ""); }); return rk; }; 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) { kidoConnector.isHostAllowed(hostname, function (err, allowed) { if (err) return cb(err); if (!allowed) return cb(new Error("The hostname is not allowed")); var timeCreated = new Date(), timeExpires = new Date(timeCreated.getTime() + 5 * 60000), requestOptions, soapHeader, xmlrequestbody, soapPostMessage, security, ntlmOptions, type1msg, agent, reqOptions, url, httpHeaders = {}; xmlrequestbody = template.replace("{requetbody}", body); if (settings.authType === "ntlm") { soapPostMessage = soapEnvelopeMessage .replace("{envelopeNS}", "http://schemas.xmlsoap.org/soap/envelope/") .replace("{header}", "") .replace("{body}", xmlrequestbody); url = (settings.useHttp ? "http://" : "https://") + hostname + ":" + settings.port + "/" + settings.organizationName + organizationPath + "/web"; 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; ntlmOptions = { username: options.username || settings.username, password: options.password || settings.password, workstation: options.workstation || settings.workstation || "", domain: options.ntlmDomain || settings.ntlmDomain || "" }; type1msg = ntlm.createType1Message(ntlmOptions); agent = settings.useHttp ? new Agentkeepalive() : new Agentkeepalive.HttpsAgent(); reqOptions = { method: options.method || "GET", url: url, headers: { Authorization: type1msg, }, agent: agent, timeout: settings.requestTimeout }; 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); }); }); } else { soapHeader = soapHeaderMessage .replace("{action}", action) .replace("{messageid}", uuid.v4()) .replace("{crmurl}", organizationServiceEndpoint); 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>"; 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 + organizationPath; soapPostMessage = soapEnvelopeMessage .replace("{envelopeNS}", "http://www.w3.org/2003/05/soap-envelope") .replace("{header}", soapHeader) .replace("{body}", xmlrequestbody); 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); }); } }); }; deepObjCopy = function (dupeObj, pfxs) { var retObj = {}, objInd, rk; 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; } } } return retObj; }; authenticateUsingMicrosoftOnline = function (opts, cb) { var loginEndpoint = "urn:crmapac:dynamics.com", username = opts.username, password = opts.password, host = "login.microsoftonline.com", path = "/extSTS.srf", //build full name condition for XPath expression name = function (name) { return "/*[name(.)='" + name + "']"; }, samlRequest = microsoftOnlineSaml .replace("{username}", username) .replace("{password}", password) .replace("{toMustUnderstand}", "https://" + host + path) .replace("{endpoint}", loginEndpoint), options = { method: "POST", uri: "https://" + host + path, body: samlRequest, headers: { "Content-Length": Buffer.byteLength(samlRequest) } }; addSecureOptions(options); request(options, function (err, res, body) { if (err) return cb(err); 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); if (fault.length > 0) return cb(new Error(fault.toString())); return cb(null, resXml); }); }; authenticateUsingLiveId = function (options, cb) { var authOptions = options; fetchEndpoints(function (err, result) { if (err) return cb(err); authOptions = result; authOptions.username = options.username; authOptions.password = options.password; loadOrRegisterDevice(authOptions, function (err, result) { if (err) return cb(err); authOptions.DeviceInfo = result; getTokenUsingDeviceId(authOptions, function (err, result) { if (err) return cb(err); var timeCreated = new Date(), timeExpires = new Date(timeCreated.getTime() + settings.timeout), requestOptions; authOptions.cipherValue = result.CipherValue; authRequestSTSTokenMessage = authRequestSTSTokenMessage .replace("{messageuuid}", uuid.v4()) .replace("{created}", timeCreated.toISOString()) .replace("{expires}", timeExpires.toISOString()) .replace("{issuer}", authOptions.IssuerAddress) .replace("{cipher}", authOptions.cipherValue) .replace("{username}", authOptions.username) .replace("{password}", authOptions.password); requestOptions = { method: "POST", uri: authOptions.IssuerAddress, body: authRequestSTSTokenMessage, headers: { "Content-Type": "application/soap+xml; charset=UTF-8", "Content-Length": Buffer.byteLength(authRequestSTSTokenMessage) } }; addSecureOptions(requestOptions); request(requestOptions, function (err, res, body) { if (err) return cb(err); var resXml = domParser.parseFromString(body), fault = xpath.select(faultTextXpath, resXml), fullMessage, faultDetailsXpath, faultDetails; if (fault.length <= 0) return cb(null, resXml); 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); fullMessage = fullMessage + ". Details:" + data; return cb(new Error(fullMessage), null); }); }); }); }); }); }; authenticateUsingFederation = function (authOptions, cb) { fetchEndpoints(function (err, wsdlInfo) { if (err) return cb(err); var wstrustFlowOptions, flow, identifier = wsdlInfo.Identifier.replace("http://", "https://"), keyTypeUnsupported = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey"; if (wsdlInfo.KeyType === keyTypeUnsupported) wsdlInfo.KeyType = "http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey"; wstrustFlowOptions = { wstrustEndpoint: identifier + "/2005/usernamemixed", username: authOptions.username, password: authOptions.password, appliesTo: organizationServiceEndpoint, useClientEntropy: wsdlInfo.RequireClientEntropy, keyType: wsdlInfo.KeyType, keySize: wsdlInfo.KeySize }; flow = new WSTrustFlow(wstrustFlowOptions); flow.getWSSecurityHeader(function (err, header) { if (err) return cb(err); return cb(null, header); }); }); }; 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) { kidoConnector.isHostAllowed(hostname, function (err, allowed) { if (err) return cb(err); if (!allowed) return cb(new Error("The hostname is not allowed")); var responseXMLCB = function (err, resXml) { if (err) return cb(err); var token = xpath.select("//*[local-name()='EncryptedData']", resXml).toString(), authToken = uuid.v4(), authItem = {token: token}; cacheTokenByAuth.set(authToken, authItem); cacheAuthByUser.set(options.username, authToken); return cb(null, {auth: authToken}); }, federationCB = function (err, header) { if (err) return cb(err); var authToken = uuid.v4(), authItem = {header: header}; cacheTokenByAuth.set(authToken, authItem); cacheAuthByUser.set(options.username, authToken); return cb(null, {auth: authToken}); }; // handles optional 'options' argument if (!cb && typeof options === "function") { cb = options; options = {}; } // sets default values cb = cb || defaultCb; options = options || {}; // validates arguments values if (typeof options !== "object") return cb(new Error("'options' argument is missing or invalid.")); // 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); }); }; this.authenticate = this.Authenticate; /* RetrieveMultiple public and private methods */ this.RetrieveMultiple = function (options, cb) { this.executePost(options, "RetrieveMultiple", apiRetrieveMultipleMessage, serializer.toXmlRetrieveMultiple(options), cb); }; /* Retrieve public and private methods */ this.Retrieve = function (options, cb) { this.executePost(options, "Retrieve", apiRetrieveMessage, serializer.toXmlRetrieve(options), cb); }; /* Create public and private methods */ this.Create = function (options, cb) { this.executePost(options, "Create", apiCreateMessage, serializer.toXmlCreateUpdate(options), cb); }; /* Update public and private methods */ this.Update = function (options, cb) { this.executePost(options, "Update", apiUpdateMessage, serializer.toXmlCreateUpdate(options), cb); }; /* Update public and private methods */ this.Delete = function (options, cb) { this.executePost(options, "Delete", apiDeleteMessage, serializer.toXmlDelete(options), cb); }; /* Execute public and private methods */ this.Execute = function (options, cb) { this.executePost(options, "Execute", apiExecuteMessage, serializer.toXmlExecute(options), cb); }; /* Associate public and private methods */ this.Associate = function (options, cb) { this.executePost(options, "Associate", apiAssociateMessage, serializer.toXmlAssociate(options), cb); }; this.Disassociate = function (options, cb) { this.executePost(options, "Disassociate", apiDisassociateMessage, serializer.toXmlAssociate(options), cb); }; this.executePost = function (options, action, template, body, cb) { var authItem; // handles optional 'options' argument if (!cb && typeof options === "function") { cb = options; options = {}; } // sets default values cb = cb || defaultCb; options = options || {}; 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) { 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 executeSoapPost(options, action, template, body, cb); } else this.Authenticate(options, function (err, data) { if (err) return cb(err); authItem = cacheTokenByAuth.get(data.auth); options.encryptedData = authItem.token; //For LiveId an MSOnline options.header = authItem.header; //For Federation executeSoapPost(options, action, template, body, cb); }); }; }; module.exports = Util;