- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 06-28-2019 02:01 AM
Getting certificate data from GeoTrust
I write up this use case, as it's no longer valid for me, but the SOAP and XML lessons I learnt still stands. Hopefully it will help someone else to traverse an XML using ServiceNows XML handler.
A few potholes I stumbled upon along the way were how to go around missing Nodes or iterating through nested nodes.
What I am missing is a way to use the Asynchronous Import function in a Scoped App, as the Script Include to handle the request to start transform is locked to Global scope.
Part 1 - SOAP
- Create a SOAP message for the WSDL: https://api.geotrust.com/webtrust/query.jws?WSDL
- Basic authentication with your user name and password at Symantec
- Generate sample SOAP messages (use the Related Link)
- This article will focus on "GetOrdersByDateRange" to get all certificates requested
- Click "GetOrdersByDateRange"
- To get the certificate information I kept the following fields:
- ApiVersion - '2018-19' (latest)
- PartnerCode
- UserName
- Password
- FromDate - Example: '2019-01-01T00:00:00.000-05:00'
- ToDate - Example: '2005-01-01T00:00:00.000-05:00'
- ReturnProductDetail - 'True' (as a string)
- ReturnContacts - 'True' (as a string)
- ReturnCertificateInfo - 'True' (as a string)
This makes the XML look like this:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:quer="http://api.geotrust.com/webtrust/query">
<soapenv:Header/>
<soapenv:Body>
<quer:GetOrdersByDateRange>
<quer:Request>
<quer:QueryRequestHeader>
<quer:ApiVersion>${QueryRequestHeader.ApiVersion}</quer:ApiVersion>
<quer:PartnerCode>${QueryRequestHeader.PartnerCode}</quer:PartnerCode>
<quer:AuthToken>
<quer:UserName>${AuthToken.UserName}</quer:UserName>
<quer:Password>${AuthToken.Password}</quer:Password>
</quer:AuthToken>
</quer:QueryRequestHeader>
<quer:FromDate>${Request.FromDate}</quer:FromDate>
<quer:ToDate>${Request.ToDate}</quer:ToDate>
<quer:OrderQueryOptions>
<quer:ReturnProductDetail>${OrderQueryOptions.ReturnProductDetail}</quer:ReturnProductDetail>
<quer:ReturnContacts>${OrderQueryOptions.ReturnContacts}</quer:ReturnContacts>
<quer:ReturnCertificateInfo>${OrderQueryOptions.ReturnCertificateInfo}</quer:ReturnCertificateInfo>
</quer:OrderQueryOptions>
</quer:Request>
</quer:GetOrdersByDateRange>
</soapenv:Body>
</soapenv:Envelope>
We will add the actual variables when running our script. And we now have a finished SOAP request to get all orders made within a certain time span.
Part 2 - Time to get our XML
The script to call the SOAP can be retrieved from SOAP message Related Link. We just need to send in the correct data. The reason I didn't use Basic authentication but instead chose to send username and password was to not have this visible in the script.
Instead username and password are kept in Password2 system properties and called in the script. They're still sent as text data in the SOAP request, but it felt better this way.
Here is the script to get the XML:
try {
var s = new sn_ws.SOAPMessageV2('[SOAP MESSAGE]', 'GetOrdersByDateRange');
s.setStringParameterNoEscape('QueryRequestHeader.ApiVersion', '2018-19');
s.setStringParameterNoEscape('QueryRequestHeader.PartnerCode', gs.getProperty('partnercode', ''));
s.setStringParameterNoEscape('AuthToken.UserName', gs.getProperty('username'));
s.setStringParameterNoEscape('AuthToken.Password', gs.getProperty('password'));
s.setStringParameterNoEscape('Request.FromDate', fromDate);
s.setStringParameterNoEscape('Request.ToDate', toDate);
s.setStringParameterNoEscape('OrderQueryOptions.ReturnProductDetail', 'true');
s.setStringParameterNoEscape('OrderQueryOptions.ReturnCertificateInfo', 'true');
s.setStringParameterNoEscape('OrderQueryOptions.ReturnContacts', 'true');
var response = s.execute();
var responseBody = response.getBody();
var xmlstatus = response.getStatusCode();
} catch (ex) {
var message = ex.message;
gs.info('SOAP unable to run');
}
Part 3 - Use the XML
XMLDocument2 is the new Script Include to handle XML. One major difference to XMLDocument is that it's available in scoped apps as well. Also, regarding this section, I will split the code into a few parts to comment each part.
First we will get all separate orders from the XML. These are all nodes under the node "OrderDetails". Be aware some nodes have very similar names. Once we have the orders, let's go through each separate order:
// 200 status means we received what we wanted
if (xmlstatus == 200) {
// Get XMLDocument2
var xmlDoc = new XMLDocument2();
// Make a proper XML from the string we received
xmlDoc.parseXML(responseBody);
// Get the node that contains all the orders
var orders = xmlDoc.getNode('//m:OrderDetails');
// Each order is a child node to m:OrderDetails and we are now iterating through them
var order = orders.getChildNodeIterator();
// As long as there is an order do this for each
while (order.hasNext()) {
Next we will look at each order and get the data we are looking for, from the separate nodes within the order we're looking at.
First we start to run through the order and create or own variables to store the data:
// Create a variable for the order
var orderdetails = order.next();
// Create a new instance to handle the order data
var certificates = new XMLDocument2();
// Parse the "new" XML
certificates.parseXML(orderdetails);
// Create the variables to be used to get the data we need
var create_date = '',
serialno = '',
identifier = '',
startdate = '',
validity = '',
certstatus = '',
displayname = '',
product_type = '',
description = '';
// Create two check variables as we need to know if a certain test worked
var certificatecheck = true;
var ordercheck = true;
Second it's time to iterate through the sometimes present "Certificate Info" node. We will check if a child node has text (meaning it has data) and if it does, we will see which known node it is, and add the data to one of our variables:
// As a node may not be present in an XML, we try each node we need data from
// This particular node holds Certificate Information, and we get it with the ('OrderQueryOptions.ReturnCertificateInfo', 'true'); parameter
// As this node is not always present, the try/catch is mainly for this part
try {
var certificatedetail = certificates.getNode('//m:CertificateInfo');
// If you want to check if certificatedetail has data, check if certificatedetail.toString() is NULL here
// As we use try, we don't need the extra check, just be aware that certificatedetail is not null or undefined, unless you use toString()
// Run through the child nodes of certificate info
var details = certificatedetail.getChildNodeIterator();
while (details.hasNext()) {
var cert = details.next();
// If the node has data, check which node it is and add the desired data to its respective variable
if (cert.getTextContent()) {
switch (cert.getNodeName().toString()) {
case 'm:SerialNumber':
serialno = cert.getTextContent();
break;
case 'm:StartDate':
startdate = cert.getTextContent();
break;
case 'm:EndDate':
validity = cert.getTextContent();
break;
case 'm:CertificateStatus':
certstatus = cert.getTextContent();
break;
case 'm:WebServerType':
product_type = cert.getTextContent();
break;
case 'm:CommonName':
displayname = cert.getTextContent();
break;
}
}
}
// If certificate info was not present, we will have an error
} catch (ex) {
// Set the check variable to false, we need it later
certificatecheck = false;
}
Third, let's go through the order details. This works the same as the certificate details, but we will get a little data from here, if the certificate info is not present. The data here is not as viable when it comes to certificate information, but something is better than nothing.
try {
var orderdetail = certificates.getNode('//m:OrderInfo');
var orderspecifics = orderdetail.getChildNodeIterator();
while (orderspecifics.hasNext()) {
var ord = orderspecifics.next();
if (ord.getTextContent()) {
switch (ord.getNodeName().toString()) {
case 'm:OrderDate':
create_date = ord.getTextContent();
break;
case 'm:SpecialInstructions':
description = ord.getTextContent();
break;
case 'm:GeoTrustOrderID':
identifier = ord.getTextContent();
break;
}
// If the previous try failed, certificatecheck was set to false
if (!certificatecheck) {
switch (ord.getNodeName().toString()) {
case 'm:OrderCompleteDate':
startdate = ord.getTextContent();
break;
case 'm:OrderState':
certstatus = ord.getTextContent();
break;
case 'm:ProductCode':
product_type = ord.getTextContent();
break;
case 'm:DomainName':
displayname = ord.getTextContent();
break;
}
}
}
}
} catch (ex) {
ordercheck = false;
}
Finally, if we have atleast order data, we will create records in an import table connected to a transform map. Once a record is inserted, transform will commence for that line.
if (ordercheck) {
var imp = new GlideRecord('[IMPORT TABLE]');
imp.initialize();
imp.identifier = identifier;
imp.display_name = (displayname) ? displayname : identifier;
imp.end_date = (validity) ? validity : '';
imp.start_date = (startdate) ? startdate : '';
imp.status = (certstatus) ? certstatus : '';
imp.product_type = (product_type) ? product_type : '';
imp.description = (description) ? description : '';
imp.create_date = (create_date) ? create_date : '';
imp.serial_number = (serialno) ? serialno : '';
imp.provider = '[SYS ID FOR THE "MANUFACTURER"]'; //For reference in the final table
imp.insert();
} else {
gs.info('Import was unable to find order data');
}
Last we make a log that we are done with the import. There is also a log not if we did not get 200 as return code.
}
gs.info('Import completed');
} else {
gs.info('Import received error message ' + xmlstatus);
}
I hope this will make it a lot faster for you to handle your XML data.