Comparing version 0.4.2 to 0.4.4
@@ -0,3 +1,29 @@ | ||
0.4.4 / 2014-04-16 | ||
================= | ||
* Added namespace prefixes to SOAP headers. #307 | ||
* Provided more documentation around security protocols in the README. #321 | ||
* Added lodash. #321 | ||
* Added a default parameter to ClientSSLSecurity. #321 | ||
* Fix to reset the generated namespace number. #308 | ||
* Fixed maximum callstack errors on certain responses. #257 | ||
0.4.3 / 2014-04-07 | ||
================= | ||
* Refactored WS-security. small modifications to pull #275 | ||
* Updated readme to add documentation for passing options to a client request | ||
* Added null check for portType and methods[methodname].output | ||
* Fixed issue where requests that included compex types led to invalid request XML. | ||
* Support for attributes array elements and support for complex extensions with array elements. | ||
* Make sure callback is done asynchronously for a cached wsdl | ||
* Added WSDL inheritance support (#133). | ||
0.4.2 / 2014-03-13 | ||
================= | ||
* Added the ability to inspect and clear soap headers. | ||
* Reducing test wsdl size. | ||
* No longer prefixing elements with a default namespace prefix i.e. xmlns. | ||
0.4.1 / 2014-03-04 | ||
================= | ||
Note: an error occurred publishing this version to npm. This version was tagged, so it can be referrenced via git. | ||
* package; increased minor version to 0.4.1 | ||
@@ -4,0 +30,0 @@ * Adding an npmignore on test/ |
@@ -90,3 +90,3 @@ /* | ||
var self = this; | ||
return function(args, callback, options) { | ||
return function(args, callback, options, extraHeaders) { | ||
if (typeof args === 'function') { | ||
@@ -98,7 +98,7 @@ callback = args; | ||
callback(error, result, raw); | ||
}, options); | ||
}, options, extraHeaders); | ||
}; | ||
}; | ||
Client.prototype._invoke = function(method, args, location, callback, options) { | ||
Client.prototype._invoke = function(method, args, location, callback, options, extraHeaders) { | ||
var self = this, | ||
@@ -123,2 +123,5 @@ name = method.$name, | ||
//Add extra headers | ||
for (var attr in extraHeaders) { headers[attr] = extraHeaders[attr]; } | ||
// Allow the security object to add headers | ||
@@ -142,2 +145,3 @@ if (self.security && self.security.addHeaders) | ||
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " + | ||
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + | ||
encoding + | ||
@@ -154,2 +158,3 @@ this.wsdl.xmlnsInEnvelope + '>' + | ||
self.lastMessage = message; | ||
self.lastRequest = xml; | ||
@@ -162,2 +167,3 @@ | ||
self.lastResponseHeaders = response && response.headers; | ||
if (err) { | ||
@@ -164,0 +170,0 @@ callback(err); |
101
lib/soap.js
@@ -10,7 +10,6 @@ /* | ||
Server = require('./server').Server, | ||
security = require('./security'), | ||
passwordDigest = require('./utils').passwordDigest, | ||
open_wsdl = require('./wsdl').open_wsdl, | ||
crypto = require('crypto'), | ||
WSDL = require('./wsdl').WSDL, | ||
https = require('https'), | ||
fs = require('fs'); | ||
WSDL = require('./wsdl').WSDL; | ||
@@ -28,3 +27,5 @@ var WSDL = require('./wsdl').WSDL; | ||
if (wsdl) { | ||
callback(null, wsdl); | ||
process.nextTick(function() { | ||
callback(null, wsdl); | ||
}); | ||
} | ||
@@ -69,88 +70,6 @@ else { | ||
function BasicAuthSecurity(username, password) { | ||
this._username = username; | ||
this._password = password; | ||
} | ||
BasicAuthSecurity.prototype.addHeaders = function(headers) { | ||
headers.Authorization = "Basic " + new Buffer((this._username + ':' + this._password) || '').toString('base64'); | ||
}; | ||
BasicAuthSecurity.prototype.toXML = function() { | ||
return ""; | ||
}; | ||
function ClientSSLSecurity(keyPath, certPath) { | ||
this.key = fs.readFileSync(keyPath); | ||
this.cert = fs.readFileSync(certPath); | ||
} | ||
ClientSSLSecurity.prototype.toXML = function(headers) { | ||
return ""; | ||
}; | ||
ClientSSLSecurity.prototype.addOptions = function(options) { | ||
options.key = this.key; | ||
options.cert = this.cert; | ||
options.agent = new https.Agent(options); | ||
}; | ||
function WSSecurity(username, password, passwordType) { | ||
this._username = username; | ||
this._password = password; | ||
this._passwordType = passwordType || 'PasswordText'; | ||
} | ||
var passwordDigest = function(nonce, created, password) { | ||
// digest = base64 ( sha1 ( nonce + created + password ) ) | ||
var pwHash = crypto.createHash('sha1'); | ||
var rawNonce = new Buffer(nonce || '', 'base64').toString('binary'); | ||
pwHash.update(rawNonce + created + password); | ||
var passwordDigest = pwHash.digest('base64'); | ||
return passwordDigest; | ||
}; | ||
WSSecurity.prototype.toXML = function() { | ||
// avoid dependency on date formatting libraries | ||
function getDate(d) { | ||
function pad(n) { | ||
return n < 10 ? '0' + n : n; | ||
} | ||
return d.getUTCFullYear() + '-' | ||
+ pad(d.getUTCMonth() + 1) + '-' | ||
+ pad(d.getUTCDate()) + 'T' | ||
+ pad(d.getUTCHours()) + ':' | ||
+ pad(d.getUTCMinutes()) + ':' | ||
+ pad(d.getUTCSeconds()) + 'Z'; | ||
} | ||
var now = new Date(); | ||
var created = getDate(now); | ||
var expires = getDate(new Date(now.getTime() + (1000 * 600))); | ||
// nonce = base64 ( sha1 ( created + random ) ) | ||
var nHash = crypto.createHash('sha1'); | ||
nHash.update(created + Math.random()); | ||
var nonce = nHash.digest('base64'); | ||
return "<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" + | ||
"<wsu:Timestamp wsu:Id=\"Timestamp-" + created + "\">" + | ||
"<wsu:Created>" + created + "</wsu:Created>" + | ||
"<wsu:Expires>" + expires + "</wsu:Expires>" + | ||
"</wsu:Timestamp>" + | ||
"<wsse:UsernameToken xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"SecurityToken-" + created + "\">" + | ||
"<wsse:Username>" + this._username + "</wsse:Username>" + | ||
(this._passwordType === 'PasswordText' ? | ||
"<wsse:Password>" + this._password + "</wsse:Password>" | ||
: | ||
"<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">" + passwordDigest(nonce, created, this._password) + "</wsse:Password>" | ||
) + | ||
"<wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" + nonce + "</wsse:Nonce>" + | ||
"<wsu:Created>" + created + "</wsu:Created>" + | ||
"</wsse:UsernameToken>" + | ||
"</wsse:Security>"; | ||
}; | ||
exports.BasicAuthSecurity = BasicAuthSecurity; | ||
exports.WSSecurity = WSSecurity; | ||
exports.ClientSSLSecurity = ClientSSLSecurity; | ||
exports.security = security; | ||
exports.BasicAuthSecurity = security.BasicAuthSecurity; | ||
exports.WSSecurity = security.WSSecurity; | ||
exports.ClientSSLSecurity = security.ClientSSLSecurity; | ||
exports.createClient = createClient; | ||
@@ -157,0 +76,0 @@ exports.passwordDigest = passwordDigest; |
149
lib/wsdl.js
@@ -480,24 +480,24 @@ /* | ||
children = this.children; | ||
if (portType){ | ||
portType.postProcess(definitions); | ||
this.methods = portType.methods; | ||
// delete portType.methods; both binding and portType should keep the same set of operations | ||
portType.postProcess(definitions); | ||
this.methods = portType.methods; | ||
// delete portType.methods; both binding and portType should keep the same set of operations | ||
for (var i = 0, child; child = children[i]; i++) { | ||
if (child.name !== 'operation') | ||
continue; | ||
child.postProcess(definitions, 'binding'); | ||
children.splice(i--, 1); | ||
child.style || (child.style = style); | ||
var method = this.methods[child.$name]; | ||
method.style = child.style; | ||
method.soapAction = child.soapAction; | ||
method.inputSoap = child.input || null; | ||
method.outputSoap = child.output || null; | ||
method.inputSoap && method.inputSoap.deleteFixedAttrs(); | ||
method.outputSoap && method.outputSoap.deleteFixedAttrs(); | ||
// delete method.$name; client will use it to make right request for top element name in body | ||
// method.deleteFixedAttrs(); why ??? | ||
for (var i = 0, child; child = children[i]; i++) { | ||
if (child.name !== 'operation') | ||
continue; | ||
child.postProcess(definitions, 'binding'); | ||
children.splice(i--, 1); | ||
child.style || (child.style = style); | ||
var method = this.methods[child.$name]; | ||
method.style = child.style; | ||
method.soapAction = child.soapAction; | ||
method.inputSoap = child.input || null; | ||
method.outputSoap = child.output || null; | ||
method.inputSoap && method.inputSoap.deleteFixedAttrs(); | ||
method.outputSoap && method.outputSoap.deleteFixedAttrs(); | ||
// delete method.$name; client will use it to make right request for top element name in body | ||
// method.deleteFixedAttrs(); why ??? | ||
} | ||
} | ||
delete this.$name; | ||
@@ -583,3 +583,13 @@ delete this.$type; | ||
if (typeElement && !(typeName in Primitives)) { | ||
element[name] = typeElement.description(definitions); | ||
if (!(typeName in definitions.descriptions.types)) { | ||
var t = {}; | ||
definitions.descriptions.types[typeName] = t; | ||
element[name] = t = typeElement.description(definitions); | ||
definitions.descriptions.types[typeName] = element[name]; | ||
} | ||
else { | ||
element[name] = definitions.descriptions.types[typeName]; | ||
} | ||
} | ||
@@ -710,3 +720,5 @@ else | ||
var inputName = methods[methodName].input.$name; | ||
var outputName = methods[methodName].output.$name; | ||
var outputName=""; | ||
if(methods[methodName].output ) | ||
outputName = methods[methodName].output.$name; | ||
topEls[inputName] = {"methodName": methodName, "outputName": outputName}; | ||
@@ -990,2 +1002,5 @@ } | ||
var parameterTypeObj = type ? this.findParameterObject(xmlns, type) : null; | ||
if (this.namespaceNumber) { | ||
this.namespaceNumber = 0; | ||
} | ||
return this.objectToXML(args, null, ns, xmlns, true, null, parameterTypeObj); | ||
@@ -1009,3 +1024,3 @@ }; | ||
parts.push(['<', key, '>'].join('')); | ||
parts.push((typeof value === 'object') ? this.objectToXML(value) : xmlEscape(value)); | ||
parts.push((typeof value === 'object') ? this.objectToXML(value, key, namespace, xmlns) : xmlEscape(value)); | ||
parts.push(['</', key, '>'].join('')); | ||
@@ -1033,12 +1048,11 @@ } | ||
} | ||
var ns = namespace && namespace !== 'xmlns' ? namespace + ':' : ''; | ||
var ns = namespace && namespace !== 'xmlns' ? (namespace.indexOf(":") === -1 ? namespace + ':' : namespace) : ''; | ||
if (Array.isArray(obj)) { | ||
for (var i = 0, item; item = obj[i]; i++) { | ||
if (i > 0) { | ||
parts.push(['</', ns, name, '>'].join('')); | ||
parts.push(['<', ns, name, xmlnsAttrib, '>'].join('')); | ||
} | ||
parts.push(self.objectToXML(item, name, namespace, xmlns)); | ||
var arrayAttr = self.processAttributes(item); | ||
parts.push(['<', ns, name, arrayAttr, xmlnsAttrib, '>'].join('')); | ||
parts.push(self.objectToXML(item, name, namespace, xmlns, false, null, null, ancXmlns)); | ||
parts.push(['</', ns, name, '>'].join('')); | ||
} | ||
@@ -1053,9 +1067,23 @@ } else if (typeof obj === 'object') { | ||
var child = obj[name]; | ||
if (child.attributes && child.attributes.xsi_type) { | ||
var xsiType = child.attributes.xsi_type; | ||
// Generate a new namespace for complex extension if one not provided | ||
if (!xsiType.namespace) { | ||
if (self.namespaceNumber) { | ||
self.namespaceNumber++; | ||
} else { | ||
self.namespaceNumber = 1; | ||
} | ||
xsiType.namespace = 'ns' + self.namespaceNumber; | ||
} | ||
} | ||
var attr = self.processAttributes(child); | ||
parts.push(['<', ns, name, attr, xmlnsAttrib, '>'].join('')); | ||
var value = ''; | ||
if (first) { | ||
parts.push(self.objectToXML(child, name, namespace, xmlns, false, null, parameterTypeObject, ancXmlns)); | ||
value = self.objectToXML(child, name, namespace, xmlns, false, null, parameterTypeObject, ancXmlns); | ||
} else { | ||
if (self.definitions.schemas) { | ||
@@ -1066,8 +1094,11 @@ var schema = this.definitions.schemas[xmlns]; | ||
var childParameterTypeObject = self.findChildParameterObject(parameterTypeObject, name); | ||
if (childParameterTypeObject) { | ||
//find sub namespace if not a primitive | ||
if (childParameterTypeObject && childParameterTypeObject.$type && (childParameterTypeObject.$type.indexOf('xsd') === -1)) { | ||
var childParameterType = childParameterTypeObject.$type; | ||
var childNamespace = ''; | ||
var childName = ''; | ||
if (childParameterType.indexOf(':') !== -1) { | ||
childNamespace = childParameterType.substring(0, childParameterType.indexOf(':')); | ||
childName = childParameterType.substring(childParameterType.indexOf(':') + 1); | ||
} | ||
@@ -1082,11 +1113,27 @@ var childXmlns = schema.xmlns[childNamespace]; | ||
parts.push(self.objectToXML(child, name, childNamespace, childXmlns, false, childXmlnsAttrib, childParameterTypeObject)); | ||
var completeChildParameterTypeObject = self.findChildParameterObjectFromSchema(childName, childXmlns) || childParameterTypeObject; | ||
value = self.objectToXML(child, name, childNamespace, childXmlns, false, childXmlnsAttrib, completeChildParameterTypeObject, ancXmlns); | ||
} else if (obj.attributes && obj.attributes.xsi_type) { //if parent object has complex type defined and child not found in parent | ||
var completeChildParamTypeObject = self.findChildParameterObjectFromSchema(obj.attributes.xsi_type.type, obj.attributes.xsi_type.xmlns); | ||
ns = obj.attributes.xsi_type.namespace + ':'; | ||
ancXmlns.push(obj.attributes.xsi_type.xmlns); | ||
value = self.objectToXML(child, name, obj.attributes.xsi_type.namespace, obj.attributes.xsi_type.xmlns, false, null, null, ancXmlns); | ||
} else { | ||
parts.push(self.objectToXML(child, name, namespace, xmlns)); | ||
value = self.objectToXML(child, name, namespace, xmlns, false, null, null, ancXmlns); | ||
} | ||
} else { | ||
value = self.objectToXML(child, name, namespace, xmlns, false, null, null, ancXmlns); | ||
} | ||
} | ||
} | ||
if (!Array.isArray(child)) { | ||
parts.push(['<', ns, name, attr, xmlnsAttrib, '>'].join('')); | ||
} | ||
parts.push(['</', ns, name, '>'].join('')); | ||
parts.push(value); | ||
if (!Array.isArray(child)) { | ||
parts.push(['</', ns, name, '>'].join('')); | ||
} | ||
} | ||
@@ -1103,3 +1150,12 @@ } else if (obj !== undefined) { | ||
for (var attrKey in child.attributes) { | ||
attr += ' ' + attrKey + '="' + xmlEscape(child.attributes[attrKey]) + '"'; | ||
//handle complex extension separately | ||
if (attrKey === 'xsi_type') { | ||
var attrValue = child.attributes[attrKey]; | ||
attr += ' xsi:type="' + attrValue.namespace + ':' + attrValue.type + '"'; | ||
attr += ' xmlns:' + attrValue.namespace + '="' + attrValue.xmlns + '"'; | ||
continue; | ||
} else { | ||
attr += ' ' + attrKey + '="' + xmlEscape(child.attributes[attrKey]) + '"'; | ||
} | ||
} | ||
@@ -1111,2 +1167,15 @@ } | ||
WSDL.prototype.findChildParameterObjectFromSchema = function(name, xmlns) { | ||
if (!this.definitions.schemas || !name || !xmlns) { | ||
return null; | ||
} | ||
var schema = this.definitions.schemas[xmlns]; | ||
if (!schema || !schema.complexTypes) { | ||
return null; | ||
} | ||
return schema.complexTypes[name]; | ||
}; | ||
WSDL.prototype.findChildParameterObject = function(parameterTypeObj, childName) { | ||
@@ -1183,2 +1252,5 @@ if (!parameterTypeObj || !childName) { | ||
this.definitions = this._parse(xml); | ||
this.definitions.descriptions = { | ||
types:{} | ||
}; | ||
this.xml = xml; | ||
@@ -1262,2 +1334,1 @@ }; | ||
{ | ||
"name": "soap", | ||
"version": "0.4.2", | ||
"version": "0.4.4", | ||
"description": "A minimal node SOAP client", | ||
@@ -11,3 +11,4 @@ "engines": { | ||
"sax": ">=0.6", | ||
"request": ">=2.9.0" | ||
"request": ">=2.9.0", | ||
"lodash": "~2.4.1" | ||
}, | ||
@@ -23,3 +24,3 @@ "repository": { | ||
"scripts": { | ||
"mocha": "mocha -R spec -u exports test/*-test.js", | ||
"mocha": "mocha test/*-test.js test/security/*.js", | ||
"jshint": "jshint index.js lib test", | ||
@@ -40,4 +41,5 @@ "test": "npm run-script jshint && npm run-script mocha" | ||
"jshint": "~2.4.2", | ||
"glob": "~3.2.8" | ||
"glob": "~3.2.8", | ||
"should": "~3.3.0" | ||
} | ||
} |
@@ -48,3 +48,3 @@ This module lets you connect to web services using SOAP. It also provides a server that allows you to run your own SOAP services. | ||
// This is how to define an asynchronous function. | ||
// This is how to define an asynchronous function. | ||
MyAsyncFunction: function(args, callback) { | ||
@@ -130,5 +130,34 @@ // do some work | ||
### Client.setSecurity(security) - use the specified security protocol (see WSSecurity below) | ||
### Client.setSecurity(security) - use the specified security protocol | ||
`node-soap` has several default security protocols. You can easily add your own | ||
as well. The interface is quite simple. Each protocol defines 2 methods: | ||
* addOptions - a method that accepts an options arg that is eventually passed directly to `request` | ||
* toXML - a method that reurns a string of XML. | ||
By default there are 3 protocols: | ||
####BasicAuthSecurity | ||
``` javascript | ||
client.setSecurity(new soap.BasicAuthSecurity('username', 'password')); | ||
``` | ||
####ClientSSLSecurity | ||
_Note_: If you run into issues using this protocol, consider passing these options | ||
as default request options to the constructor: | ||
* rejectUnauthorized: false | ||
* strictSSL: false | ||
* secureOptions: constants.SSL_OP_NO_TLSv1_2//this is likely needed for node >= 10.0 | ||
``` javascript | ||
client.setSecurity(new soap.ClientSSLSecurity( | ||
'/path/to/key' | ||
, '/path/to/cert' | ||
, {/*default request options*/} | ||
)); | ||
``` | ||
####WSSecurity | ||
``` javascript | ||
client.setSecurity(new WSSecurity('username', 'password')) | ||
@@ -144,3 +173,3 @@ ``` | ||
``` | ||
### Client.*service*.*port*.*method*(args, callback) - call a *method* using a specific *service* and *port* | ||
### Client.*service*.*port*.*method*(args, callback[, options]) - call a *method* using a specific *service* and *port* | ||
@@ -152,2 +181,11 @@ ``` javascript | ||
``` | ||
+#### Options (optional) | ||
- Accepts any option that the request module accepts, see [here.](https://github.com/mikeal/request) | ||
- For example, you could set a timeout of 5 seconds on the request like this: | ||
``` javascript | ||
client.MyService.MyPort.MyFunction({name: 'value'}, function(err, result) { | ||
// result is a javascript object | ||
}, {timeout: 5000}) | ||
``` | ||
### Client.*addSoapHeader*(soapHeader[, name, namespace, xmlns]) - add soapHeader to soap:Header node | ||
@@ -154,0 +192,0 @@ #### Options |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
71233
19
1748
207
1
3
4
+ Addedlodash@~2.4.1
+ Addedlodash@2.4.2(transitive)