package com.ensoftcorp.open.android.essentials.permissions.mappings;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.ensoftcorp.atlas.core.db.graph.GraphElement;
import com.ensoftcorp.atlas.core.db.set.AtlasHashSet;
import com.ensoftcorp.atlas.core.query.Q;
import com.ensoftcorp.atlas.java.core.script.Common;
import com.ensoftcorp.open.android.essentials.log.Log;
import com.ensoftcorp.open.android.essentials.permissions.Permission;
import com.ensoftcorp.open.android.essentials.permissions.PermissionGroup;
import com.ensoftcorp.open.android.essentials.permissions.ProtectionLevel;
/**
* Applies permission mapping results on the Atlas program graph
*
* @author Ben Holland, Yuqing Chen, Xing Yan
*/
public class PermissionMapping {
/**
* Defines the highest available permission mapping, if no mapping can be found
* for the requested version this is the default.
*/
public static final int HIGHEST_AVAILABLE_MAPPING = 22;
private static final String MAPPING_FILENAME_PREFIX = "API";
private static final ArrayList<InputStream> PERMISSION_MAPPING_RESOURCES = findPermissionMappingResources();
// helper method to locate all available mappings up to the highest available mapping
private static ArrayList<InputStream> findPermissionMappingResources(){
ArrayList<InputStream> permissionMappingResources = new ArrayList<InputStream>();
for(int apiVersion = 1; apiVersion <= HIGHEST_AVAILABLE_MAPPING; apiVersion++){
String xmlFile = MAPPING_FILENAME_PREFIX + apiVersion + ".xml";
permissionMappingResources.add(PermissionMapping.class.getResourceAsStream(xmlFile));
}
return permissionMappingResources;
}
/**
* Returns the permission tagged methods for the permission instance
* Assumes the permission mapping tags have already been applied
* @param apiVersion
* @return
*/
public static Q getMethods(Permission permission, int apiVersion){
return PermissionMapping.getPermissionTaggedMethods(permission, apiVersion);
}
/**
* Returns the permission tagged methods for the permissions in the permission group
* Assumes the permission mapping tags have already been applied
* @param apiVersion
* @return
*/
public static Q getMethods(PermissionGroup permissionGroup, int apiVersion){
AtlasHashSet<GraphElement> methods = new AtlasHashSet<GraphElement>();
for(Permission permission : permissionGroup.getPermissions()){
methods.addAll(getMethods(permission, apiVersion).eval().nodes());
}
return Common.toQ(Common.toGraph(methods));
}
/**
* Returns the permission tagged methods for the permissions in the protection level
* Assumes the permission mapping tags have already been applied
* @param apiVersion
* @return
*/
public static Q getMethods(ProtectionLevel protectionLevel, int apiVersion){
AtlasHashSet<GraphElement> methods = new AtlasHashSet<GraphElement>();
for(Permission permission : protectionLevel.getPermissions()){
methods.addAll(getMethods(permission, apiVersion).eval().nodes());
}
return Common.toQ(Common.toGraph(methods));
}
/**
* Returns the tagging prefix for a given android api version
* @param apiVersion
* @return
*/
public static String getTagPrefix(int apiVersion) {
return getBestAvailableMapping(apiVersion) + "-";
}
/**
* Returns a sorted list of Android API versions that mappings exist for
* @return
*/
public static ArrayList<Integer> getAvailableMappings() {
ArrayList<Integer> availableMappings = new ArrayList<Integer>();
for(int i = 0; i>=0 && i<PERMISSION_MAPPING_RESOURCES.size(); i++){
if(PERMISSION_MAPPING_RESOURCES.get(i) != null){
int version = (i+1);
availableMappings.add(version);
}
}
Collections.sort(availableMappings);
return availableMappings;
}
/**
* Returns the api version of the closest available permission mapping to a given android api version
* Searches for forward for next higher available mapping
* If no mapping is found, defaults to the highest available mapping
* @param apiVersion
* @return
*/
public static int getBestAvailableMapping(int apiVersion) {
int version = -1;
// search for the requested mapping or next available mapping
for(int i = (apiVersion-1); i>=0 && i<PERMISSION_MAPPING_RESOURCES.size(); i++){
if(PERMISSION_MAPPING_RESOURCES.get(i) != null){
version = (i+1);
break;
}
}
// if mapping is still not found, just default to the highest available mapping
if(version == -1){
version = HIGHEST_AVAILABLE_MAPPING;
}
return version;
}
/**
* Returns a Q of permission tagged nodes for the given API version
* @param permission
* @param apiVersion
* @return
*/
public static Q getPermissionTaggedMethods(Permission permission, int apiVersion){
return Common.universe().nodesTaggedWithAll(getPermissionTag(permission, apiVersion));
}
/**
* Returns the tag string for a given permission and Android api version
* @param permission
* @param apiVersion
* @return
*/
private static String getPermissionTag(Permission permission, int apiVersion){
return Integer.toString(apiVersion) + "-" + permission.getQualifiedName();
}
/**
* This method applies tags to the Android api's. The versions with no
* Permission mapping will switch to the next available version.
*/
public static HashMap<Permission, AtlasHashSet<GraphElement>> applyTags(int apiVersion) {
Log.info("Applying tags for permission mapping " + apiVersion + "...");
// based on the input API version, round up to the nearest available API mapping
apiVersion = getBestAvailableMapping(apiVersion);
// apply the mapping tags
HashMap<Permission, AtlasHashSet<GraphElement>> permissionMap = new HashMap<Permission, AtlasHashSet<GraphElement>>();
try {
// read xml file that is under the same package of this class
String xmlFile = MAPPING_FILENAME_PREFIX + apiVersion + ".xml";
DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder();
Document doc = documentBuilder.parse(PermissionMapping.class.getResourceAsStream(xmlFile));
// get the permission tag list
NodeList permissionList = doc.getElementsByTagName("permission");
for (int permissionNum = 0; permissionNum < permissionList.getLength(); permissionNum++) {
Node permissionNode = permissionList.item(permissionNum);
if (permissionNode.getNodeType() == Node.ELEMENT_NODE) {
// select the current permission tag
Element permission = (Element) permissionNode;
String permissionName = permission.getAttribute("name");
// get the call tag list under current permission tag
NodeList callList = permission.getElementsByTagName("call");
// initialize the hashset for storing found method
AtlasHashSet<GraphElement> methods = new AtlasHashSet<GraphElement>();
for (int callNum = 0; callNum < callList.getLength(); callNum++) {
Node callNode = callList.item(callNum);
if (callNode.getNodeType() == Node.ELEMENT_NODE) {
// select current call tag
Element call = (Element) callNode;
// get package, class, method, return type
// under this call tag
String packageName = call.getElementsByTagName("package").item(0).getTextContent();
String className = call.getElementsByTagName("class").item(0).getTextContent();
String methodName = call.getElementsByTagName("method").item(0).getTextContent();
// // TODO: optionally use return type to enhance method check
// String returnType = call.getElementsByTagName("returnType").item(0).getTextContent();
// get parameters tag under call tag
Node parameterNodes = call.getElementsByTagName("parameters").item(0);
Element parameters = (Element) parameterNodes;
// get parameter names under parameters tags
NodeList parameterList = parameters.getElementsByTagName("parameter");
int parametersNum = parameterList.getLength();
ArrayList<String> paramList = new ArrayList<String>();
for (int parameterNum = 0; parameterNum < parametersNum; parameterNum++) {
Element parameter = (Element) parameterList.item(parameterNum);
paramList.add(parameter.getTextContent());
}
// find Method to search inside index workspace and apply tag
String apiWithQualifiedPermissionTag = apiVersion + "-" + permissionName;
String qualifiedPermissionTag = permissionName;
GraphElement methodNode = PermissionUtils.tagMethod(packageName, className, methodName, paramList.toArray(new String[0]), apiWithQualifiedPermissionTag, qualifiedPermissionTag);
// add method node to hashset
if (methodNode != null){
methods.add(methodNode);
}
}
}// end the for loop for each call(method)
// add permission->methods to the map for each permission
permissionMap.put(Permission.getPermissionByQualifiedName(permissionName), methods);
}// end the if for each permission
}// end the for loop for permission
} catch (Exception e) {
throw new RuntimeException("Tagging permissions failed", e);
}
return permissionMap;
}
}