diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 413a7b0..6ae31ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index bb9fcfa..29319c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). +## [0.0.22] - 2023-07-13 +### Changed +- Update core APIs for 2.13. +- Update the APIs of the following add-ons: + - AJAX Spider version 23.15.0; + - Alert Filters version 17; + - GraphQL Support version 0.18.0; + - Network version 0.10.0; + - Selenium version 15.13.0; + - Spider version 0.5.0. + ## [0.0.21] - 2022-10-28 ### Added - Add the API of the following add-on: @@ -125,6 +136,7 @@ ensure it's automatically sent in all API requests. ### Changed - Moved from the main `zaproxy` repository. +[0.0.22]: https://github.com/zaproxy/zap-api-python/compare/0.0.21...0.0.22 [0.0.21]: https://github.com/zaproxy/zap-api-python/compare/0.0.20...0.0.21 [0.0.20]: https://github.com/zaproxy/zap-api-python/compare/0.0.19...0.0.20 [0.0.19]: https://github.com/zaproxy/zap-api-python/compare/0.0.18...0.0.19 diff --git a/setup.py b/setup.py index a2082f3..3dbb1fa 100755 --- a/setup.py +++ b/setup.py @@ -26,13 +26,13 @@ ) setup( name="python-owasp-zap-v2.4", - version="0.0.21", - description="OWASP ZAP 2.12 API client", - long_description="OWASP Zed Attack Proxy 2.12 API Python client (the 2.4 package name has been kept to make it easier to upgrade)", + version="0.0.22", + description="OWASP ZAP 2.13 API client", + long_description="OWASP Zed Attack Proxy 2.13 API Python client (the 2.4 package name has been kept to make it easier to upgrade)", author="ZAP development team", author_email='', url="https://www.zaproxy.org/", - download_url="https://github.com/zaproxy/zap-api-python/releases/tag/0.0.21", + download_url="https://github.com/zaproxy/zap-api-python/releases/tag/0.0.22", platforms=['any'], license="ASL2.0", package_dir={ @@ -47,9 +47,9 @@ 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ], install_requires=install_dependencies, tests_require=test_requirements, diff --git a/src/examples/zap_example_api_script.py b/src/examples/zap_example_api_script.py index dd59b91..3ba63ae 100755 --- a/src/examples/zap_example_api_script.py +++ b/src/examples/zap_example_api_script.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python ''' This script aims to be the most generic and the most explicit possible. @@ -354,6 +354,7 @@ ascan.set_policy_attack_strength(id=policyId, attackstrength=attackStrength, scanpolicyname=scanPolicyName) + ascanIds = ",".join(str(id) for id in ascanIds) if isWhiteListPolicy: # Disable all active scanners in order to enable only what you need pprint('Disable all scanners -> ' + diff --git a/src/zapv2/__init__.py b/src/zapv2/__init__.py index 1a66d0d..ae4040d 100644 --- a/src/zapv2/__init__.py +++ b/src/zapv2/__init__.py @@ -20,7 +20,7 @@ """ __docformat__ = 'restructuredtext' -__version__ = '0.0.21' +__version__ = '0.0.22' import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning diff --git a/src/zapv2/ajaxSpider.py b/src/zapv2/ajaxSpider.py index 15f7180..fead181 100644 --- a/src/zapv2/ajaxSpider.py +++ b/src/zapv2/ajaxSpider.py @@ -35,6 +35,13 @@ def allowed_resources(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ajaxSpider/view/allowedResources/'))) + def excluded_elements(self, contextname): + """ + Gets the excluded elements. The excluded elements are not clicked during crawling, for example, to prevent logging out. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ajaxSpider/view/excludedElements/', {'contextName': contextname}))) + @property def status(self): """ @@ -196,6 +203,51 @@ def add_allowed_resource(self, regex, enabled=None, apikey=''): params['enabled'] = enabled return six.next(six.itervalues(self.zap._request(self.zap.base + 'ajaxSpider/action/addAllowedResource/', params))) + def add_excluded_element(self, contextname, description, element, xpath=None, text=None, attributename=None, attributevalue=None, enabled=None, apikey=''): + """ + Adds an excluded element to a context. + This component is optional and therefore the API will only work if it is installed + """ + params = {'contextName': contextname, 'description': description, 'element': element, 'apikey': apikey} + if xpath is not None: + params['xpath'] = xpath + if text is not None: + params['text'] = text + if attributename is not None: + params['attributeName'] = attributename + if attributevalue is not None: + params['attributeValue'] = attributevalue + if enabled is not None: + params['enabled'] = enabled + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ajaxSpider/action/addExcludedElement/', params))) + + def modify_excluded_element(self, contextname, description, element, descriptionnew=None, xpath=None, text=None, attributename=None, attributevalue=None, enabled=None, apikey=''): + """ + Modifies an excluded element of a context. + This component is optional and therefore the API will only work if it is installed + """ + params = {'contextName': contextname, 'description': description, 'element': element, 'apikey': apikey} + if descriptionnew is not None: + params['descriptionNew'] = descriptionnew + if xpath is not None: + params['xpath'] = xpath + if text is not None: + params['text'] = text + if attributename is not None: + params['attributeName'] = attributename + if attributevalue is not None: + params['attributeValue'] = attributevalue + if enabled is not None: + params['enabled'] = enabled + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ajaxSpider/action/modifyExcludedElement/', params))) + + def remove_excluded_element(self, contextname, description, apikey=''): + """ + Removes an excluded element from a context. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ajaxSpider/action/removeExcludedElement/', {'contextName': contextname, 'description': description, 'apikey': apikey}))) + def remove_allowed_resource(self, regex, apikey=''): """ Removes an allowed resource. diff --git a/src/zapv2/alertFilter.py b/src/zapv2/alertFilter.py index 3fbfd7b..fe32827 100644 --- a/src/zapv2/alertFilter.py +++ b/src/zapv2/alertFilter.py @@ -42,7 +42,7 @@ def global_alert_filter_list(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'alertFilter/view/globalAlertFilterList/'))) - def add_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, apikey=''): + def add_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, methods=None, apikey=''): """ Adds a new alert filter for the context with the given ID. This component is optional and therefore the API will only work if it is installed @@ -66,9 +66,11 @@ def add_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex=Non params['evidence'] = evidence if evidenceisregex is not None: params['evidenceIsRegex'] = evidenceisregex + if methods is not None: + params['methods'] = methods return six.next(six.itervalues(self.zap._request(self.zap.base + 'alertFilter/action/addAlertFilter/', params))) - def remove_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, apikey=''): + def remove_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, methods=None, apikey=''): """ Removes an alert filter from the context with the given ID. This component is optional and therefore the API will only work if it is installed @@ -92,9 +94,11 @@ def remove_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex= params['evidence'] = evidence if evidenceisregex is not None: params['evidenceIsRegex'] = evidenceisregex + if methods is not None: + params['methods'] = methods return six.next(six.itervalues(self.zap._request(self.zap.base + 'alertFilter/action/removeAlertFilter/', params))) - def add_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, apikey=''): + def add_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, methods=None, apikey=''): """ Adds a new global alert filter. This component is optional and therefore the API will only work if it is installed @@ -118,9 +122,11 @@ def add_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None, p params['evidence'] = evidence if evidenceisregex is not None: params['evidenceIsRegex'] = evidenceisregex + if methods is not None: + params['methods'] = methods return six.next(six.itervalues(self.zap._request(self.zap.base + 'alertFilter/action/addGlobalAlertFilter/', params))) - def remove_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, apikey=''): + def remove_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, methods=None, apikey=''): """ Removes a global alert filter. This component is optional and therefore the API will only work if it is installed @@ -144,6 +150,8 @@ def remove_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None params['evidence'] = evidence if evidenceisregex is not None: params['evidenceIsRegex'] = evidenceisregex + if methods is not None: + params['methods'] = methods return six.next(six.itervalues(self.zap._request(self.zap.base + 'alertFilter/action/removeGlobalAlertFilter/', params))) def apply_all(self, apikey=''): diff --git a/src/zapv2/ascan.py b/src/zapv2/ascan.py index 86404f5..74852d5 100644 --- a/src/zapv2/ascan.py +++ b/src/zapv2/ascan.py @@ -163,6 +163,13 @@ def option_host_per_scan(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionHostPerScan/'))) + @property + def option_max_alerts_per_rule(self): + """ + Gets the maximum number of alerts that a rule can raise before being skipped. + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionMaxAlertsPerRule/'))) + @property def option_max_chart_time_in_mins(self): """ @@ -236,7 +243,7 @@ def option_allow_attack_on_start(self): @property def option_inject_plugin_id_in_header(self): """ - Tells whether or not the active scanner should inject the HTTP request header X-ZAP-Scan-ID, with the ID of the scanner that's sending the requests. + Tells whether or not the active scanner should inject the HTTP request header X-ZAP-Scan-ID, with the ID of the scan rule that's sending the requests. """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionInjectPluginIdInHeader/'))) @@ -531,7 +538,7 @@ def remove_excluded_param(self, idx, apikey=''): def skip_scanner(self, scanid, scannerid, apikey=''): """ - Skips the scanner using the given IDs of the scan and the scanner. + Skips the scan rule using the given IDs of the scan and the scan rule. """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/skipScanner/', {'scanId': scanid, 'scannerId': scannerid, 'apikey': apikey}))) @@ -579,10 +586,16 @@ def set_option_host_per_scan(self, integer, apikey=''): def set_option_inject_plugin_id_in_header(self, boolean, apikey=''): """ - Sets whether or not the active scanner should inject the HTTP request header X-ZAP-Scan-ID, with the ID of the scanner that's sending the requests. + Sets whether or not the active scanner should inject the HTTP request header X-ZAP-Scan-ID, with the ID of the scan rule that's sending the requests. """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/setOptionInjectPluginIdInHeader/', {'Boolean': boolean, 'apikey': apikey}))) + def set_option_max_alerts_per_rule(self, integer, apikey=''): + """ + Sets the maximum number of alerts that a rule can raise before being skipped. + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/setOptionMaxAlertsPerRule/', {'Integer': integer, 'apikey': apikey}))) + def set_option_max_chart_time_in_mins(self, integer, apikey=''): """ diff --git a/src/zapv2/graphql.py b/src/zapv2/graphql.py index 5becbb6..b981bab 100644 --- a/src/zapv2/graphql.py +++ b/src/zapv2/graphql.py @@ -75,6 +75,14 @@ def option_optional_args_enabled(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'graphql/view/optionOptionalArgsEnabled/'))) + @property + def option_query_gen_enabled(self): + """ + Returns whether the query generator is enabled. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'graphql/view/optionQueryGenEnabled/'))) + @property def option_query_split_type(self): """ @@ -163,3 +171,10 @@ def set_option_optional_args_enabled(self, boolean, apikey=''): This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'graphql/action/setOptionOptionalArgsEnabled/', {'Boolean': boolean, 'apikey': apikey}))) + + def set_option_query_gen_enabled(self, boolean, apikey=''): + """ + Sets whether the query generator is enabled. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'graphql/action/setOptionQueryGenEnabled/', {'Boolean': boolean, 'apikey': apikey}))) diff --git a/src/zapv2/network.py b/src/zapv2/network.py index 50edb80..c5f37d8 100644 --- a/src/zapv2/network.py +++ b/src/zapv2/network.py @@ -147,6 +147,14 @@ def is_use_global_http_state(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/view/isUseGlobalHttpState/'))) + @property + def get_rate_limit_rules(self): + """ + List of rate limit rules. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/view/getRateLimitRules/'))) + def generate_root_ca_cert(self, apikey=''): """ Generates a new Root CA certificate, used to issue server certificates. @@ -281,7 +289,7 @@ def add_http_proxy_exclusion(self, host, enabled=None, apikey=''): def remove_http_proxy_exclusion(self, host, apikey=''): """ - Removes a HTTP proxy exclusion. + Removes an HTTP proxy exclusion. This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/action/removeHttpProxyExclusion/', {'host': host, 'apikey': apikey}))) @@ -316,7 +324,7 @@ def set_http_proxy_enabled(self, enabled, apikey=''): def set_http_proxy_exclusion_enabled(self, host, enabled, apikey=''): """ - Sets whether or not a HTTP proxy exclusion is enabled. + Sets whether or not an HTTP proxy exclusion is enabled. This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/action/setHttpProxyExclusionEnabled/', {'host': host, 'enabled': enabled, 'apikey': apikey}))) @@ -368,6 +376,27 @@ def set_use_client_certificate(self, use, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/action/setUseClientCertificate/', {'use': use, 'apikey': apikey}))) + def add_rate_limit_rule(self, description, enabled, matchregex, matchstring, requestspersecond, groupby, apikey=''): + """ + Adds a rate limit rule + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/action/addRateLimitRule/', {'description': description, 'enabled': enabled, 'matchRegex': matchregex, 'matchString': matchstring, 'requestsPerSecond': requestspersecond, 'groupBy': groupby, 'apikey': apikey}))) + + def remove_rate_limit_rule(self, description, apikey=''): + """ + Remove a rate limit rule + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/action/removeRateLimitRule/', {'description': description, 'apikey': apikey}))) + + def set_rate_limit_rule_enabled(self, description, enabled, apikey=''): + """ + Set enabled state for a rate limit rule. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'network/action/setRateLimitRuleEnabled/', {'description': description, 'enabled': enabled, 'apikey': apikey}))) + def proxy_pac(self, apikey=''): """ Provides a PAC file, proxying through the main proxy. diff --git a/src/zapv2/pscan.py b/src/zapv2/pscan.py index 8ce1ba5..215dd3b 100644 --- a/src/zapv2/pscan.py +++ b/src/zapv2/pscan.py @@ -107,7 +107,7 @@ def disable_scanners(self, ids, apikey=''): def set_scanner_alert_threshold(self, id, alertthreshold, apikey=''): """ - Sets the alert threshold of the passive scanner with the given ID, accepted values for alert threshold: OFF, DEFAULT, LOW, MEDIUM and HIGH + Sets the alert threshold of the passive scan rule with the given ID, accepted values for alert threshold: OFF, DEFAULT, LOW, MEDIUM and HIGH """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'pscan/action/setScannerAlertThreshold/', {'id': id, 'alertThreshold': alertthreshold, 'apikey': apikey}))) diff --git a/src/zapv2/selenium.py b/src/zapv2/selenium.py index 7fb62be..7e2b1c3 100644 --- a/src/zapv2/selenium.py +++ b/src/zapv2/selenium.py @@ -83,7 +83,6 @@ def option_last_directory(self): @property def option_phantom_js_binary_path(self): """ - Returns the current path to PhantomJS binary This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/view/optionPhantomJsBinaryPath/'))) @@ -130,7 +129,6 @@ def set_option_last_directory(self, string, apikey=''): def set_option_phantom_js_binary_path(self, string, apikey=''): """ - Sets the current path to PhantomJS binary This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/action/setOptionPhantomJsBinaryPath/', {'String': string, 'apikey': apikey}))) diff --git a/src/zapv2/spider.py b/src/zapv2/spider.py index 91ab6bd..37c7100 100644 --- a/src/zapv2/spider.py +++ b/src/zapv2/spider.py @@ -216,6 +216,13 @@ def option_parse_comments(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionParseComments/'))) + @property + def option_parse_ds_store(self): + """ + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionParseDsStore/'))) + @property def option_parse_git(self): """ @@ -511,6 +518,12 @@ def set_option_parse_comments(self, boolean, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/action/setOptionParseComments/', {'Boolean': boolean, 'apikey': apikey}))) + def set_option_parse_ds_store(self, boolean, apikey=''): + """ + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/action/setOptionParseDsStore/', {'Boolean': boolean, 'apikey': apikey}))) + def set_option_parse_git(self, boolean, apikey=''): """