Reverse Engineering Mobile Apps for Oracle EBS – part 2
Published on: Author: Richard Velden Category: OracleIn the first part of this article we have shown some of the inner workings of the Timecard mobile app. In this second part we will focus on the REST Login API provided by Oracle EBS.
Just as in the first part we need to have the Timecard MAA file from Oracle opened in JDeveloper, and access to Oracle EBS. For probing Oracle EBS REST services we have used Postman.
Probing the REST Login Services
When developing our own mobile app for Oracle EBS, we will also need to use EBS’ REST login services. To get a better understanding of this API we will do some API probing using Postman. Our goal is to simulate the login for a mobile app using the REST API. Once authenticated, we will try to fetch timecards for a particular user.
Fetching Timecards – take 1
In the decompiled Java sources (mobileApplicationArchive.jar in ViewController\classlib) we can find the REST call going to Oracle EBS in the TimecardWS.java class, located in package oracle.apps.hxc.mobile.ws
public static void fetchDetails() { RestUtil rest = new RestUtil(); rest.setConnection("EBSConn"); rest.setRequestType("POST"); rest.setRetryLimit(0); rest.setRequestURL("/OA_HTML/RF.jsp?function_id=HXC_MOB_TC_STATUS"); sb.append("<params> <param>ALL</param>"); sb.append("<param>"); sb.append("yyyy-MM-dd"); sb.append("</param></params>"); rest.setRequestXML(sb.toString()); try { response = rest.processRequest(); } }
This call looks rather simple. It concerns a HTTP POST with request URL: http://ebsURL:port//OA_HTML/RF.jsp?function_id=HXC_MOB_TC_STATUS
The POST payload should be the following XML:
<params> <param>ALL</param> <param>yyyy-MM-dd</param> </params>
When trying to invoke this method in Postman (figure 2) we get the following error response:
<response> <status> <code>401</code> <description>FND_ERR_DOLOGIN</description> </status> <data></data> </response>
This seems like a logical response because we did not provide any credentials yet.
For doing a REST call the Timecard app uses the RESTUtil class from package oracle.apps.hxc.application.util. When investigating this utility class however, we did not find any login wrapper. So where to find the login procedure?
Fetching Timecards – take 2 (Login API)
The login code is hidden somewhere in the ApplicationController project. In this project you will find the EBSLoginLib.jar library containing login related logic. To investigate the implementation we first need to unzip the jar file, and decompile all Java classes using jad.
unzip: Timecards_Archive\lib\EBSLoginLib.jar Recursive Jad: jad -d . -s java -r **/*.class
The class to look for is the RestUtil from package oracle.apps.fnd.mobile.common.utils. This is a different RestUtil than the one from the Timecard ViewController project. What we found here was a REST call to some sort of login function.
restServiceAdapter = Model.createRestServiceAdapter(); restServiceAdapter.clearRequestProperties(); restServiceAdapter.setConnectionName("EBSRestConn"); restServiceAdapter.setRequestType("GET"); sbUrl.append("/OA_HTML/RF.jsp?function_id=mLogin"); restServiceAdapter.setRequestURI(sbUrl.toString()); restServiceAdapter.addRequestProperty("Content-Type", "application/xml");
We simulate this GET request to endpoint http://ebsurl:port/OA_HTML/RF.jsp?function_id=mLogin in Postman (see figure 4).
Without providing any credentials we get an ‘Invalid username/password’ error from the Login service. Seems logical to us, so we try again using some Basic Authentication. For this we enter our Oracle EBS user credentials.
The response contains an access token which is valid for a certain amount of time.
<response> <data> <accessToken>GhuAjvARQpie3uh6Q0Tl5o6kti</accessToken> <accessTokenName>VIS</accessTokenName> <ebsVersion>12.1.3</ebsVersion> <userName>BPALMER</userName> </data> </response>
It seems we have logged in. Let’s try to get the Timecards again.
Still no timecards in the response! But we are one step further in the login procedure. We still need to initialize our session by choosing a responsibility.
Fetching Timecards – take 3 (Responsibility Selection)
For REST API calls concerning Responsibility selection we check RespUtils.java located in package oracle.apps.fnd.mobile.common.utils.
Here the initialization of the session is implemented in method mInit.
throws Exception { orgId = orgId != null && !orgId.isEmpty() ? orgId : ""; RestUtil rest = new RestUtil(); rest.setConnection("EBSRestConn"); rest.setRequestURL("/OA_HTML/RF.jsp?function_id=mInit"); String input = (new StringBuilder()).append("<data> <resp> <id>").append(respId).append("</id>").append(" <key></key>").append(" <applId>").append(applId).append("</applId>").append(" <applKey></applKey>").append(" </resp>").append(" <securityGroup>").append(" <id></id>").append(" <key>").append(securityGroupId).append("</key>").append(" </securityGroup>").append(" <org>").append(" <id>").append(orgId).append("</id>").append(" <key></key>").append(" </org>").append("</data>").toString(); rest.setRequestXML(input); try { response = rest.processRequest(); throw e; } return response; }
The REST endpoint used is: http://ebsurl:port/OA_HTML/RF.jsp?function_id=mInit
The POST message being sent looks like the one below:
<data> <resp> <id>$RESPID</id> <key></key> <applId>$APPLID</applId> <applKey></applKey> </resp> <securityGroup> <id></id> <key>$SECURITYGROUPID</key> </securityGroup> <org> <id>$ORGID</id> <key></key> </org> </data>
Now we still have to fill in the blanks! Which id's to use? For this we move to the Responsibility.java in package oracle.apps.fnd.mobile.common.resppicker. The method of interest is getResponsibility() which will fetch all relevant responsibilities for the logged in user and pass it to the Responsibility Picker component.
rest = new RestUtil(); rest.setConnection("EBSRestConn"); rest.setRequestURL("/OA_HTML/RF.jsp?function_id=mAcs"); JSONObject json = new JSONObject(); try { json.put("appName", appName); json.put("mode", mode); json.put("roleCode", roleCode); } catch(JSONException e) { e.printStackTrace(); } rest.setRequestXML(json.toString()); customHeaders.put("Content-Type", "application/json"); customHeaders.put("Accept", "application/json");
As you see the REST endpoint is http://ebsurl:port/OA_HTML/RF.jsp?function_id=mAcs the payload is formatted as a JSON string, and there are 3 JSON parameters part of the request.
We have looked at the properties, such as oracle.ebs.login.roleappname in the ebs.properties file. Using these properties we came up with the following JSON payload:
{
"appName": "HXC",
"mode": "parent",
"roleCode": "UMX|HXC_MBL_TIME_ENTRY"
}
We’ve then executed this call using Postman (see figure 10 and 11).
The response (figure 11) contained the data we needed for our Responsibility initialization. Such as the responsibility_id, application_id, and security_group_key.
Figure 11: JSON response when trying to fetch Time Entry responsibility details
{
"data": [
{
"name": "FND_RESP|HXC|HXC_SELF_SERVICE|STANDARD",
"display_name": "Self Service Time and Expenses",
"responsibility_id": "22717",
"responsibility_application_id": "809",
"security_group_key": "STANDARD"
}
]
}
Finally we can initialize the responsibility. We have used the responsibility_id (22717), the responsibility_application_id (809) and the security_group_key (STANDARD) for our session initialization request (see figures 12 and 13).
<data> <resp> <id>22717</id> <key></key> <applId>809</applId> <applKey></applKey> </resp> <securityGroup> <id></id> <key>STANDARD</key> </securityGroup> <org> <id></id> <key></key> </org> </data>
In our final take we try to fetch all Timecards for this user using the http://ebsurl:port/OA_HTML/RF.jsp?function_id=HXC_MOB_TC_STATUS endpoint (see figures 14 and 15).
<?xml version = '1.0' encoding = 'UTF-8'?> <response status="200"> <timecard_list> <timecard> <tc_start_time>2016-04-03</tc_start_time> <tc_id>219199</tc_id> <tc_ovn>2</tc_ovn> <layout_id>4197</layout_id> <line1>2016-04-03 to 2016-04-09</line1> <line2>40 Hours</line2> <tc_status>SUBMITTED</tc_status> <month>PREV</month> </timecard> <timecard> <tc_start_time>2016-04-10</tc_start_time> <tc_id>219226</tc_id> <tc_ovn>2</tc_ovn> <layout_id>4197</layout_id> <line1>2016-04-10 to 2016-04-16</line1> <line2>40 Hours</line2> <tc_status>SUBMITTED</tc_status> <month>PREV</month> </timecard> </timecard_list> </response>
Conclusion
It took some Java decompiling and reverse engineering, but finally we were able to simulate the E-Business Suite login using the Authentication REST APIs.
Please keep in mind, when creating your own mobile app for Oracle EBS, you do not need to program all these REST calls yourselves. The EBSLoginLib.jar in combination with the Sample App from Oracle can take these worries away for you. This is described in detail in the Developer’s Guide [1].
It is also possible to just enable your own API’s in the Integrated SOA Gateway, and forget about the entire session context. Just use basic authentication on each REST call to access the services. But bear in mind that using basic authentication alone won’t initialize a session context. So there will be no responsibility or organization context, unless you explicitly code this into your API.
References
[1] Oracle® E-Business Suite Mobile Foundation Developer's Guide
[3] Introduction to Oracle Mobile Application Framework
Question: How can <ebsurl>/OA_HTML/RF.jsp?function_id=mLogin be made to accept SSO (OAM) credentials. It seems that <ebsurl>/OA_HTML/RF.jsp?function_id=mLogin only authenticates with Local EBS credentials or if integrated with OID - OID credentials (with some settings for EBS). We have EBS 12.2 integrated with OAM and wanted to use OAM credentials to authenticate so that more elaborate authentication mechanism can be leveraged.