Skip to content

Commit 580e63a

Browse files
committed
Adding permission mapping support
1 parent 0f595f8 commit 580e63a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+151886
-3
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,15 @@ $ pip install apkparser-ag
5050

5151
## Usage
5252

53+
5354
## API
5455

56+
### File Extraction
57+
58+
### Signature
59+
60+
### Permissions
61+
5562
## License
5663

5764
Distributed under the [Apache License, Version 2.0](LICENSE).

apkparser/permissions/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from axml.axml import AXMLPrinter
22

3+
from .ressources import load_api_specific_resource_module
34
from apkparser.helper.logging import LOGGER
45

56
# Dictionary of the different protection levels mapped to their corresponding attribute names as described in
@@ -41,10 +42,20 @@ def __init__(self, apk) -> None:
4142
self._apk = apk
4243
self.declared_permissions = {}
4344

45+
# Copy permissions from the AXML module for easy usage !
4446
self.permissions = apk.axml.permissions.copy()
4547
self.uses_permissions = apk.axml.uses_permissions.copy()
4648

4749

50+
self.permission_module = load_api_specific_resource_module(
51+
"aosp_permissions", apk.axml.get_target_sdk_version()
52+
)
53+
self.permission_module_min_sdk = (
54+
load_api_specific_resource_module(
55+
"aosp_permissions", apk.axml.get_min_sdk_version()
56+
)
57+
)
58+
4859
# getting details of the declared permissions
4960
for d_perm_item in apk.axml.find_tags('permission'):
5061
d_perm_name = apk._get_res_string_value(
@@ -140,9 +151,10 @@ def _update_permission_protection_level(
140151
return protection_level
141152

142153
def _fill_deprecated_permissions(self, permissions):
143-
min_sdk = self.get_min_sdk_version()
144-
target_sdk = self.get_target_sdk_version()
154+
min_sdk = self._apk.axml.get_min_sdk_version()
155+
target_sdk = self._apk.axml.get_target_sdk_version()
145156
filled_permissions = permissions.copy()
157+
146158
for permission in filled_permissions:
147159
protection_level, label, description = filled_permissions[
148160
permission
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import json
2+
import os
3+
import re
4+
from typing import Union
5+
6+
from apkparser.helper.logging import LOGGER
7+
8+
9+
class InvalidResourceError(Exception):
10+
pass
11+
12+
class APILevelNotFoundError(Exception):
13+
pass
14+
15+
16+
def load_api_specific_resource_module(
17+
resource_name: str, api: Union[str, int, None] = None, default_api: int = 16
18+
) -> dict:
19+
"""
20+
Load the module from the JSON files and return a dict, which might be empty
21+
if the resource could not be loaded.
22+
23+
If no api version is given, the default one from the CONF dict is used.
24+
25+
:param resource_name: Name of the resource to load
26+
:param api: API version
27+
:param default_api: Default API version
28+
:raises InvalidResourceError: if resource not found
29+
:returns: dict
30+
"""
31+
loader = dict(
32+
aosp_permissions=load_permissions,
33+
api_permission_mappings=load_permission_mappings,
34+
)
35+
36+
if resource_name not in loader:
37+
raise InvalidResourceError(
38+
"Invalid Resource '{}', not in [{}]".format(
39+
resource_name, ", ".join(loader.keys())
40+
)
41+
)
42+
43+
if not api:
44+
api = default_api
45+
46+
ret = loader[resource_name](api)
47+
48+
if ret == {}:
49+
# No API mapping found, return default
50+
LOGGER.warning(
51+
"API mapping for API level {} was not found! "
52+
"Returning default, which is API level {}".format(
53+
api, CONF['DEFAULT_API']
54+
)
55+
)
56+
ret = loader[resource_name](default_api)
57+
58+
return ret
59+
60+
def load_permissions(
61+
apilevel: Union[str, int], permtype: str = 'permissions'
62+
) -> dict[str, dict[str, str]]:
63+
"""
64+
Load the Permissions for the given apilevel.
65+
66+
The permissions lists are generated using this tool: https://github.com/U039b/aosp_permissions_extraction
67+
68+
Has a fallback to select the maximum or minimal available API level.
69+
For example, if 28 is requested but only 26 is available, 26 is returned.
70+
If 5 is requested but 16 is available, 16 is returned.
71+
72+
If an API level is requested which is in between of two API levels we got,
73+
the lower level is returned. For example, if 5,6,7,10 is available and 8 is
74+
requested, 7 is returned instead.
75+
76+
:param apilevel: integer value of the API level
77+
:param permtype: either load permissions (`'permissions'`) or
78+
permission groups (`'groups'`)
79+
:return: a dictionary of {Permission Name: {Permission info}
80+
"""
81+
if permtype not in ['permissions', 'groups']:
82+
raise ValueError("The type of permission list is not known.")
83+
84+
# Usually apilevel is supplied as string...
85+
apilevel = int(apilevel)
86+
87+
root = os.path.dirname(os.path.realpath(__file__))
88+
permissions_file = os.path.join(
89+
root, "aosp_permissions", "permissions_{}.json".format(apilevel)
90+
)
91+
92+
levels = filter(
93+
lambda x: re.match(r'^permissions_\d+\.json$', x),
94+
os.listdir(os.path.join(root, "aosp_permissions")),
95+
)
96+
levels = list(map(lambda x: int(x[:-5].split('_')[1]), levels))
97+
98+
if not levels:
99+
LOGGER.error("No Permissions available, can not load!")
100+
return {}
101+
102+
LOGGER.debug(
103+
"Available API levels: {}".format(", ".join(map(str, sorted(levels))))
104+
)
105+
106+
if not os.path.isfile(permissions_file):
107+
if apilevel > max(levels):
108+
LOGGER.warning(
109+
"Requested API level {} is larger than maximum we have, returning API level {} instead.".format(
110+
apilevel, max(levels)
111+
)
112+
)
113+
return load_permissions(max(levels), permtype)
114+
if apilevel < min(levels):
115+
LOGGER.warning(
116+
"Requested API level {} is smaller than minimal we have, returning API level {} instead.".format(
117+
apilevel, max(levels)
118+
)
119+
)
120+
return load_permissions(min(levels), permtype)
121+
122+
# Missing level between existing ones, return the lower level
123+
lower_level = max(filter(lambda x: x < apilevel, levels))
124+
LOGGER.warning(
125+
"Requested API Level could not be found, using {} instead".format(
126+
lower_level
127+
)
128+
)
129+
return load_permissions(lower_level, permtype)
130+
131+
with open(permissions_file, "r") as fp:
132+
return json.load(fp)[permtype]
133+
134+
135+
def load_permission_mappings(
136+
apilevel: Union[str, int]
137+
) -> dict[str, list[str]]:
138+
"""
139+
Load the API/Permission mapping for the requested API level.
140+
If the requetsed level was not found, None is returned.
141+
142+
:param apilevel: integer value of the API level, i.e. 24 for Android 7.0
143+
:return: a dictionary of {MethodSignature: [List of Permissions]}
144+
"""
145+
root = os.path.dirname(os.path.realpath(__file__))
146+
permissions_file = os.path.join(
147+
root, "api_permission_mappings", "permissions_{}.json".format(apilevel)
148+
)
149+
150+
if not os.path.isfile(permissions_file):
151+
return {}
152+
153+
with open(permissions_file, "r") as fp:
154+
return json.load(fp)

0 commit comments

Comments
 (0)