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 @@

Source code for appknox.client

 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

         
@@ -825,11 +824,11 @@

Quick search

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 @@

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

         
@@ -310,11 +321,11 @@

Quick search

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 @@

All modules for which code is available

@@ -111,11 +112,11 @@

Quick search

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

+
+
+get_sama(sama_id: str) SAMA[source]
+

Fetch SAMA by ID

+
+
Parameters:
+

sama_id – sama ID

+
+
+
+
get_summary_csv_report_url(report_id: int) str[source]
@@ -312,7 +324,7 @@
@@ -387,11 +399,11 @@

Quick search