@ripeworks/xrm-api
Version:
Module that allows to invoke Microsoft Dynamics CRM services
251 lines (202 loc) • 9.63 kB
JavaScript
var httpntlm = require("httpntlm");
var cookie = require("cookie");
var uuid = require("uuid");
var WSTrustFlow = require("./ws-security/wsTrustFlow.js");
var Auth = function (settings) {
var 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);
});
};
var authenticateUsingLiveId = function (options, cb) {
var authOptions = options;
settings.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);
});
});
});
});
});
};
var authenticateUsingFederation = function (authOptions, cb) {
settings.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: settings.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);
});
});
};
var authenticateUsingNTLM = function (options, cb) {
var authOptions = {
url: (settings.useHttp ? "http://" : "https://") + settings.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": settings.userAgent
}
};
httpntlm.get(authOptions, function (err, res) {
if (err) {
return cb(err);
}
if (res.cookies.length === 0) {
return cb(new Error("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
};
settings.cacheTokenByAuth.set(authToken, session);
settings.cacheAuthByUser.set(options.username, authToken);
return cb(null, {auth: authToken, username: options.username});
});
};
this.Do = function (options, cb) {
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};
settings.cacheTokenByAuth.set(authToken, authItem);
settings.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};
settings.cacheTokenByAuth.set(authToken, authItem);
settings.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);
} else {
// Default Live Id
authenticateUsingLiveId(options, responseXMLCB);
}
};
}
module.exports = Auth;