2

I'm using this library in node.js to make SOAP API calls: https://github.com/vpulim/node-soap

Here are the wsdl and xsd files for reference: https://drive.google.com/open?id=1ha7CqyJBnkISsI0wafwVrV4qG813ML64

These calls are being made to Cisco CUCM (VOIP application server). To give context, all it's trying to do is get the details for a specific phone with getPhone method, with device name SEPAAAABBBBCCCC in this example. Here is the code:

var soap = require("soap");

var url = "AXLAPI.wsdl";
var auth = "Basic " + new Buffer("axl" + ":" + "cisco").toString("base64");
var args = {
name: "SEPAAAABBBBCCCC"
};

soap.createClient(url, function(err, client) {
   client.setEndpoint("https://192.168.138.15:8443/axl");
   client.addHttpHeader("Authorization", auth);
   client.setSecurity(new soap.ClientSSLSecurity(undefined,undefined, undefined, {rejectUnauthorized: false,},));

   client.getPhone(args, function(err,result) {
       if (err) {
           console.log("soap api error is: " + err);
           console.log("last request: " + client.lastRequest);
           process.exit(1);
       }

   console.log("result is: " + result);
   });

   if (err) {
      console.log("soap client error is: " + err);
   }
});

It's not working. The error that is printed to the console log is not very useful. It simply states: "Error: Cannot parse response."

The library has function where you can log the XML request (client.lastRequest in the code). This is the output I get:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s0="http://www.cisco.com/AXLAPIService/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:xsd1="http://www.cisco.com/AXL/API/11.5">
<soap:Header></soap:Header>
    <soap:Body>
        <getPhoneIn>
            <name>SEPAAAABBBBCCCC</name>
        </getPhoneIn>
    </soap:Body>
</soap:Envelope>

But this is not correct. If I use postman to manually send an AXL request with the above body I get this fault string:

<faultstring>error: The document is not a getPhone@http://www.cisco.com/AXL/API/11.5: document element mismatch got getPhoneIn</faultstring>

I'm not sure how the library is getting a getPhoneIn element as it's not valid per the WSDL file. It should be getPhone.

EDIT: I'm posting the solution I used so it can help others (thanks to the excellent support and input from Terry)

"use-strict";

var soap = require("strong-soap").soap;
var request = require("request");


var url = "AXLAPI.wsdl";

var auth = "Basic " + new Buffer("axl" + ":" + "cisco").toString("base64");

var requestArgs = {
    name:  "SEPAAAABBBBCCCC"
};

var specialRequest = request.defaults({ strictSSL: false });

var options = { 
              endpoint:  "https://192.168.138.15:8443/axl/",
              request: specialRequest
          };

soap.createClient(url, options, function(err, client) { 
    client.addHttpHeader("Authorization", auth);

    var method = client.getPhone;
    method(requestArgs, function (err, result, envelope, soapHeader) {
        console.log("Result: " + JSON.stringify(result));
    });
});
Mr Wannabe
  • 143
  • 10

1 Answers1

1

I'm not sure how much this helps you, but I tested this with the strong-soap library, you get the following envelope:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header/>
  <soap:Body>
    <ns1:getPhone xmlns:ns1="http://www.cisco.com/AXL/API/11.5">
      <name>test_name</name>
    </ns1:getPhone>
  </soap:Body>
</soap:Envelope>

Code is below:

"use strict";

var soap = require('strong-soap').soap;
var request = require("request");

// wsdl of the web service this client is going to invoke. For local wsdl you can use, url = './wsdls/stockquote.wsdl'
var url = './AXLAPI.wsdl';

// Use this to intercept calls from strong-soap if we want to fiddle with anything..
var stubRequest = (requestOptions, callbackFn) => { 
    console.log("Strong-Soap-Call-To-Request: ", requestOptions, callbackFn);
    // Here you can change the HTTP method.. but don't do this if it's pulling the WSDL
    if ((requestOptions.uri.search || "").toLowerCase() !== "?wsdl") {
        console.log("Strong-Soap-Call-To-Request: Changing HTTP method");
        requestOptions.method = 'POST';
    }

    console.log("Strong-Soap-Call-To-Request-After-Mods: ", requestOptions, callbackFn);
    request(requestOptions, callbackFn);
};
var options = { endpoint: 'put service url here', request: stubRequest};
var requestArgs = { name: 'test_name' };
soap.createClient(url, options, function(err, client) {
  var method = client.getPhone;
  method(requestArgs, function(err, result, envelope, soapHeader) {
    //response envelope
    console.log('Response Envelope: \n' + envelope);
    //'result' is the response body
    console.log('Result: \n' + JSON.stringify(result));
  });
});

You'd need to set the endpoint in the options object. Also the auth header will be required.

The describe method is also useful:

