Hi All,
We are trying to call the WorldCheckOne API from the SAP, where we need to convert the XML to JSON and post the data to WorldCheckOne API.
For which, I have written a program to make a GET request to /v2/groups , I am getting HTTP 401 error. Not exactly sure what is causing the error, could you please check and let me know what is wrong in the below code:
FYI. But from postman it is working fine with the same API key and Secret.
package HMACSignatureAuthentication;
import com.sap.aii.mapping.api.*;
import org.json.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static java.lang.String.format;
import static java.time.ZoneOffset.UTC;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
import org.apache.commons.net.ntp.NTPUDPClient;
import java.net.InetAddress;
public class APICallWithHMACAuthentication extends AbstractTransformation {
private static final String HMAC_SHA_256 = "HmacSHA256";
private static final String CONTENT_TYPE_PREFIX = "application/json";
private static final String SIGNATURE_HEADERS_WITHOUT_CONTENT = "(request-target) host date";
private static final String SIGNATURE_HEADERS_WITH_CONTENT = "(request-target) host date content-type content-length";
private List<String> array_nodes = new ArrayList<>();
private boolean isSapEnvironment = false;
private static final String PROTOCOL = "https";
private static final String GATEWAY_HOST = "api-worldcheck.refinitiv.com";
private static final String GATEWAY_URL = "/v2/";
public static void main(String[] args) {
APICallWithHMACAuthentication authInstance = new APICallWithHMACAuthentication();
authInstance.isSapEnvironment = false;
try {
String keyId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
String secretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
String method = "groups"; // "cases/screeningRequest"; // groups
String requestMethod = "GET"; // GET
String sampleXml = "";
String result = authInstance.transformStandalone(secretKey, GATEWAY_HOST, method, requestMethod, sampleXml,
keyId);
System.out.println("Transformed Output: \n" + result);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
public String transformStandalone(String secretKey, String host, String method, String requestMethod,
String inputXml, String keyId) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
InputStream inputStream = new ByteArrayInputStream(inputXml.getBytes());
performTransformation(secretKey, host, method, requestMethod, inputStream, outputStream, keyId);
return outputStream.toString("UTF-8");
}
public void transform(TransformationInput transformationInput, TransformationOutput transformationOutput)
throws StreamTransformationException {
this.isSapEnvironment = true;
try {
APICallWithHMACAuthentication authInstance = new APICallWithHMACAuthentication();
String secretKey = transformationInput.getInputParameters().getString("Secret_Key");
String host = transformationInput.getInputParameters().getString("Host");
String method = transformationInput.getInputParameters().getString("Method");
String requestMethod = transformationInput.getInputParameters().getString("Request_Method");
String sampleXml = "";
String keyId = "";
InputStream inputStream = transformationInput.getInputPayload().getInputStream();
OutputStream outputStream = transformationOutput.getOutputPayload().getOutputStream();
authInstance.transformStandalone(secretKey, GATEWAY_HOST, method, requestMethod, sampleXml, keyId);
} catch (Exception e) {
if (isSapEnvironment && getTrace() != null) {
getTrace().addDebugMessage(e.getMessage());
}
throw new StreamTransformationException(e.toString());
}
}
public void performTransformation(String secretKey, String host, String method, String requestMethod,
InputStream inputStream, OutputStream outputStream, String keyId) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sourceXml = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sourceXml.append(line).append("\n");
}
br.close();
URI uri = new URI(PROTOCOL, null, GATEWAY_HOST, -1, GATEWAY_URL + method, null, null);
Map<String, String> authHeaders;
String jsonPayload = "";
System.out.println("URI: " + uri.toString());
System.out.println("sourceXML: " + sourceXml);
// Check if XML payload is empty
if (sourceXml.toString().trim().isEmpty()) {
// XML payload is empty, set empty JSON string
System.out.println("No XML payload provided. Skipping JSON transformation.");
// jsonPayload = "";
} else {
// XML payload exists, perform JSON transformation
JSONObject xmlJSONObj = XML.toJSONObject(sourceXml.toString());
array_nodes.add("arraynode");
xmlJSONObj = handleJSONData(xmlJSONObj);
// Attempt to remove root node if there is only one top-level key
Iterator<String> keys = xmlJSONObj.keys();
if (xmlJSONObj.length() == 1 && keys.hasNext()) {
String rootKey = keys.next(); // Get the root key
Object innerObject = xmlJSONObj.get(rootKey);
// Check if the inner object is a JSONObject (not an array or simple type)
if (innerObject instanceof JSONObject) {
xmlJSONObj = (JSONObject) innerObject; // Strip away the root by assigning inner JSON object
}
}
jsonPayload = xmlJSONObj.toString(4).replaceAll("\"xmlns:ns0\":\"your namespace\",", "")
.replaceAll("\"ns0:Message Type Name\":", "").trim();
}
String jsonBase64 = Base64.getEncoder().encodeToString(jsonPayload.getBytes());
System.out.println(jsonBase64);
authHeaders = generateAuthHeaders(keyId, secretKey, requestMethod, uri, "application/json", jsonPayload);
// Call API with either transformed JSON or empty payload
//postToAPI(uri, jsonBase64, authHeaders, requestMethod, outputStream);
postToAPI(uri, jsonPayload, authHeaders, requestMethod, outputStream);
}
/**
* Generates a {@link Map} with required authorization headers (Date, Authorization, Content-Type, Content-Length).
*
* @param apiKey the user's API key
* @param apiSecret the user's API secret
* @param httpMethod the HTTP method of a request (e.g. 'get', 'options', 'head', 'post', 'put', 'patch',
* or 'delete')
* @param uri the {@link URI} to send the request to (e.g. 'https://www.example.com/resources')
* @param payload payload/body of the request
* @return {@link Map} with all required headers for authorization (Date, Authorization, Content-Type,
* Content-Length)
*/
private Map<String, String> generateAuthHeaders(String apiKey, String apiSecret, String httpMethod, URI uri,
String contentType, String payload) {
Map<String, String> authHeaders = new HashMap<>();
Date now = new Date();
// Format the date to "EEE, dd MMM yyyy HH:mm:ss z"
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); // Set time zone to GMT
// Print the formatted date
String date = dateFormat.format(now);
StringBuilder dataToSign = new StringBuilder(300).append("(request-target): ")
.append(httpMethod.toLowerCase()).append(' ')
.append(uri.getPath())
.append("\nhost: ").append(uri.getHost())
.append("\ndate: ").append(date);
validateContentType(contentType, payload);
if (payload != null) {
int contentLength = payload.length();
dataToSign.append("\ncontent-type: ").append(contentType)
.append("\ncontent-length: ").append(contentLength)
.append('\n').append(payload);
authHeaders.put("Content-Type", contentType);
authHeaders.put("Content-Length", String.valueOf(contentLength));
}
System.out.println(dataToSign);
System.out.println(payload);
String headers = payload == null ? SIGNATURE_HEADERS_WITHOUT_CONTENT : SIGNATURE_HEADERS_WITH_CONTENT;
String signature = signDataWithHmacSha256(apiSecret, dataToSign.toString());
String authorization = "Signature keyId=\"" + apiKey + "\",algorithm=\"hmac-sha256\",headers=\"" + headers
+ "\",signature=\"" + signature + '"';
authHeaders.put("Date", date);
authHeaders.put("Authorization", authorization);
return authHeaders;
}
private void validateContentType(String contentType, String payload) {
if (contentType != null && !contentType.startsWith(CONTENT_TYPE_PREFIX)) {
throw new IllegalArgumentException("Unsupported content type " + contentType);
}
if (contentType != null && payload == null) {
throw new IllegalArgumentException("The request payload(body) has not been provided");
}
if (contentType == null && payload != null) {
throw new IllegalArgumentException("The content type of request payload(body) has not been provided");
}
}
private String signDataWithHmacSha256(String apiSecret, String dataToSign) {
try {
Mac mac = Mac.getInstance(HMAC_SHA_256);
mac.init(new SecretKeySpec(apiSecret.getBytes(), HMAC_SHA_256));
byte[] hmac = mac.doFinal(dataToSign.getBytes());
return Base64.getEncoder().encodeToString(hmac);
} catch (NoSuchAlgorithmException | IllegalArgumentException | InvalidKeyException e) {
throw new RuntimeException(format("Unable to sign user: %s with data: %n%s", apiSecret, dataToSign), e);
}
}
private void postToAPI(URI uri, String payload, Map<String, String> authHeaders, String requestMethod,
OutputStream outputStream) throws Exception {
HttpURLConnection connection = null;
try {
// Open connection
connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod(requestMethod.toUpperCase());
connection.setDoOutput(true);
System.out.println(requestMethod.toUpperCase() + " " + uri.toString());
// Set request headers
for (Map.Entry<String, String> entry : authHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
System.out.println(entry.getKey() + ": " + entry.getValue());
}
System.out.println(payload);
// Write payload to output stream
try (OutputStream os = connection.getOutputStream()) {
os.write(payload.getBytes("UTF-8"));
os.flush();
}
// Get response code
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
// Check for successful response code (200-299)
if (responseCode >= 200 && responseCode < 300) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
// Write response to output stream
outputStream.write(response.toString().getBytes("UTF-8"));
}
} else {
// Handle non-successful response
throw new IOException(
"HTTP error code: " + responseCode + ", Response message: " + connection.getResponseMessage());
}
} catch (IOException e) {
// Handle IOException (network issues, read/write errors)
System.err.println("IOException occurred: " + e.getMessage());
throw e; // Re-throwing the exception to be handled by the caller
} catch (Exception e) {
// Handle any other exceptions that might occur
System.err.println("Unexpected exception occurred: " + e.getMessage());
throw e; // Re-throwing the exception to be handled by the caller
} finally {
if (connection != null) {
connection.disconnect(); // Ensure the connection is closed
}
}
}
public JSONObject handleJSONData(JSONObject jsonObj) {
try {
String[] keys = (String[]) jsonObj.keySet().toArray(new String[0]);
for (String key : keys) {
if (array_nodes.contains(key)) {
jsonObj = forceToJSONArray(jsonObj, key);
}
if (jsonObj.get(key) instanceof JSONArray) {
JSONArray jsonArray = jsonObj.getJSONArray(key);
for (int i = 0; i < jsonArray.length(); i++) {
jsonArray.put(i, handleJSONData(jsonArray.getJSONObject(i)));
}
jsonObj.put(key, jsonArray);
} else if (jsonObj.get(key) instanceof JSONObject) {
jsonObj.put(key, handleJSONData(jsonObj.getJSONObject(key)));
} else if (jsonObj.get(key) instanceof Number) {
jsonObj.put(key, jsonObj.get(key).toString());
}
}
} catch (Exception e) {
if (isSapEnvironment && getTrace() != null) {
getTrace().addDebugMessage("Exception while updating payload: " + e.getMessage());
} else {
System.err.println("Exception while updating payload: " + e.getMessage());
}
}
return jsonObj;
}
public static JSONObject forceToJSONArray(JSONObject jsonObj, String key) throws JSONException {
Object obj = jsonObj.opt(key);
if (obj instanceof JSONObject) {
JSONArray jsonArray = new JSONArray();
jsonArray.put((JSONObject) obj);
jsonObj.put(key, jsonArray);
} else if (obj != null) {
JSONArray jsonArray = new JSONArray();
for (int i = 0; i < ((JSONArray) obj).length(); i++) {
jsonArray.put(((JSONArray) obj).getJSONObject(i));
}
jsonObj.put(key, jsonArray);
}
return jsonObj;
}
}
Thanks
Sai