diff --git a/README.md b/README.md index 42820fb..7a8153a 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ $ appknox reports create 4 3 $ appknox reports download summary-csv 3 -Organization ID,Project ID,Application Name,Application Namespace,Platform,Version,Version Code,File ID,Test Case,Scan Type,Severity,Risk Override,CVSS Score,Findings,Description,Noncompliant Code Example,Compliant Solution,Business Implication,OWASP,CWE,MSTG,OWASP MASVS (v2),ASVS,PCI-DSS,GDPR,Created On +Organization ID,Project ID,Application Name,Application Namespace,Platform,Version,Version Code,File ID,Test Case,Scan Type,Severity,Risk Override,CVSS Score,Findings,Description,Noncompliant Code Example,Compliant Solution,Business Implication,OWASP,CWE,MSTG,OWASP MASVS (v2),ASVS,PCI-DSS,GDPR,SAMA,Created On 1,1,MFVA,com.appknox.mfva,Android,1.1,1605631525,51,Broken SSL Trust Manager,Static,High,,6.9,"BluK8lNUoeHkNxZ3GVrKN9BP2 NVWmfbtHDiJBOTbOEpCnsbMhc6T31t...(Truncated) diff --git a/appknox/client.py b/appknox/client.py index acc889f..60cea4a 100644 --- a/appknox/client.py +++ b/appknox/client.py @@ -30,6 +30,7 @@ from appknox.mapper import Organization from appknox.mapper import OWASP from appknox.mapper import PCIDSS +from appknox.mapper import SAMA from appknox.mapper import PersonalToken from appknox.mapper import ProfileReportPreference from appknox.mapper import Project @@ -436,6 +437,25 @@ def get_pcidss(self, pcidss_id: str) -> PCIDSS: pcidss = self.drf_api["v2/pcidsses"](pcidss_id).get() return mapper_drf_api(PCIDSS, pcidss) + @lru_cache(maxsize=1) + def get_samas(self) -> List[SAMA]: + samas_raw = self.drf_api["v2/samas"]().get() + samas = self.paginated_drf_data(samas_raw, SAMA) + return samas + + def get_sama(self, sama_id: str) -> SAMA: + """ + Fetch SAMA by ID + + :param sama_id: sama ID + """ + samas = self.get_samas() + sama = next((x for x in samas if x.id == sama_id), None) + if sama: + return sama + sama = self.drf_api["v2/samas"](sama_id).get() + return mapper_drf_api(SAMA, sama) + def upload_file(self, file_data: str) -> int: """ Upload and scan a package and returns the file_id @@ -542,6 +562,8 @@ def get_unselected_report_preference(self, file_id: int) -> list: unselected_report_pref.append(ReportPreferenceMapper["show_hipaa"]) if not profile_report_preference.show_pcidss.value: unselected_report_pref.append(ReportPreferenceMapper["show_pcidss"]) + if not profile_report_preference.show_sama.value: + unselected_report_pref.append(ReportPreferenceMapper["show_sama"]) return unselected_report_pref def list_reports(self, file_id: int) -> typing.List["Report"]: diff --git a/appknox/mapper.py b/appknox/mapper.py index d38f413..e065a0a 100644 --- a/appknox/mapper.py +++ b/appknox/mapper.py @@ -69,6 +69,7 @@ def mapper_drf_api(model: type, resource: dict) -> object: "masvs", "asvs", "gdpr", + "sama", "computed_risk", "overridden_risk", ], @@ -93,12 +94,15 @@ def mapper_drf_api(model: type, resource: dict) -> object: PCIDSS = namedtuple("PCIDSS", ["id", "code", "title", "description"]) +SAMA = namedtuple("SAMA", ["id", "code", "title", "description"]) + PersonalToken = namedtuple("AccessToken", ["name", "key"]) ReportPreferenceMapper = { "show_pcidss": "pcidss", "show_hipaa": "hipaa", "show_gdpr": "gdpr", + "show_sama": "sama", } @@ -112,7 +116,7 @@ class ProfileReportPreference: show_gdpr: ProfileReportPreferenceConfig show_hipaa: ProfileReportPreferenceConfig show_pcidss: ProfileReportPreferenceConfig - + show_sama: ProfileReportPreferenceConfig @classmethod def from_json(cls, data): return cls( @@ -121,6 +125,9 @@ def from_json(cls, data): show_pcidss=ProfileReportPreferenceConfig( value=data["show_pcidss"]["value"] ), + show_sama=ProfileReportPreferenceConfig( + value=data["show_sama"]["value"] + ) ) @@ -146,6 +153,7 @@ class ReportPreference: "show_ignored_analyses", "show_hipaa", "show_pcidss", + "show_sama", ] show_api_scan: bool @@ -155,6 +163,7 @@ class ReportPreference: show_ignored_analyses: bool show_hipaa: InheritedPreference show_pcidss: InheritedPreference + show_sama: InheritedPreference @classmethod def from_json(cls, data: typing.Dict[str, typing.Any]) -> "ReportPreference": @@ -166,6 +175,7 @@ def from_json(cls, data: typing.Dict[str, typing.Any]) -> "ReportPreference": show_ignored_analyses=data["show_ignored_analyses"], show_hipaa=InheritedPreference.from_json(data["show_hipaa"]), show_pcidss=InheritedPreference.from_json(data["show_pcidss"]), + show_sama=InheritedPreference.from_json(data["show_sama"]), ) diff --git a/appknox/tests/test_client.py b/appknox/tests/test_client.py index 69be466..b84d5a0 100644 --- a/appknox/tests/test_client.py +++ b/appknox/tests/test_client.py @@ -25,6 +25,7 @@ def setUp(self): "show_ignored_analyses": True, "show_hipaa": {"value": True, "is_inherited": True}, "show_pcidss": {"value": True, "is_inherited": True}, + "show_sama": {"value":False, "is_inherited": False}, } with mock.patch.object(Appknox, "get_organizations", self.get_org_list): self.ap_client = Appknox( diff --git a/docs/.doctrees/client.doctree b/docs/.doctrees/client.doctree index 041626d..7fceb99 100644 Binary files a/docs/.doctrees/client.doctree and b/docs/.doctrees/client.doctree differ diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle index d61d0d0..50c44e6 100644 Binary files a/docs/.doctrees/environment.pickle and b/docs/.doctrees/environment.pickle differ diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree index 9a04860..8f621a5 100644 Binary files a/docs/.doctrees/index.doctree and b/docs/.doctrees/index.doctree differ diff --git a/docs/.doctrees/mapper.doctree b/docs/.doctrees/mapper.doctree index e982863..1c34b4e 100644 Binary files a/docs/.doctrees/mapper.doctree and b/docs/.doctrees/mapper.doctree differ diff --git a/docs/_modules/appknox/client.html b/docs/_modules/appknox/client.html index 87e06fd..fbbd726 100644 --- a/docs/_modules/appknox/client.html +++ b/docs/_modules/appknox/client.html @@ -16,8 +16,9 @@ + + -
@@ -62,6 +63,7 @@
from appknox.mapper import Organization
from appknox.mapper import OWASP
from appknox.mapper import PCIDSS
+from appknox.mapper import SAMA
from appknox.mapper import PersonalToken
from appknox.mapper import ProfileReportPreference
from appknox.mapper import Project
@@ -84,6 +86,7 @@ Source code for appknox.client
[docs]class Appknox(object):
"""
+ Client to interact with API server
"""
def __init__(
@@ -292,9 +295,7 @@ Source code for appknox.client
whoami = self.drf_api.me().get()
return mapper_drf_api(Whoami, whoami)
-[docs] def paginated_data(self, response, mapper_class):
- """
- """
+ def paginated_data(self, response, mapper_class):
initial_data = [
mapper_json_api(mapper_class, dict(data=value))
for value in response["data"]
@@ -313,11 +314,9 @@ Source code for appknox.client
for value in resp["data"]
]
- return initial_data
+ return initial_data
-[docs] def paginated_drf_data(self, response, mapper_class):
- """
- """
+ def paginated_drf_data(self, response, mapper_class):
initial_data = [
mapper_drf_api(mapper_class, value) for value in response["results"]
]
@@ -331,7 +330,7 @@ Source code for appknox.client
initial_data += [
mapper_drf_api(mapper_class, value) for value in resp["results"]
]
- return initial_data
+ return initial_data
[docs] def get_organizations(self) -> List[Organization]:
"""
@@ -404,10 +403,8 @@ Source code for appknox.client
analyses = self.drf_api["v2/files/{}/analyses".format(file_id)]().get()
return self.paginated_drf_data(analyses, Analysis)
-[docs] @lru_cache(maxsize=1)
+ @lru_cache(maxsize=1)
def get_vulnerabilities(self) -> List[Vulnerability]:
- """
- """
total_vulnerabilities = self.drf_api["v2/vulnerabilities"]().get(limit=1)[
"count"
] # limit is 1 just to get total count
@@ -415,7 +412,7 @@ Source code for appknox.client
limit=total_vulnerabilities + 1
)
vulnerabilities = self.paginated_drf_data(vulnerabilities_raw, Vulnerability)
- return vulnerabilities
+ return vulnerabilities
[docs] def get_vulnerability(self, vulnerability_id: int) -> Vulnerability:
"""
@@ -433,13 +430,11 @@ Source code for appknox.client
vulnerability = self.drf_api["v2/vulnerabilities"](vulnerability_id).get()
return mapper_drf_api(Vulnerability, vulnerability)
-[docs] @lru_cache(maxsize=1)
+ @lru_cache(maxsize=1)
def get_owasps(self) -> List[OWASP]:
- """
- """
owasps_raw = self.drf_api["v2/owasps"]().get()
owasps = self.paginated_drf_data(owasps_raw, OWASP)
- return owasps
+ return owasps
[docs] def get_owasp(self, owasp_id: str) -> OWASP:
"""
@@ -455,13 +450,11 @@ Source code for appknox.client
owasp = self.drf_api["v2/owasps"](owasp_id).get()
return mapper_drf_api(OWASP, owasp)
-[docs] @lru_cache(maxsize=1)
+ @lru_cache(maxsize=1)
def get_pcidsses(self) -> List[PCIDSS]:
- """
- """
pcidsss_raw = self.drf_api["v2/pcidsses"]().get()
pcidsss = self.paginated_drf_data(pcidsss_raw, PCIDSS)
- return pcidsss
+ return pcidsss
[docs] def get_pcidss(self, pcidss_id: str) -> PCIDSS:
"""
@@ -477,6 +470,25 @@ Source code for appknox.client
pcidss = self.drf_api["v2/pcidsses"](pcidss_id).get()
return mapper_drf_api(PCIDSS, pcidss)
+ @lru_cache(maxsize=1)
+ def get_samas(self) -> List[SAMA]:
+ samas_raw = self.drf_api["v2/samas"]().get()
+ samas = self.paginated_drf_data(samas_raw, SAMA)
+ return samas
+
+[docs] def get_sama(self, sama_id: str) -> SAMA:
+ """
+ Fetch SAMA by ID
+
+ :param sama_id: sama ID
+ """
+ samas = self.get_samas()
+ sama = next((x for x in samas if x.id == sama_id), None)
+ if sama:
+ return sama
+ sama = self.drf_api["v2/samas"](sama_id).get()
+ return mapper_drf_api(SAMA, sama)
+
[docs] def upload_file(self, file_data: str) -> int:
"""
Upload and scan a package and returns the file_id
@@ -583,6 +595,8 @@ Source code for appknox.client
unselected_report_pref.append(ReportPreferenceMapper["show_hipaa"])
if not profile_report_preference.show_pcidss.value:
unselected_report_pref.append(ReportPreferenceMapper["show_pcidss"])
+ if not profile_report_preference.show_sama.value:
+ unselected_report_pref.append(ReportPreferenceMapper["show_sama"])
return unselected_report_pref
[docs] def list_reports(self, file_id: int) -> typing.List["Report"]:
@@ -670,9 +684,6 @@ Source code for appknox.client
headers: object = None,
auth: Dict[str, str] = None,
):
- """
- Initialise APIResource object
- """
self.host = host
self.headers = {**headers}
self.headers["User-Agent"] = f"appknox-python/{__version__}"
@@ -682,37 +693,27 @@ Source code for appknox.client
self.endpoint = urljoin(host, API_BASE)
def __getitem__(self, resource):
- """
- """
return partial(self.set_endpoint, resource)
def __getattr__(self, resource):
- """
- """
return partial(self.set_endpoint, resource)
-[docs] def set_endpoint(self, resource, resource_id=None):
- """
- """
+ def set_endpoint(self, resource, resource_id=None):
self.endpoint = "{}/{}".format(urljoin(self.host, API_BASE), resource)
if resource_id:
self.endpoint += "/{}".format(str(resource_id))
- return self
+ return self
-[docs] def get(self, **kwargs):
- """
- """
+ def get(self, **kwargs):
resp = self.request_session.get(
self.endpoint,
headers=self.headers,
auth=self.auth,
params=kwargs,
)
- return resp.json()
+ return resp.json()
-[docs] def post(self, data, content_type=None, **kwargs):
- """
- """
+ def post(self, data, content_type=None, **kwargs):
resp = self.request_session.post(
self.endpoint,
headers=self.headers,
@@ -720,18 +721,16 @@ Source code for appknox.client
params=kwargs,
data=data,
)
- return resp.json()
+ return resp.json()
-[docs] def direct_get(self, url, **kwargs):
- """
- """
+ def direct_get(self, url, **kwargs):
resp = self.request_session.get(
url,
headers=self.headers,
auth=self.auth,
params=kwargs,
)
- return resp.json()
+ return resp.json()
[docs] def direct_get_http_response(self, url: str, **kwargs) -> "Response":
"""
@@ -754,7 +753,7 @@ Source code for appknox.client
Source code for appknox.mapper
"masvs",
"asvs",
"gdpr",
+ "sama",
"computed_risk",
"overridden_risk",
],
@@ -125,12 +127,15 @@ Source code for appknox.mapper
PCIDSS = namedtuple("PCIDSS", ["id", "code", "title", "description"])
+SAMA = namedtuple("SAMA", ["id", "code", "title", "description"])
+
PersonalToken = namedtuple("AccessToken", ["name", "key"])
ReportPreferenceMapper = {
"show_pcidss": "pcidss",
"show_hipaa": "hipaa",
"show_gdpr": "gdpr",
+ "show_sama": "sama",
}
@@ -144,7 +149,7 @@ Source code for appknox.mapper
show_gdpr: ProfileReportPreferenceConfig
show_hipaa: ProfileReportPreferenceConfig
show_pcidss: ProfileReportPreferenceConfig
-
+ show_sama: ProfileReportPreferenceConfig
@classmethod
def from_json(cls, data):
return cls(
@@ -153,6 +158,9 @@ Source code for appknox.mapper
show_pcidss=ProfileReportPreferenceConfig(
value=data["show_pcidss"]["value"]
),
+ show_sama=ProfileReportPreferenceConfig(
+ value=data["show_sama"]["value"]
+ )
)
@@ -178,6 +186,7 @@ Source code for appknox.mapper
"show_ignored_analyses",
"show_hipaa",
"show_pcidss",
+ "show_sama",
]
show_api_scan: bool
@@ -187,6 +196,7 @@ Source code for appknox.mapper
show_ignored_analyses: bool
show_hipaa: InheritedPreference
show_pcidss: InheritedPreference
+ show_sama: InheritedPreference
@classmethod
def from_json(cls, data: typing.Dict[str, typing.Any]) -> "ReportPreference":
@@ -198,6 +208,7 @@ Source code for appknox.mapper
show_ignored_analyses=data["show_ignored_analyses"],
show_hipaa=InheritedPreference.from_json(data["show_hipaa"]),
show_pcidss=InheritedPreference.from_json(data["show_pcidss"]),
+ show_sama=InheritedPreference.from_json(data["show_sama"]),
)
@@ -239,7 +250,7 @@ Source code for appknox.mapper
All modules for which code is available
diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt
index 86e1cbf..f4ec2af 100644
--- a/docs/_sources/index.rst.txt
+++ b/docs/_sources/index.rst.txt
@@ -165,91 +165,39 @@ Get the analyses for this new file:
.. code-block:: python
- >>> client.get_analyses(273)[:3]
- [Analysis(id = 22248, risk = 2, status = 3, cvss_base = 6.6, cvss_vector = 'CVSS:3.0/AV:A/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H', cvss_version = 3, cvss_metrics_humanized = [{
- 'key': 'Attack Vector',
- 'value': 'Adjacent'
- }, {
- 'key': 'Attack Complexity',
- 'value': 'Low'
- }, {
- 'key': 'Privileges Required',
- 'value': 'High'
- }, {
- 'key': 'User Interaction',
- 'value': 'Required'
- }, {
- 'key': 'Scope',
- 'value': 'Unchanged'
- }, {
- 'key': 'Confidentiality Impact',
- 'value': 'High'
- }, {
- 'key': 'Integrity Impact',
- 'value': 'High'
- }, {
- 'key': 'Availability Impact',
- 'value': 'High'
- }],
- findings = [{
- 'title': 'ssLA5o60a398i7TM5RkofIA1J',
- 'description': 'MfmnwBwK2HsWqnZMOJoDvWnhIFdVMn'
- }, {
- 'title': 'p9TPfBKLqtlExLklJYnifHO72',
- 'description': '0rppCThV5ybdROVlizmG5ryoWd7S7r'
- }, {
- 'title': 'DpqNGv4q8ZhrYgyobSpEuqiq7',
- 'description': 'BmQkMywysefELpWcG1OGYN9N98PdSi'
- }, {
- 'title': 'pcqd88I0ZLpRqKYD7lTrbGEEY',
- 'description': '7PYqk3Gg9J3Zr7nu8PKhv1tHH1NhdA'
- }, {
- 'title': 'TGdwRQOaFBQ9J046BRB2DJXn4',
- 'description': 'skEJq90yDVC5y0zmSD09f1rQyK8KNZ'
- }],
- updated_on = '2023-09-13T06:08:18.384903Z', vulnerability = 1, owasp = ['M1_2016'], pcidss = ['3_2', '3_3', '3_4'], hipaa = ['164_312_a_1'], cwe = ['CWE_926'], mstg = ['MSTG_6_3', 'MSTG_6_4'], masvs = ['MASVS_6_1'], asvs = [], gdpr = ['gdpr_25', 'gdpr_32'], computed_risk = 2, overridden_risk = None),
- Analysis(id = 22247, risk = 2, status = 3, cvss_base = 5.7, cvss_vector = 'CVSS:3.0/AV:P/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:N', cvss_version = 3, cvss_metrics_humanized = [{
- 'key': 'Attack Vector',
- 'value': 'Physical'
- }, {
- 'key': 'Attack Complexity',
- 'value': 'Low'
- }, {
- 'key': 'Privileges Required',
- 'value': 'High'
- }, {
- 'key': 'User Interaction',
- 'value': 'Not Required'
- }, {
- 'key': 'Scope',
- 'value': 'Changed'
- }, {
- 'key': 'Confidentiality Impact',
- 'value': 'Low'
- }, {
- 'key': 'Integrity Impact',
- 'value': 'High'
- }, {
- 'key': 'Availability Impact',
- 'value': 'None'
- }],
- findings = [{
- 'title': 'y4iutu3KCWb7shg6BsZqu867Y',
- 'description': 'cqB9EcXpGrvQbsrGNMProR3J1cbmxw'
- }, {
- 'title': 'kPLH7e9juz1wq2JCBJrVR9fnb',
- 'description': '2rSLRxGXZbeSZ437l5bzKTTwwSB7il'
- }, {
- 'title': 'qUObDBfoIvOSbVgyhQwxBWOY6',
- 'description': 'iHdHrlq0dCA1gxjWyo4wnGZ3flmr70'
- }, {
- 'title': 'l1i4LxUXU3PaMv1wsYaN7zzLu',
- 'description': '5g4ml46nrfndL7M4V43ZbkEXVX0bVn'
- }, {
- 'title': 'OgZY2lNjHTPqvNl75bupA3tNH',
- 'description': 'IiwQX1xQDjX5t4W6Y9KyWIrMdeREtw'
- }],
- updated_on = '2023-09-13T06:08:21.540225Z', vulnerability = 2, owasp = ['M1_2016'], pcidss = ['3_2', '3_3', '3_4'], hipaa = ['164_312_a_1'], cwe = ['CWE_926'], mstg = ['MSTG_6_1'], masvs = ['MASVS_6_1'], asvs = [], gdpr = ['gdpr_25', 'gdpr_32'], computed_risk = 2, overridden_risk = None), ]
+ >>> client.get_analyses(1560)[2]
+ Analysis(
+ id=128060,
+ risk=3,
+ status=3,
+ cvss_base=7.7,
+ cvss_vector="CVSS:3.0/AV:L/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H",
+ cvss_version=3,
+ cvss_metrics_humanized=[
+ {"key": "Attack Vector", "value": "Local"},
+ {"key": "Attack Complexity", "value": "High"},
+ {"key": "Privileges Required", "value": "None"},
+ {"key": "User Interaction", "value": "Required"},
+ {"key": "Scope", "value": "Changed"},
+ {"key": "Confidentiality Impact", "value": "High"},
+ {"key": "Integrity Impact", "value": "High"},
+ {"key": "Availability Impact", "value": "High"},
+ ],
+ findings=[{"title": None, "description": "Debug enabled within the app"}],
+ updated_on="2024-08-06T13:57:07.148861Z",
+ vulnerability=3,
+ owasp=[],
+ pcidss=[],
+ hipaa=["164_312_a_1"],
+ cwe=["CWE_489"],
+ mstg=["MSTG_7_2"],
+ masvs=["MASVS_7_4"],
+ asvs=[],
+ gdpr=["gdpr_25", "gdpr_32"],
+ sama=["3_3_6"],
+ computed_risk=3,
+ overridden_risk=None,
+ )
Note the ``vulnerability_id`` for ``Analysis(id=235)``. To get details about this vulnerability:
@@ -432,7 +380,7 @@ __
Download Report Data from URL
------------------------------
-Returns full HTTP response body from a given absolute URL
+Returns full HTTP response body from a given absolute URL
.. code-block:: python
@@ -459,7 +407,7 @@ Complete Reference
:maxdepth: 2
client
-
+
mapper
--
diff --git a/docs/_static/alabaster.css b/docs/_static/alabaster.css
index 6476cd5..c46b859 100644
--- a/docs/_static/alabaster.css
+++ b/docs/_static/alabaster.css
@@ -69,6 +69,11 @@ div.relations {
}
+div.sphinxsidebar {
+ max-height: 100%;
+ overflow-y: auto;
+}
+
div.sphinxsidebar a {
color: #444;
text-decoration: none;
@@ -155,6 +160,14 @@ div.sphinxsidebar input {
font-size: 1em;
}
+div.sphinxsidebar #searchbox input[type="text"] {
+ width: 160px;
+}
+
+div.sphinxsidebar .search > div {
+ display: table-cell;
+}
+
div.sphinxsidebar hr {
border: none;
height: 1px;
@@ -638,15 +651,7 @@ a:hover tt, a:hover code {
display: none!important;
}
-/* Make nested-list/multi-paragraph items look better in Releases changelog
- * pages. Without this, docutils' magical list fuckery causes inconsistent
- * formatting between different release sub-lists.
- */
-div#changelog > div.section > ul > li > p:only-child {
- margin-bottom: 0;
-}
-
-/* Hide fugly table cell borders in ..bibliography:: directive output */
+/* Hide ugly table cell borders in ..bibliography:: directive output */
table.docutils.citation, table.docutils.citation td, table.docutils.citation th {
border: none;
/* Below needed in some edge cases; if not applied, bottom shadows appear */
diff --git a/docs/_static/basic.css b/docs/_static/basic.css
index 7577acb..2d467bf 100644
--- a/docs/_static/basic.css
+++ b/docs/_static/basic.css
@@ -222,7 +222,7 @@ table.modindextable td {
/* -- general body styles --------------------------------------------------- */
div.body {
- min-width: 360px;
+ min-width: inherit;
max-width: 800px;
}
diff --git a/docs/client.html b/docs/client.html
index 551956e..dac366b 100644
--- a/docs/client.html
+++ b/docs/client.html
@@ -19,8 +19,9 @@
+
+
-
@@ -170,6 +171,17 @@
in the given organizations
+
diff --git a/docs/_modules/index.html b/docs/_modules/index.html
index f42964a..3dbb979 100644
--- a/docs/_modules/index.html
+++ b/docs/_modules/index.html
@@ -16,8 +16,9 @@
+
+
-
@@ -42,7 +43,7 @@
+
diff --git a/docs/_modules/appknox/mapper.html b/docs/_modules/appknox/mapper.html
index a85c6fa..7c6cc9d 100644
--- a/docs/_modules/appknox/mapper.html
+++ b/docs/_modules/appknox/mapper.html
@@ -16,8 +16,9 @@
+
+
-
@@ -101,6 +102,7 @@