// Describes the entire WSDL in a JSON tree object form.
var description = client.describe();
Terry Lennox
  • 29,471
  • 5
  • 28
  • 40
  • Thanks that worked. Interestingly I couldn't get authorization to work with wdsl_header, so I had to use the solution here: https://stackoverflow.com/a/34657640/9318925 – Mr Wannabe Feb 09 '18 at 21:01
  • One last question if I may. I need to send an HTTP POST for request. Logs show I'm sending HTTP GET (see https://pastebin.com/VPz9C6rR). I tried this and it didn't work: var specialRequest = request.defaults({ method: "POST" }). In the options variable I put request: specialRequest. – Mr Wannabe Feb 09 '18 at 21:06
  • And obviously add the other parameters! – Terry Lennox Feb 09 '18 at 21:48
  • Hmm I tried that but I get "TypeError: self.request is not a function." I am posting the update code, complete error and stack trace here: https://pastebin.com/DRYy1a97. Would you be so kind as to review it? – Mr Wannabe Feb 09 '18 at 22:24
  • I'll do that! It will be tomorrow though! – Terry Lennox Feb 10 '18 at 00:08
  • It's very strange that it would send a HTTP GET. SOAP requests are usually POSTS. The only time it should really send a GET is when it's pulling WSDL from the server (and in this case it should be local) – Terry Lennox Feb 10 '18 at 09:47
  • Yeah it's local. – Mr Wannabe Feb 10 '18 at 10:14
  • What you did with the request: specialRequest is correct, I've tried this and it works as you might expect. However, the problem is that this only sets the default request and strong-soap is sending a GET request for some reason. – Terry Lennox Feb 10 '18 at 10:26
  • I'm wondering if this is something weird like a proxy server on the other end changing the HTTP method. Have you tried using Fiddler or WireShark to profile the HTTP/HTTPS traffic to see the exact shape of the requests? – Terry Lennox Feb 10 '18 at 10:34
  • Ok, it's a bit ugly but there is a way of overriding the strong-soap HTTP calls. You can basically hook in to the calls and change the method from GET to POST. It should be flexible enough to deal with what you're doing. I'll update the answer with this approach. – Terry Lennox Feb 10 '18 at 11:07
  • There is no proxy. The SOAP call is done from an ubuntu virtual machine. The target endpoint (192.168.138.15) is a virtual machine on the same PC. – Mr Wannabe Feb 10 '18 at 17:34
  • OK, cool.. That's a bit easier. Did you see the method I used to change the request method? This should override what the strong-soap library is using. – Terry Lennox Feb 10 '18 at 17:36
  • I thought about wireshark but the problem is the server only accecpts HTTPS hence all traffic is encrypted. I did try to decrypt it by getting the private key from the server and importing it into wireshark, but it didn't work. I realized later this approach won't work as TLS implements forward secracy. – Mr Wannabe Feb 10 '18 at 17:37
  • Yes I see the updated answer. I will try it later tonight and update you. – Mr Wannabe Feb 10 '18 at 17:39
  • Fiddler allows you to monitor HTTPs traffic when you specifically enable it, so this can be useful. I updated the answer however to force the calls to be HTTP POSTS, by wrapping the request object. The only thing about using HTTPS monitoring in Fiddler is that.you will see it should only be turned on when you're testing, otherwise it causes any HTTPS traffic to be a little..dodgy! – Terry Lennox Feb 10 '18 at 17:39
  • I didn't know Fiddler is able to monitor HTTPS traffic. I will check that out too. – Mr Wannabe Feb 10 '18 at 17:41
  • Oh yes, it's very handy. It basically acts as a.proxy, you just don't want it on all the time! – Terry Lennox Feb 10 '18 at 17:42
  • I see from the logs now it is using POST. In fact it appears it always used POST. See updated code and logs here: https://pastebin.com/R7xZnRDj. I'm still not getting the expected output. But this is a problem with the server and not the code. I copy/pasted the XML request into postman and sent a POST to the endpoint and indeed I don't get the correct output. I will investigate server side. Thank you so much Terry !!! – Mr Wannabe Feb 11 '18 at 06:25
  • If you're curious, this is the output returned by the server: https://pastebin.com/0MPX9ssV. What's interesting is I don't see a problem with the soap envelope. I found this blog: http://info.stack8.com/blog/cisco-axl-remote-provisioning-cisco-unified-communications-manager. My envelope is the same what's documented there so not sure what is wrong with the server. But that's my problem! – Mr Wannabe Feb 11 '18 at 06:30
  • I figured it out! I had to put an "/" at the end of the url. Cheers mate !! – Mr Wannabe Feb 11 '18 at 07:19
  • Nice one! It's really annoying when you have to add / to a URL, I've encountered that before, super bad. – Terry Lennox Feb 11 '18 at 09:44