Overview
DataWedge provides an option for admins to control access to DataWedge intent APIs, allowing only approved apps to configure DataWedge. DataWedge intent APIs are categorized, allowing the administrator to grant DataWedge API access to specific apps based on category. By default, DataWedge accepts any intent API to avoid impact to existing applications.
Previously, any application on the device could configure DataWedge parameters and receive information such as configurations and status notifications via DataWedge APIs. This led to a potential security risk where a malicious application can configure DataWedge profiles used by other applications and leverage it for its own advantage, for example a malicious app could modify the destination URI of IP Output plugin to divert data delivery to a different address.
MX version 10.1.1 or above is required on the device.
Video demonstration about securing access to DataWedge intent APIs:
Control Access
Process to control access to DataWedge intent APIs:
- Whitelist approved app using AccessMgr CSP from MX.
- Specify the intent API category to restrict intent API access via DataWedgeMgr CSP from MX.
- Acquire the app token from AccessMgr using EMDK.
- Access the API by including the token in the DataWedge intent.
Details are provided in the sections that follow.
1. Whitelist Approved App
The first step to control access to DataWedge Intent APIs is for the admin to whitelist the app using AccessMgr CSP from MX. Whitelisting allows only the apps specified on a list to run, restricting use of apps not on the approved whitelist. Use StageNow or EMDK Profile Manager (Android or Xamarin) to deploy the configuration to whitelist the app. Set the following parameters:
- Service Access Action: "Allow caller"
- Service Identifier: com.symbol.datawedge.api
- Specify Caller Package Name: <enter app package name, e.g.: com.company.appname>
- Caller Signature: <select signature file that contains the app certificate>
2. Restrict DataWedge Intent APIs
By default, all DataWedge APIs are in uncontrolled mode - any application can access all DataWedge APIs. An admin can place a DataWedge API category into controlled/uncontrolled mode using DataWedgeMgr CSP via StageNow to restrict access to a particular API category. Refer to Control Access to Intent APIs in DataWedgeMgr CSP.
API Category | Control Modes |
---|---|
Configuration APIs | Uncontrolled Controlled |
Notification APIs | Uncontrolled Controlled |
Query API | Uncontrolled Controlled |
Runtime APIs | Uncontrolled Controlled |
Uncontrolled – All apps have access to APIs; this the default setting.
Controlled – This restricts API access. Only the admin can whitelist apps to access the APIs using AccessMgr CSP.
DataWedge APIs are categorized into 4 types:
- Configuration APIs - APIs related to actions taken on configurations
- Notification APIs - APIs related to retrieving status for scanner, profile, or configuration
- Query API - APIs that retrieve information or enumerate scanners
- Runtime APIs - APIs that can change functionality at runtime
Administrators can designate which categories are protected using DataWedgeMgr CSP. By default, all the API categories are in unprotected mode.
Intent API category | Intent APIs |
---|---|
Configuration APIs | Get Config Get Disabled App List Get Ignore Disabled Profiles Get Profile List Clone Profile Create Profile Delete Profile Rename Profile Set Config Set Ignore Disabled Profiles Set Disabled App list Restore Config Import Config |
Notification APIs | Profile Switching (from Register/Unregister for Notification) Scanner Status (from Register/Unregister for Notification) Configuration Change (from Register/Unregister for Notification) |
Query API | Enumerate Scanners Get Active Profile Get DataWedge Status Get Version Info Get Scanner Status Get Profiles List Get Ignore Disabled Profiles |
Runtime APIs | Enable/Disable DataWedge Enable/Disable Scanner Input Plug-in Switch Scanner Params Switch SimulScan Params Soft Scan Trigger Soft RFID Trigger Switch to Profile Set Default Profile Reset Default Profile Switch Scanner Set Reporting Options |
See DataWedge APIs.
3. Acquire a Token
Only whitelisted applications are allowed to call DataWedge service identifier to generate tokens. The applications must use EMDK ProfileManager APIs to acquire a token from AccessMgr CSP.
To generate a token, use EMDK Profile Manager to create a profile:
- Open EMDK Profile Manager (Android or Xamarin).
- Create a new profile using MX 10.1 or above and provide a name, such as RequestToken.
- In the “Available Features” list, select and add “Access Manager” as a feature.
- Access Manager is listed under “Selected Features”.
- In the Access Manager parameters section, set/enter the following:
- Service Access Action: “Request Token”
- Service Identfier: com.symbol.datawedge.api
Sample generated EMDKConfig.xml from EMDK Profile Manager:
<?xml version="1.0" encoding="UTF-8"?><!--This is an auto generated document. Changes to this document may cause incorrect behavior.--><wap-provisioningdoc>
<characteristic type="ProfileInfo">
<parm name="created_wizard_version" value="7.3.2"/>
</characteristic>
<characteristic type="Profile">
<parm name="ProfileName" value="RequestToken"/>
<parm name="ModifiedDate" value="2020-03-23 20:01:24"/>
<parm name="TargetSystemVersion" value="9.1"/>
<characteristic type="AccessMgr" version="8.3">
<parm name="emdk_name" value=""/>
<parm name="ServiceAccessAction" value="7"/>
<parm name="ServiceIdentifier" value="com.symbol.datawedge.api"/>
</characteristic>
</characteristic>
</wap-provisioningdoc>
Use ProcessProfile API from EMDK to generate the token to be passed to the desired DataWedge API intent for secure access. Sample code to generate the token:
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Xml;
import com.symbol.emdk.EMDKManager;
import com.symbol.emdk.EMDKResults;
import com.symbol.emdk.ProfileManager;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.StringReader;
public class TokenOperations implements EMDKManager.EMDKListener {
static Object syncObj = new Object();
Context mContext;
// Provides the error type for characteristic-error
private String errorType = "";
// Provides the parm name for parm-error
private String parmName = "";
// Provides error description
private String errorDescription = "";
// Provides error string with type/name + description
private String errorString = "";
private String mToken = "";
//Assign the profile name used in EMDKConfig.xml
private String profileName = "";
//Declare a variable to store ProfileManager object
private ProfileManager profileManager = null;
//Declare a variable to store EMDKManager object
private EMDKManager emdkManager = null;
public TokenOperations(Context context)
{
this.mContext = context;
}
public String getErrorDescription()
{
return errorDescription;
}
public String generateToken()
{
EMDKResults results = EMDKManager.getEMDKManager(mContext, this);
//Check the return status of EMDKManager object creation.
if(results.statusCode == EMDKResults.STATUS_CODE.SUCCESS) {
//EMDKManager object creation success
}else {
//EMDKManager object creation failed
}
try {
synchronized (syncObj) {
syncObj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (emdkManager != null) {
emdkManager.release();
emdkManager = null;
}
return mToken;
}
@Override
public void onOpened(EMDKManager emdkManager) {
this.emdkManager = emdkManager;
//Get the ProfileManager object to process the profiles
profileManager = (ProfileManager) emdkManager.getInstance(EMDKManager.FEATURE_TYPE.PROFILE);
profileName = "RequestToken";
new ProcessProfileTask().execute("");
}
@Override
public void onClosed() {
//This callback will be issued when the EMDK closes unexpectedly.
if (emdkManager != null) {
emdkManager.release();
emdkManager = null;
}
}
private void parseXML(XmlPullParser myParser) {
int event;
try {
// Retrieve error details if parm-error/characteristic-error in the response XML
event = myParser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String name = myParser.getName();
switch (event) {
case XmlPullParser.START_TAG:
if (name.equals("parm-error")) {
parmName = myParser.getAttributeValue(null, "name");
errorDescription = myParser.getAttributeValue(null, "desc");
errorString = " (Name: " + parmName + ", Error Description: " + errorDescription + ")";
return;
}
else if (name.equals("parm")) {
String parmName = myParser.getAttributeValue(null, "name");
if(parmName.equalsIgnoreCase("ServiceAccessToken"))
{
//Retrieved Token will be saved to a variable
mToken = myParser.getAttributeValue(null, "value");
return;
}
}
break;
case XmlPullParser.END_TAG:
break;
}
event = myParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private class ProcessProfileTask extends AsyncTask<String, Void, EMDKResults> {
@Override
protected EMDKResults doInBackground(String... params) {
parmName = "";
errorDescription = "";
errorType = "";
mToken = "";
EMDKResults resultsReset = profileManager.processProfile(profileName, ProfileManager.PROFILE_FLAG.RESET, params);
EMDKResults results = profileManager.processProfile(profileName, ProfileManager.PROFILE_FLAG.SET, params);
return results;
}
@Override
protected void onPostExecute(EMDKResults results) {
super.onPostExecute(results);
String resultString = "";
//Check the return status of processProfile
if(results.statusCode == EMDKResults.STATUS_CODE.CHECK_XML) {
// Get XML response as a String
String statusXMLResponse = results.getStatusString();
try {
// Create instance of XML Pull Parser to parse the response
XmlPullParser parser = Xml.newPullParser();
// Provide the string response to the String Reader that reads
// for the parser
parser.setInput(new StringReader(statusXMLResponse));
// Call method to parse the response
parseXML(parser);
if (TextUtils.isEmpty(parmName) && TextUtils.isEmpty(errorType) && TextUtils.isEmpty(errorDescription) ) {
resultString = "Profile update success.";
if(!TextUtils.isEmpty(mToken))
{
resultString = "";
}
}
else {
resultString = "Profile update failed." + errorString;
}
synchronized (syncObj) {
syncObj.notify();
}
} catch (XmlPullParserException e) {
resultString = e.getMessage();
}
}
}
}
}
4. Accessing the APIs
Once the token is generated, it needs to be included in the intent API call along with the calling application’s package name being sent to DataWedge. When the intent is received by DataWedge, DataWedge verifies if the token is valid for the given application. If the token is valid, DataWedge processes the intent. If the token is not valid, one of the following error codes occur:
- Main error code:
APPLICATION_NOT_AUTHORIZED
- Sub error codes:
TOKEN_EXPIRED
: The token is expired; applications are required to acquire a new token.MX_NOT_INITIALIZED
: MX is not initialized. This can usually occur right after a reboot.FAILED_TO_VERIFY_APPLICATION
: Unable to verify the calling application (i.e token is invalid or application is not whitelisted).
Source code samples are provided for multiple APIs:
Example 1: Get Config
Intent i = new Intent();
i.setAction("com.symbol.datawedge.api.ACTION");
i.setPackage("com.symbol.datawedge");
i.putExtra("APPLICATION_PACKAGE", getPackageName());
i.putExtra("TOKEN", "291f7f7c-1479-4813-9639-2d4ab31e37b9");
i.putExtra("com.symbol.datawedge.api.GET_CONFIG", "Profile0 (default)");
this.sendBroadcast(i);
Example 2: Enable/Disable DataWedge
Intent i = new Intent();
i.setAction("com.symbol.datawedge.api.ACTION");
i.setPackage("com.symbol.datawedge");
i.putExtra("APPLICATION_PACKAGE", getPackageName());
i.putExtra("TOKEN", "<token here>"););
i.putExtra("SEND_RESULT","LAST_RESULT");
i.putExtra("com.symbol.datawedge.api.ENABLE_DATAWEDGE", true);
sendBroadcast(i);
Example 3: Set Config
Bundle bMain = new Bundle();
bMain.putString("PROFILE_NAME", "Profile009");
bMain.putString("PROFILE_ENABLED", "true");
bMain.putString("CONFIG_MODE", "CREATE_IF_NOT_EXIST");
Bundle bConfig = new Bundle();
bConfig.putString("PLUGIN_NAME","BARCODE");
bConfig.putString("RESET_CONFIG","true");
Bundle paramList = new Bundle();
paramList.putString("scanner_selection","auto");
paramList.putString("decoder_code11", "true");
bConfig.putBundle("PARAM_LIST", paramList);
bMain.putBundle("PLUGIN_CONFIG", bConfig);
Intent i = new Intent();
i.setAction("com.symbol.datawedge.api.ACTION");
i.setPackage("com.symbol.datawedge");
i.putExtra("APPLICATION_PACKAGE", getPackageName());
i.putExtra("TOKEN", "<token here>"););
i.putExtra("com.symbol.datawedge.api.SET_CONFIG", bMain);
i.putExtra("SEND_RESULT","LAST_RESULT");
i.putExtra("COMMAND_IDENTIFIER", "SET_CONFIG");
this.sendBroadcast(i);
Usage Notes
- When DataWedge APIs are set as "Controlled" and when the device restarts, sending an intent API to the "Controlled" group from a whitelisted application may return an error. Since MX framework did not complete initialization, allow some time to elapse after reboot before sending the intent to avoid this error.
- The token expires in the following situations:
- Device date or time is set prior to the timestamp on the token.
- 24 Hours passed in the device clock after generating the token.
- Automatic time updates caused the device time to go backward or move forward more than 24 hours.
- If intent APIs are placed in protected mode, existing Zebra apps that use DataWedge may not work as expected. E.g. If Runtime APIs are placed in protected mode, the scan button of the Enterprise Keyboard does not work on Enterprise Keyboard versions 4.0 or lower.
Related Guides: