5

I'm having issues correctly getting Salesforce to connect to one of our external systems. They recently whitelisted the range of Salesforce addresses (At least so they said). Our Sandbox is coming from cs7.

96.43.144.0/20 _ subnet mask: 255.255.240.0
136.146.0.0/15 _ subnet mask: 255.254.0.0
182.50.76.0/22 _ subnet mask: 255.255.252.0
204.14.232.0/21 _ subnet mask: 255.255.248.0

I have 2 different Chrome plugin REST clients that are managing the login call fine.

Advanced REST Client:

Advanced REST Client

Postman REST Client:

Postman REST Client

I was given some Java code that is being used to connect but I'm having a hard time converting this to Salesforce.

Java:

URL url = new URL(SOME_URL + '/etc/authentication/Login?_contenttype=text/xml");
String urlParams = "username=" + username + "&password=" + password;

HttpsURLConnection uc = (HttpsURLConnection) url.openConnection();
uc.setRequestMethod("POST");
uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

uc.setRequestProperty("Content-Length", "" + Integer.toString(urlParams.getBytes().length));

uc.setUseCaches (false);
uc.setDoInput(true);
uc.setDoOutput(true);

//Send request
DataOutputStream wr = new DataOutputStream (uc.getOutputStream());
wr.writeBytes (urlParams);
wr.flush ();
wr.close ();

if(uc.getContent()!=null) {
   InputStream in = (InputStream) uc.getContent();
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   DocumentBuilder builder = factory.newDocumentBuilder();
   Document doc = builder.parse(in);

   NodeList recordSet = doc.getDocumentElement().getElementsByTagName("recordset");
   String sessionId = recordSet.item(0).getChildNodes().item(1).getAttributes().getNamedItem("UserSessionID").getNodeValue(); 
}

The code I have created in Apex to replicate these calls:

HttpRequest req = new HttpRequest();
req.setEndpoint(SOME_URL + '/etc/authentication/Login?_contenttype=text/xml');
req.setMethod('POST');

req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
//I tried all 3 versions below.  
//String params = 'username=' + EncodingUtil.urlEncode(userName, 'UTF-8') + '&password=' + EncodingUtil.urlEncode(password, 'UTF-8');
String params = 'username=' + userName + '&password=' + password;

//This version gives a different error that says 'Both username and password are required.'
//String params = EncodingUtil.urlEncode('username=' + userName + '&password=' + password, 'UTF-8');
req.setBody(params);
Http hp = new Http();

HTTPResponse res = hp.send(req);
System.debug('body = ' + res.getBody());

My code always returns the message: 'Invalid stored procedure name.' I am thinking that potentially the Salesforce IP isn't correctly whitelisted but I really have no proof since they told me as of this morning it was now whitelisted. Any ideas?

  • Have you verified your call from Salesforce is actually making it to the external system? Do you have a way to monitor/verify the traffic or have the external system log incoming requests? – Mike Chale Oct 28 '14 at 17:35
  • @MikeChale Unfortionately not. That other system is part of a Contract and we are not managing it. It does make trouble shooting extremely difficult. I believe it is making it to the other system though because I am getting a success response back. The only issue is that success response returns that error. I get back System.HttpResponse[Status=OK, StatusCode=200] from the system. – dphil Oct 28 '14 at 18:01
  • @dphil The message 'Invalid stored procedure name.' and the HTTP 200 response code implies the request reached the server successfully. Is it possible the web service responds differently based on the credentials and/or the source IP addresses? – Daniel Ballinger Oct 28 '14 at 18:48
  • @DanielBallinger That was actually part of my question. One thing I'm not ruling out is the fact that they might not have correctly whitelisted the Salesforce IP ranges that I gave them. If I am not whitelisted, this is actually a response I could get, but I don't know how to verify if this is the case or not. – dphil Oct 28 '14 at 18:52
  • 1
    @dphil So that would suggest that the service doesn't have the required IP address white listed (or at least you get the same error). Looks like you have the correct IP ranges from Salesforce IP Address to whitelist – Daniel Ballinger Oct 29 '14 at 1:06
3
+25

Based on the discussion in the comments I'll put this forward as a possible answer.

Details:

  1. Using independent tools such as the Postman REST Client for Chrome on a local machine with a trusted IP address you were able to successfully call the web service.
  2. Using the same tools from another machine with a non-whitelisted IP address resulted in a HTTP 200 Response with the error message "Invalid stored procedure name."
  3. The response to Salesforce is also an HTTP 200 with the same message - "Invalid stored procedure name."
  4. In your question you list the valid IP ranges as per Salesforce IP Address to whitelist.

Since the REST request from Salesforce is getting a valid HTTP response and the message is the same as an non-whitelisted IP address it is possible that the Salesforce IP rangers haven't been added correctly.

This would be hard to prove further without access to the web server logs for your service.

If possible, see if the underlying error with the missing stored procedure can be fixed. Ideally, IMHO, the web service wouldn't be broadcasting those sort of error messages to the world. That's how security issues happen.

| improve this answer | |
1

Here is some (untested) code that I think is the Apex equivalent of the Java code:

HttpRequest req = new HttpRequest();
req.setEndpoint('https://testweb1...');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(''
        + 'username=' + EncodingUtil.urlEncode('userid', 'UTF-8')
        + '&'
        + 'password=' + EncodingUtil.urlEncode('userpw', 'UTF-8')
        );

HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200) {
    Dom.Document doc = res.getBodyDocument();
    // Use XmlNode class methods to examine response
} else {
    // Handle errors or redirects
}

See the XmlNode class documentation for the methods that are available to access the DOM. There isn't the convenience of a getElementsByTagName method but you can walk down through the elements using the getChildElements method and pick out what you need.

| improve this answer | |
  • Content-Length should be automatically set by calling setBody, you don't need to set it yourself. – superfell Oct 28 '14 at 14:36
  • @superfell I think you responded to my comment just as I deleted it. I deleted it because my question has been modified heavily since this answer. But that is useful information to know either way. – dphil Oct 28 '14 at 14:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.