diff --git a/Authentication_ZAP_GT_CRIVO/.gitignore b/Authentication_ZAP_GT_CRIVO/.gitignore new file mode 100644 index 0000000..195d58c --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/.gitignore @@ -0,0 +1,170 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# arquivos +user_data_test.py +tests/ +notas.txt +contexto.py +test.py +Command_container.txt \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/Makefile b/Authentication_ZAP_GT_CRIVO/Makefile new file mode 100644 index 0000000..14970a0 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/Makefile @@ -0,0 +1,21 @@ +generate_key: + @echo "Generating API key..." + @python3 -c 'import random; import string; key = "".join(random.choices(string.ascii_lowercase + string.digits, k=26)); print(key)' > apikey.txt + +update_env: + @key=$$(cat apikey.txt); \ + echo "Key to be used: $$key"; \ + if grep -q '^ZAP_API_KEY=' file.env; then \ + sed -i "s/^ZAP_API_KEY=.*/ZAP_API_KEY=$$key/" file.env; \ + else \ + echo "ZAP_API_KEY=$$key" >> file.env; \ + fi + @echo "file.env updated successfully!" + rm apikey.txt + + +run: generate_key update_env + @echo "Starting services with new API key..." + docker compose --env-file file.env up --build --force-recreate -d + ./copy.sh $(DIR) + rm file.env diff --git a/Authentication_ZAP_GT_CRIVO/README.md b/Authentication_ZAP_GT_CRIVO/README.md new file mode 100644 index 0000000..5154077 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/README.md @@ -0,0 +1,182 @@ +# Authentication_ZAP_GT_CRIVO + +This framework was designed to automate the process of security testing in web applications, utilizing authentication to expand the attack surface. It allows the user to test multiple applications without the need to interact directly with the application's content. The framework automates the process of metadata capture, extraction of key elements, and authentication within the applications. After authentication, the framework processes the metadata and generates a web application configuration file that can later be used for security testing. + +## ZAP + +The Zed Attack Proxy (ZAP) is a tool used for security testing in web applications. The framework uses a Docker image of ZAP, which includes a proxy and an API running on port 8080. The main idea is to connect the framework to the ZAP proxy at the moment a web application is instantiated. This way, ZAP analyzes the application using both passive and active scans during security tests. + +The framework interacts with ZAP by sending commands and receiving data through the API. The necessary data to create the application's context file is extracted from the alerts generated by ZAP, which indicate potential vulnerabilities. These alerts are configured to require evidence when triggered, which can be provided by the user or the tool's developers. In this version, the framework uses the alerts defined by ZAP's own developers. + +### Context + +A context is metadata of an application that can be used to perform security tests. This metadata may include information such as user credentials to be used for authentication, details about the application's session management, data on the type of authentication requests the application supports, URLs associated with the application to be tested, URLs to be excluded from testing, and technologies to be covered during the tests. + +A context can be named and saved in a file format, which can later be instantiated for future vulnerability tests on the same application. + +### Automate plan + +An automation plan is a YAML file that uses the context metadata to perform actions beyond just creating the mentioned file. In addition to generating the context file, the plan can also define jobs to create an automated execution pipeline, eliminating the need for the user to manually import the file into the tool and select the desired type of test. This plan enables the inclusion of various types of vulnerability test executions in ZAP, covering all preliminary tests on a web application in an automated manner. + +The developed framework generates the automation plan with some predefined jobs so that, by using this automation file, the user can either generate the file based on the context or perform a full scan. + +## Framework + +The framework connects to the ZAP API to perform tests and capture metadata. Initially, it checks for the existence of configuration files in the designated volume directory. Once identified, the execution flow begins. Each configuration file is parsed, and its fields are interpreted using the Pydantic library. If a file does not contain the essential information defined in the configuration class, it will be disregarded. + +The code is organized into modules, and the process for each of them will be explained below. + +### Authentication + +The `authentication.py` module contains functions to locate and validate elements on a web page based on specific attributes. Additionally, it verifies the presence of credentials in the request body of an authentication request. + +The `find_element_by_attribute` function is used to locate an element on a web page based on a specified attribute. It accepts three arguments: `driver`, which is the instance of Selenium WebDriver; `attribute`, the attribute to search for (e.g., "name," "type," "placeholder"); and `value`, the value of the attribute to look for. The function attempts to find the corresponding element and returns the found element (`WebElement`) if it exists, or `None` if the element is not found. If the specified attribute is invalid, the function raises a `ValueError`. + +The `validate_by_attribute` function validates whether an element with the specified attribute and value is visible on the page. The function attempts to find the corresponding element and checks if it is visible on the page. If the element is found and visible, the function logs a failure message for login, refreshes the page to clear partially found credentials, and returns `False`. If the element is not found, the function logs a success message for login and returns `True`. + +The `check_credentials` function receives a request string and two credentials (login and password) and verifies if both credentials are present in the request body. If the login and password credentials are identical, the function checks if the credential appears at least twice in the string, indicating that the login and password are the same. Otherwise, the function checks if both credentials are present in the string. The function returns `True` if the credentials are found as expected, or `False` otherwise. + +### Context + +The `replace_words` function is responsible for replacing login and password keywords with the default credentials accepted by ZAP. It takes as arguments the text to be modified, the login and password to be replaced, and the default credentials (`credential_login` and `credential_password`). The function handles a special case where `%40` is replaced by `@`, as observed in some tests where requests using `@` were returned with it replaced by `%40`. If the login and password are identical, the function replaces the first occurrence of both with the default credentials. Otherwise, if `%40` is present in the text, the login is modified by substituting `@` with `%40` before making the replacement. The function returns the modified text, which is the standard input for ZAP. + +The `build_yaml` function takes context information as parameters and constructs the YAML file responsible for the application's automation plan. Parameters include the context, alert count, request body, login and password credentials, the name of the file to be generated with the context, the base URL, and the base login URL. If the alert count is greater than zero, the function sets the application's session management to auto-detection for context creation. Afterward, the `replace_words` function is called to replace the login and password keywords in the request body with the default credentials. The modified request body is then used to update the YAML context. + +The `update_jobs` function is used to update the automation plan file with jobs that can be manually specified by the user, such as Spider, Ajax Spider, Full Scan, and other functionalities provided by ZAP. + +### Keywords +#### elements + + +The `elements.py` module contains functions for identifying password fields in web page elements and for filtering page elements based on evidence of authentication forms. + +The `find_password` function is used to check whether an element in a form contains a password field. It takes an element as an argument and uses a regular expression to determine if any value of the element matches a password pattern. The function returns True if a password field is found; otherwise, it returns False. + +The `filtered_dict` function takes a page instance, a page potentially containing an authentication form, and an empty structure to be populated. It extracts all elements from the page and uses regular expressions to verify if the elements are authentication fields. The function filters these page elements and returns a dictionary with the filtered elements. + +The function operates in a loop, which means that multiple non-candidate pages may exist in the queue. Because of this, the page does not use the ZAP proxy. Consequently, the driver is closed as soon as the elements are extracted. If no elements are found initially, the function attempts to find elements again after sending the ESCAPE key to handle potential pop-ups. The elements found are then added to a list, array_elements, which is populated with information about the elements' attributes, such as name, type, placeholder, and id. + +#### keywords + +The `keywords.py` module contains the `RegexSets` class, which is responsible for loading regular expression files that will be used to identify elements on a page. The `RegexSets` class includes methods to load and read these files, as well as to store the loaded regular expression sets. + +The `RegexSets` class is initialized with the path to the folder where the regex files are located. The `__init__` method sets the folder path and calls the `load_files` method to load the regex files. The `load_files` method retrieves all `.txt` files in the specified folder, sorts them, and reads them using the `read_files` method. For each file, the key is set as the file name (excluding the extension), and the file's content is stored in the `keywords` dictionary. The loaded regex sets are then assigned to specific class attributes: `invalid_values_urls`, `type_elements`, `valid_values_elements`, and `url_words`. + +The `read_files` method is responsible for reading the content of a text file. It takes the file name as an argument, opens the file, and reads each line, removing any extra whitespace. The method returns a list of lines read from the file. + +In addition to the `RegexSets` class, the module defines some global variables: +- **`password_field_identifiers`**: A tuple containing identifiers for password fields. +- **`parameters_types`**: A tuple containing parameter types (`form` and `json`). +- **`parameter_types_found`**: A dictionary that tracks the count of parameter types found (`form`, `json`, and `script`). + + +### Params + +The `define_authentication_type` function is responsible for determining the authentication type based on the application's request body. It takes as arguments an instance of ZAP, the `requestbody`, and the login and password credentials. + +The function checks if the request body contains the login and password credentials and, based on this, sets the authentication type to either `form` or `json`. If the credentials are present in the request body, the function sets the authentication type to `form`. Otherwise, it sets it to `json`. + +### Urls_Login + +The `find_urls_login` function takes two lists as parameters: `list_of_strings` and `list_of_words`. It iterates over each string in the `list_of_strings`, and for each string, it iterates over each word in the `list_of_words`. If a word is found within the string, the string is added to a results list called `found_urls`, and the inner iteration is terminated. + +At the end, the function returns the `found_urls` list, which contains all the strings that include at least one of the words from the `list_of_words`. + +### User_data + +The `user_data` module contains the class responsible for parsing the JSON provided by the user to perform authentication and application identification during testing with ZAP. + +### **Configuration in `user_data.py`** + +The `Configuration` class is a subclass of Pydantic's `BaseModel`, used to parse a JSON file that the user must provide with application data to enable automation. The class includes the following attributes: + +- **`context`**: A string representing the title of the configuration, which will be imported as the testing context for the application. +- **`url`**: A list of strings containing the URLs related to the configuration. +- **`url_login`**: An optional string representing the login URL. If the user provides this login URL, the automation will use it. Otherwise, the module will perform a heuristic search to find the URL with the authentication form. +- **`exclude_urls`**: A list of strings containing URLs to be excluded. The default value is an empty list. +- **`report_title`**: An optional string representing the title of the ZAP test report. The default value is `"Report"`. +- **`login`**: A string representing the user's login. +- **`password`**: A string representing the user's password. + +This class structures the necessary information for conducting tests through ZAP. + +## configuration file + +The configuration file must be in JSON format. Below is an real example: + +```json + context: "context_bWAPP" + url: "192.168.15.3/bWAPP/" + url_login: "192.168.15.3/bWAPP/login" + exclude_urls: [] + report_title: "Report_bWAPP" + login: "admin" + password: "admin" +``` + +Pydantic will parse the file and extract the necessary attributes for creating the context. An example will be provided in the root of the repository, named `example.json`. + +## Main + +The `main.py` file is responsible for orchestrating the entire process of security test automation in web applications. It performs various steps, from reading and validating the user's configuration to generating a YAML context file for ZAP (Zed Attack Proxy). The `main` function has several responsibilities. + +The main module reads the JSON configuration file provided by the user and validates the JSON using the `Configuration` class from the `user_data` module. After that, it sets up variables and initializes ZAP by loading regular expressions using the `RegexSets` class from the `keywords` module, setting the required environment variables such as `FIREFOX` and `ZAP_API_KEY`, and starting a new ZAP session using the `zapv2` library. + +Selenium is also configured in the main module, setting up Firefox to use ZAP's proxy and ignoring certificate errors. A Firefox profile is defined to use ZAP's proxy. + +The module checks whether a login URL was provided by the user. If the URL is not provided, the module performs a scan with ZAP's spider to find potential login URLs using the `spider_scan` function. When a URL with an authentication form is found, the module instantiates the `filtered_dict` function from the `elements` module to extract and filter authentication elements from the login page. After creating the structure to perform the login, the main module submits the login credentials to the identified authentication fields and validates whether the login was successful using the `validate_by_attribute` function from the `autentication` module. + +After authentication is completed by instantiating the WebDriver with ZAP's proxy, the module captures alerts generated by ZAP during the authentication process, verifies whether the credentials were sent correctly using the `check_credentials` function from the `autentication` module, defines the type of authentication based on the request body using the `Define_authentication_type` function from the `params` module, constructs the YAML context file using the `build_yaml` function from the `context` module, and updates the jobs in the YAML file using the `update_jobs` function from the `context` module. Finally, the configuration is saved to a YAML file that is generated in the shared directory. + +## Docker Compose + +The `docker-compose` was configured to allow the two applications to interact with each other. A *bridge* mode subnet was created, and dependencies between the containers were defined to ensure the correct execution order. First, the ZAP container is started, followed by the framework container. + +## Makefile + +The `Makefile` performs important operations for the proper functioning of the framework: + +1. Generates the API key to be used by ZAP, setting it as an environment variable (the key is randomly generated with each execution). +2. Executes the `copy.sh` script, which copies the configuration files from the specified directory to the framework's volume. + +### copy.sh + + +This script takes the configuration files' directory as a parameter and copies them to the volume, avoiding duplications with each execution. Only `.json` files are copied, and if any file fails the Pydantic parser, it will be ignored. + +## Input + +Before starting the framework, it is necessary to create a configuration file for the application to be tested, as shown above. After creating one or more configuration files, store them in a single directory, whose path will be used as a parameter for the `Makefile`. + +## Output + +The framework generates a YAML file with the plan for passive scans and the application's context. + +## How to use + +To start the framework, use the command: + +```bash +make run DIR="directory of the files" +``` + +The `docker-compose` will bring up two containers: ZAP and the framework. The containers run in the *background*. To view the logs, use the commands: + +```bash +docker logs -f authentication_zap_gt_crivo-zaproxy-1 +docker logs -f authentication_zap_gt_crivo-framework-1 +``` + +The framework application can be managed using the command: + +```bash +docker exec -it authentication_zap_gt_crivo-framework-1 /bin/bash +``` + +All information related to the configuration files or the automation plans that are generated is located in the `shared_data` folder at the root of the application's compose. + +To stop the application, use: + +```bash +docker compose down +``` diff --git a/Authentication_ZAP_GT_CRIVO/copy.sh b/Authentication_ZAP_GT_CRIVO/copy.sh new file mode 100755 index 0000000..5ca9646 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/copy.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -eu + +# Check if only 1 argument was passed +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Argument for the directory to be copied +SOURCE=$1 + +DEST_DIR="input_config" +# volume in compose +DESTINATION="/shared_data/$DEST_DIR" + +# Check if the source directory exists +if [ ! -d "$SOURCE" ]; then + echo "Error: Source directory '$SOURCE' does not exist." + exit 1 +fi + +# Check if the destination directory exists, if not, create it +docker compose exec framework bash -c "mkdir -p $DESTINATION && rm -rf $DESTINATION/*" + +# command that deletes all configuration files if any before copying the new ones. + +# Copy only the .json content from the source directory to the volume +for file in "$SOURCE"/*.json; do + if [ -f "$file" ]; then + docker compose cp "$file" framework:"$DESTINATION/$(basename "$file")" + fi +done + +echo "Content copied from $SOURCE to $DESTINATION successfully." diff --git a/Authentication_ZAP_GT_CRIVO/docker-compose.yml b/Authentication_ZAP_GT_CRIVO/docker-compose.yml new file mode 100644 index 0000000..1fa9bc1 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/docker-compose.yml @@ -0,0 +1,34 @@ +services: + zaproxy: + image: zaproxy/zap-weekly + command: ["zap.sh", "-config", "api.key=${ZAP_API_KEY}", "-daemon", "-host", "0.0.0.0", "-port", "8080", "-config", "api.addrs.addr.name=.*", "-config", "api.addrs.addr.regex=true"] + ports: + - "8080:8080" + networks: + - zapnet + environment: + - ZAP_API_KEY=${ZAP_API_KEY} + volumes: + - shared_data:/shared_data + + framework: + build: ./framework + environment: + - FIREFOX=/usr/local/bin/geckodriver + - ZAP_API_KEY=${ZAP_API_KEY} + - ZAP_PROXY_ADDRESS=zaproxy + - ZAP_PROXY_PORT=8080 + networks: + - zapnet + depends_on: + - zaproxy + volumes: + - ./framework:/app + - shared_data:/shared_data + +networks: + zapnet: + driver: bridge + +volumes: + shared_data: diff --git a/Authentication_ZAP_GT_CRIVO/example.json b/Authentication_ZAP_GT_CRIVO/example.json new file mode 100644 index 0000000..d8e8bdb --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/example.json @@ -0,0 +1,8 @@ +{ + "context": "", + "url": [], + "url_login": "", + "report_title": "Report", + "login": "", + "password": "" +} \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/Dockerfile b/Authentication_ZAP_GT_CRIVO/framework/Dockerfile new file mode 100644 index 0000000..5027924 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/Dockerfile @@ -0,0 +1,35 @@ +FROM python:3.12.5-slim + +# Install necessary dependencies +RUN apt-get update && \ + apt-get install -y \ + wget \ + unzip \ + libxi6 \ + libgconf-2-4 \ + default-jdk \ + python3-pip \ + firefox-esr \ + curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install geckodriver +RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.29.1/geckodriver-v0.29.1-linux64.tar.gz -O /tmp/geckodriver.tar.gz && \ + tar -xzf /tmp/geckodriver.tar.gz -C /usr/local/bin/ && \ + rm /tmp/geckodriver.tar.gz + +# Install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy your application code to the container +COPY . /app +WORKDIR /app + +# Set environment variables +ENV FIREFOX="/usr/local/bin/geckodriver" +ENV ZAP_PROXY_ADDRESS="zaproxy" + +# Command to start the application after zap started +CMD sleep 30 && python3 main.py diff --git a/Authentication_ZAP_GT_CRIVO/framework/autentication/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/autentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py new file mode 100644 index 0000000..062b866 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/autentication/autentication.py @@ -0,0 +1,70 @@ +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +import logging + +logging.basicConfig(level=logging.INFO, format="%(message)s") + + +def find_element_by_attribute(driver, attribute, value): + """ + + Find an element on the website based on the specified attribute. + + """ + try: + if attribute == "name": + element = driver.find_element(By.NAME, value) + elif attribute == "type": + element = driver.find_element(By.CSS_SELECTOR, f"input[type='{value}']") + elif attribute == "placeholder": + element = driver.find_element( + By.CSS_SELECTOR, f"input[placeholder='{value}']" + ) + else: + raise ValueError(f"Atributo inválido: {attribute}") + logging.info(f"Element found successfully.") + return element + except NoSuchElementException: + logging.info(f"Element not found based on attribute '{attribute}'.") + return None + + +def validate_by_attribute(driver, attribute, value): + """ + + Validate if an element with the given attribute and value is displayed on the page. + + """ + try: + if attribute == "name": + if driver.find_element(By.NAME, value).is_displayed(): + pass + elif attribute == "type": + if driver.find_element( + By.CSS_SELECTOR, f"input[type='{value}']" + ).is_displayed(): + pass + elif attribute == "placeholder": + if driver.find_element( + By.CSS_SELECTOR, f"input[placeholder='{value}']" + ).is_displayed(): + pass + logging.info("Failed to log in!") + driver.refresh() + return + except: + logging.info("Login successful!") + + +def check_credentials(request, credencial_login, credencial_passsword): + """ + + This function receives a string and two credentials and checks if both credentials are present in the request body string. + The function can check if the credentials are the same by verifying the number of occurrences in the string. + And it checks if both credentials are present in the string. + + """ + if credencial_login == credencial_passsword: + return request.count(credencial_login) >= 2 + else: + return credencial_login in request and credencial_passsword in request diff --git a/Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml b/Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml new file mode 100644 index 0000000..5e5f80f --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/base_context/context_base.yaml @@ -0,0 +1,83 @@ +--- +env: + contexts: + - name: "" + urls: + - "" + includePaths: + - "" + excludePaths: [] + authentication: + method: "" + parameters: + loginPageUrl: "" + loginRequestUrl: "" + loginRequestBody: "" + verification: + method: "response" + pollFrequency: 60 + pollUnits: "requests" + pollUrl: "" + pollPostData: "" + sessionManagement: + method: "" + parameters: {} + technology: + exclude: [] + users: + - name: "" + credentials: + password: "" + username: "" + parameters: + failOnError: true + failOnWarning: false + progressToStdout: true + vars: {} +jobs: +- parameters: + scanOnlyInScope: true + enableTags: false + disableAllRules: false + rules: [] + name: "passiveScan-config" + type: "passiveScan-config" +- parameters: + context: "" + user: "" + url: "" + maxDuration: 0 + maxDepth: 0 + maxChildren: 0 + name: "spider" + type: "spider" + tests: + - onFail: "INFO" + statistic: "automation.spider.urls.added" + site: "" + operator: ">=" + value: 100 + name: "At least 100 URLs found" + type: "stats" +- parameters: + context: "" + user: "" + url: "" + maxDuration: 60 + maxCrawlDepth: 10 + numberOfBrowsers: 8 + inScopeOnly: true + runOnlyIfModern: false + name: "spiderAjax" + type: "spiderAjax" + tests: + - onFail: "INFO" + statistic: "spiderAjax.urls.added" + site: "" + operator: ">=" + value: 100 + name: "At least 100 URLs found" + type: "stats" +- parameters: {} + name: "passiveScan-wait" + type: "passiveScan-wait" \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/context/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/context/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/context/context.py b/Authentication_ZAP_GT_CRIVO/framework/context/context.py new file mode 100644 index 0000000..588bfcf --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/context/context.py @@ -0,0 +1,116 @@ +import logging + +from ruamel.yaml.comments import CommentedMap +from keywords import keywords + + +def replace_words( + text, + login, + password, + credential_login="{%username%}", + credential_password="{%password%}", +): + """ + + This function is responsible for replacing the login and password keywords with the default credentials accepted by ZAP. + The replace_words function has a case where it treats %40 as @, which was highlighted in some tests where the request with @ was returned by replacing it with the characters %40. + + """ + if login == password: + new_request = text.replace(login, credential_login, 1).replace( + password, credential_password, 1 + ) + else: + if "%40" in text: + login = login.replace("@", "%40") + new_request = text.replace(login, credential_login, 1).replace( + password, credential_password, 1 + ) + return new_request + + +def build_yaml( + context, + alert_count, + request_body, + credential_login, + credential_password, + context_name, + base_url, + base_url_login, +): + """ + + This function receives the context information as a parameter and constructs the YAML file responsible for the application's automation plan. + + """ + if alert_count > 0: + # marcar contexto como auto detect. + context["env"]["contexts"][0]["sessionManagement"]["method"] = "autodetect" + logging.info("Session management set to auto-detection") + + request_text = replace_words(request_body, credential_login, credential_password) + + context["env"]["contexts"][0]["authentication"]["parameters"][ + "loginRequestBody" + ] = request_text + + # Authentication validation + context["env"]["contexts"][0]["authentication"]["verification"][ + "method" + ] = "autodetect" + + # User crendetials + context["env"]["contexts"][0]["users"][0]["name"] = credential_login + context["env"]["contexts"][0]["users"][0]["credentials"][ + "password" + ] = credential_password + context["env"]["contexts"][0]["users"][0]["credentials"][ + "username" + ] = credential_login + + context["env"]["contexts"][0]["name"] = context_name + context["env"]["contexts"][0]["urls"] = base_url + context["env"]["contexts"][0]["includePaths"] = [] + # Authentication + context["env"]["contexts"][0]["authentication"]["parameters"][ + "loginPageUrl" + ] = base_url_login + context["env"]["contexts"][0]["authentication"]["parameters"][ + "loginRequestUrl" + ] = base_url_login + + verification_context = context["env"]["contexts"][0]["authentication"][ + "verification" + ] + new_verification_entries = CommentedMap() + new_verification_entries["method"] = "autodetect" + + if keywords.parameter_types_found["form"]: + context["env"]["contexts"][0]["authentication"]["method"] = "form" + + if keywords.parameter_types_found["json"]: + + new_verification_entries["method"] = "json" + + # Atualizar os dados de verificação com a nova ordem + context["env"]["contexts"][0]["authentication"][ + "verification" + ] = new_verification_entries + + +def update_jobs(jobs, new_context, new_user, new_url): + """ + + Instantiates jobs defined by default in the application's automation plan file. + + """ + for job in jobs: + if "parameters" in job: + if "context" in job["parameters"]: + job["parameters"]["context"] = new_context + if "user" in job["parameters"]: + job["parameters"]["user"] = new_user + if "url" in job["parameters"]: + job["parameters"]["url"] = new_url diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py new file mode 100644 index 0000000..dab1c45 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/elements.py @@ -0,0 +1,105 @@ +import logging +import re +import time + +from selenium import webdriver +from selenium.common.exceptions import NoSuchAttributeException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +from keywords.keywords import RegexSets + + +logging.basicConfig(level=logging.INFO, format="%(message)s") + +DEFAULT_TIME = 1 +regex_sets = RegexSets() +password_regex = regex_sets.valid_values_elements[0] +valid_values_elements = regex_sets.valid_values_elements + + +def find_password(element): + return any( + re.search(password_regex, str(value), re.IGNORECASE) + for _, value in element.items() + ) + + +def filtered_dict(driver, evidence, struct_login): + """ + + The function will receive an instance of a page and an empty structure that will be filled. + For each "evidence" (page with a potential authentication form), the function will extract all elements from the page and use regex to verify if the elements are indeed authentication fields. + The function will filter the elements from the page and return a dictionary with the filtered elements. + This function runs in a loop, meaning there can be multiple pages that are not candidates in the queue. Therefore, the page does not use the ZAP proxy. Because of this, the driver is closed as soon as the elements are extracted. + + """ + + driver.get(evidence) + time.sleep(DEFAULT_TIME) + elements = driver.find_elements(By.XPATH, "//*") + + if not elements: + elements_popup = driver.find_elements(By.XPATH, "//*") + webdriver.ActionChains(driver).send_keys(Keys.ESCAPE).perform() + elements = driver.find_elements(By.XPATH, "//*") + elements.extend(elements_popup) + + array_elements = [] + + for element in elements: + element_info = {} + try: + attributes_to_gather = [ + ("name", element.get_attribute("name")), + ("type", element.get_attribute("type")), + ("placeholder", element.get_attribute("placeholder")), + ("id", element.get_attribute("id")), + ] + element_info = {key: value for key, value in attributes_to_gather if value} + except NoSuchAttributeException: + pass + + if element_info: + array_elements.append(element_info) + logging.info(array_elements) + + filtered_dictionaries = [] + for einfo in array_elements: + for _, value in einfo.items(): + for regex in valid_values_elements: + if re.search(regex, value): + if einfo not in filtered_dictionaries: + filtered_dictionaries.append(einfo) + break + + logging.info(filtered_dictionaries) + + if filtered_dictionaries: + + if len(filtered_dictionaries) > 2: + login = [] + for index, element in enumerate(filtered_dictionaries): + if find_password(element): + if filtered_dictionaries[index - 1] in login: + pass + else: + login.append(filtered_dictionaries[index - 1]) + if element in login: + pass + else: + login.append(element) + filtered_dictionaries = login + + authentication_data = { + "name": tuple(element.get("name", "") for element in filtered_dictionaries), + "type": tuple(element.get("type", "") for element in filtered_dictionaries), + "placeholder": tuple( + element.get("placeholder", "") for element in filtered_dictionaries + ), + } + + dicionario_autenticado = {evidence: authentication_data} + struct_login.append(dicionario_autenticado) + + driver.quit() diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py new file mode 100644 index 0000000..c3b90d5 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/keywords.py @@ -0,0 +1,39 @@ +import os + + +class RegexSets: + """ + + The class is responsible for loading the regex files that will be used to identify elements on the page. + + """ + + def __init__(self): + self.folder_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "regex_words/" + ) + self.keywords = {} + self.load_files() + + def load_files(self): + # Obtém todos os arquivos .txt na pasta + files = [f for f in os.listdir(self.folder_path) if f.endswith(".txt")] + files = sorted(files) + + for file in files: + key = file.split(".")[0] + self.keywords[key] = self.read_files(file) + + self.invalid_values_urls = self.keywords.get("invalid_urls", []) + self.type_elements = self.keywords.get("type_elements", []) + self.valid_values_elements = self.keywords.get("valid_elements", []) + self.url_words = self.keywords.get("valid_urls", []) + + def read_files(self, file): + with open(f"{self.folder_path}{file}", "r") as f: + return [line.strip() for line in f] + + +password_field_identifiers = ("password", "senha", "txtPassword") +parameters_types = ("form", "json") +parameter_types_found = {"form": 0, "json": 0, "script": 0} diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt new file mode 100644 index 0000000..9b685a6 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/invalid_urls.txt @@ -0,0 +1,4 @@ +\b[\w.-]*css[\w.-]*\b +\b[\w.-]*png[\w.-]*\b +\b[\w.-]*pdf[\w.-]*\b +\b[\w.-]*jpg[\w.-]*\b diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt new file mode 100644 index 0000000..7117601 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/type_elements.txt @@ -0,0 +1,3 @@ +name +type +placeholder \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt new file mode 100644 index 0000000..5ed9aa8 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_elements.txt @@ -0,0 +1,8 @@ +(?i)\b[\w.-]*password[\w.-]*\b +(?i)\b[\w.-]*senha[\w.-]*\b +(?i)\b[\w.-]*user[\w.-]*\b +(?i)\b[\w.-]*login[\w.-]*\b +(?i)\b[\w.-]*email[\w.-]*\b +(?i)\b[\w.-]*idLoginForm[\w.-]*\b +(?i)\b[\w.-]*uid[\w.-]*\b +(?i)\b[\w.-]*name[\w.-]*\b \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt new file mode 100644 index 0000000..e9f12b6 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/keywords/regex_words/valid_urls.txt @@ -0,0 +1 @@ +login \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/main.py b/Authentication_ZAP_GT_CRIVO/framework/main.py new file mode 100644 index 0000000..49a99e7 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/main.py @@ -0,0 +1,273 @@ +import os +import logging +import re +import time +from pathlib import Path + +from ruamel.yaml import YAML +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.keys import Keys +from zapv2 import ZAPv2 + +from autentication import autentication +from context.context import build_yaml, update_jobs +from keywords.elements import filtered_dict +from keywords.keywords import RegexSets, parameter_types_found +from params import params +from urls_login import urls_login +from user_data.user_data import Configuration + +""" +The default time is used to wit while selenium or zap is working, this delay is a reng sefety to wait a frameowrk to finish the work. +""" +DEFAULT_TIME = 3 + +logging.basicConfig(level=logging.INFO, format='%(message)s') + + +def main(file_path): + + file_path = Path(file_path) + + json_data = file_path.read_text() + + user = Configuration.model_validate_json(json_data) + + logging.info("User configuration loaded successfully.") + + regex_sets = RegexSets() + + invalid_values_urls = regex_sets.invalid_values_urls + url_words = regex_sets.url_words + + FIREFOX = os.getenv("FIREFOX") + ZAP_API_KEY = os.getenv("ZAP_API_KEY") + if FIREFOX is None or ZAP_API_KEY is None: + raise EnvironmentError("FIREFOX e ZAP_API_KEY devem estar definidos nas variáveis de ambiente.") + + ZAP_PROXY_ADDRESS = os.getenv("ZAP_PROXY_ADDRESS") + ZAP_PROXY_PORT = int(os.getenv("ZAP_PROXY_PORT")) + + zap = ZAPv2( + apikey=ZAP_API_KEY, + proxies={ + "http": f"http://{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}", + "https": f"http://{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}", + }, + ) + proxy_server_url = f"{ZAP_PROXY_ADDRESS}:{ZAP_PROXY_PORT}" + firefox_options = webdriver.FirefoxOptions() + firefox_options.add_argument("--ignore-certificate-errors") + firefox_options.add_argument(f"--proxy-server={proxy_server_url}") + firefox_options.add_argument("--headless") + + s = Service(FIREFOX) + firefox_profile = webdriver.FirefoxProfile() + firefox_profile.set_preference("network.proxy.type", 1) + firefox_profile.set_preference("network.proxy.http", ZAP_PROXY_ADDRESS) + firefox_profile.set_preference("network.proxy.http_port", ZAP_PROXY_PORT) + firefox_profile.set_preference("network.proxy.ssl", ZAP_PROXY_ADDRESS) + firefox_profile.set_preference("network.proxy.ssl_port", ZAP_PROXY_PORT) + firefox_profile.update_preferences() + + firefox_options.profile = firefox_profile + + if user.url_login == "": + FLAG_LOGIN = 0 + logging.info("url de login não foi setada") + else: + FLAG_LOGIN = 1 + logging.info("url de login setada") + + zap.core.new_session(overwrite=True) + + struct_login = [] + + driver = webdriver.Firefox(service=s, options=firefox_options) + if FLAG_LOGIN: + filtered_dict(driver, user.url_login, struct_login) + time.sleep(DEFAULT_TIME) + + if FLAG_LOGIN == 0: + struct_login = spider_scan(driver, zap, user, urls_login, url_words, invalid_values_urls, s, firefox_options, struct_login) + + logging.info("Struct login: %s", struct_login) + + zap.core.new_session(overwrite=True) + + driver = webdriver.Firefox(service=s, options=firefox_options) + if len(struct_login) == 1: + url_authentication = list(struct_login[0].keys())[0] + driver.get(url_authentication) + else: + raise ValueError("Failed to create the list of URLs: struct_login does not contain exactly one element.") + + time.sleep(DEFAULT_TIME) + + for authentication_dictionary in struct_login: + for _, elements in authentication_dictionary.items(): + for element_type, field in elements.items(): + login_element, password_element = field + logging.info(login_element, password_element) + username_field = autentication.find_element_by_attribute( + driver, element_type, login_element + ) + if username_field is None: + continue # Se não encontrou o campo de username, pula para o próximo elemento + password_field = autentication.find_element_by_attribute( + driver, element_type, password_element + ) + if password_field is None: + continue # Se não encontrou o campo de password, pula para o próximo elemento + + username_field.send_keys(user.login) + password_field.send_keys(user.password) + + time.sleep(DEFAULT_TIME) + + try: + password_field.send_keys(Keys.RETURN) + + time.sleep(DEFAULT_TIME) + autentication.validate_by_attribute(driver, element_type, login_element) + except Exception as e: + raise RuntimeError(f"Failed to submit the login form: {e}") + + time.sleep(DEFAULT_TIME) + # there exists a list of element types to test, if we find the first one, we can break the loop and continue. + break + + driver.quit() + + # Pegar todos os alertas da aplicação + alerts = zap.alert.alerts() + logging.info(f"Number of Alerts: {len(alerts)}") + # se não retornar alerta, lançar assert informando a possibilidade do scan passivo ter parado de funcionar + # Alertas no momento que o post foi passado, isso inclui as aplicações que foram passadas de forma errada + # Lembrar que pegar o ultimo ainda é o mais sensato (evitar casos que manda a credencial que queremos, mas de uma forma errada) tem que testar. + messageId = [alert["messageId"] for alert in alerts if alert["name"] == "Authentication Request Identified"] + + # verifica todos os alertas candidatos e filtra o que foi passado as credenciais de forma correta. + for message in messageId: + request_autenticated = zap.core.message(message) + if autentication.check_credentials( + request_autenticated["requestBody"], + user.login, + user.password, + ): + logging.info( + f"The application's response during login: {request_autenticated["requestBody"]}" + ) + break + + request_body = request_autenticated["requestBody"] + + # Verifica que o zap encontrou o gerenciamento de sessão + alert_count = len( + [ + alert + for alert in alerts + if alert["name"] == "Session Management Response Identified" + ] + ) + + BASE_CONTEXT = "base_context/context_base.yaml" + + params.define_authentication_type( + zap, parameter_types_found, url_authentication, request_body + ) + + yaml = YAML() + + with open(BASE_CONTEXT, "r") as file: + context = yaml.load(file) + + build_yaml( + context, + alert_count, + request_body, + user.login, + user.password, + user.context, + user.url, + url_authentication, + ) + + new_context = user.context + new_user = user.login + new_url = url_authentication + + # Chama a função para atualizar os valores + update_jobs(context["jobs"], new_context, new_user, new_url) + + OUTPUT = f"context_{user.context}" + + # Salvar em um novo arquivo YAML + with open(f"../shared_data/{OUTPUT}.yaml", "w") as file: + yaml.dump(context, file) + + logging.info("YAML file saved successfully!") + + output_yaml = f"../shared_data/{OUTPUT}.yaml" + logging.info(f" Yaml path: ",output_yaml) + + + +def wait_for_directory(directory_path): + directory_path = Path(directory_path) + + if directory_path.exists(): + if any(directory_path.iterdir()): + logging.info(f"Files found in directory: {directory_path}") + return + else: + logging.info(f"Empty Directory: {directory_path}") + return + + +def spider_scan(driver, zap, user, urls_login, url_words, invalid_values_urls, s, firefox_options, struct_login): + # Primeira url será utilizada para realizar o crawler na página com o spider + driver.get(user.url[0]) + + time.sleep(DEFAULT_TIME) + scanid = zap.spider.scan(user.url[0]) + + while int(zap.spider.status(scanid)) < 100: + # Loop until the spider has finished + logging.info("Spider progress %: {}".format(zap.spider.status(scanid))) + time.sleep(DEFAULT_TIME) + + logging.info("Spider completed") + + driver.quit() + + urls_found = zap.core.urls() + + logging.info(urls_found) + general_results = urls_login.find_urls_login(urls_found, url_words) + logging.info(general_results) + evidences_urls_login = [] + for url in general_results: + if not any( + re.search(pattern, url) for pattern in invalid_values_urls + ): # match das palavras do array (regex group) + evidences_urls_login.append(url) + + logging.info(evidences_urls_login) + for evidence in evidences_urls_login: + driver = webdriver.Firefox(service=s, options=firefox_options) + filtered_dict(driver, evidence, struct_login) + + +if __name__ == "__main__": + DIRECTORY_PATH = "/shared_data/input_config" + wait_for_directory(DIRECTORY_PATH) + arq_config = os.listdir(DIRECTORY_PATH) + for arq in arq_config: + arq = os.path.join(DIRECTORY_PATH, arq) + try: + main(arq) + except Exception as e: + logging.error(f"An error occurred while processing the file {arq}: {e}") + raise RuntimeError(f"Failed to process the file {arq}") from e \ No newline at end of file diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/params/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/params/params.py b/Authentication_ZAP_GT_CRIVO/framework/params/params.py new file mode 100644 index 0000000..0d3f140 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/params/params.py @@ -0,0 +1,32 @@ +import re +from keywords import keywords + + +def define_authentication_type( + zap, parameter_types_found, BASE_URL_LOGIN, request_body +): + """ + + Function to define the authentication type based on the request body of the application: json, form, etc. + + """ + session_parameters = zap.params.params(BASE_URL_LOGIN) + session_parameters = session_parameters[0]["Parameter"] + + for params in session_parameters: + name = params.get("name") + param_type = params.get("type") + + if ( + name in keywords.password_field_identifiers + and param_type in keywords.parameters_types + ): + parameter_types_found[param_type] = 1 + break + + all_zero = all(value == 0 for value in parameter_types_found.values()) + + if all_zero: + r = r"{\s*[^{}]*\s*}" + if re.search(r, request_body): + parameter_types_found["json"] = 1 diff --git a/Authentication_ZAP_GT_CRIVO/framework/requirements.txt b/Authentication_ZAP_GT_CRIVO/framework/requirements.txt new file mode 100644 index 0000000..a74c2c7 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/requirements.txt @@ -0,0 +1,23 @@ +attrs==23.2.0 +beautifulsoup4==4.12.3 +bs4==0.0.2 +exceptiongroup==1.2.1 +h11==0.14.0 +outcome==1.3.0.post0 +pydantic==2.8.2 +pydantic_core==2.20.1 +PySocks==1.7.1 +PyYAML==6.0.1 +requests==2.32.3 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.8 +selenium==4.22.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +soupsieve==2.5 +trio==0.26.0 +trio-websocket==0.11.1 +typing_extensions==4.12.2 +websocket-client==1.8.0 +wsproto==1.2.0 +zaproxy==0.3.2 diff --git a/Authentication_ZAP_GT_CRIVO/framework/urls_login/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/urls_login/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py new file mode 100644 index 0000000..5f98c89 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/urls_login/urls_login.py @@ -0,0 +1,18 @@ +def find_urls_login(list_of_strings, lista_of_words): + """ + + List to store the strings that contain any of the words + Iterate over each string in the list of strings + Iterate over each word in the list of words + Check if the word is present in the string + Add the string to the list of results and exit the inner loop + + """ + found_urls = [] + + for string in list_of_strings: + for word in lista_of_words: + if word in string: + found_urls.append(string) + break + return found_urls diff --git a/Authentication_ZAP_GT_CRIVO/framework/user_data/__init__.py b/Authentication_ZAP_GT_CRIVO/framework/user_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py new file mode 100644 index 0000000..c16ab21 --- /dev/null +++ b/Authentication_ZAP_GT_CRIVO/framework/user_data/user_data.py @@ -0,0 +1,19 @@ +from pydantic import BaseModel +from typing import List, Optional +from pathlib import Path + + +class Configuration(BaseModel): + """ + + Class used to parse the JSON that the user must provide with the application data to perform the automation. + + """ + + context: str + url: List[str] + url_login: Optional[str] = None + exclude_urls: List[str] = [] + report_title: Optional[str] = "Report" + login: str + password: str