From 8b88e71a87b8743c4a081245d706e241876bbea1 Mon Sep 17 00:00:00 2001 From: Nicolas Gomez Palacios Date: Thu, 7 Nov 2024 18:37:11 -0300 Subject: [PATCH 1/3] test: adds a basic simulator, in python, to handle the requests to the server --- src/agent/test_tool/mock_server/README.md | 80 ++++++++ .../mock_server/group_files/invalidYaml.conf | 3 + .../mock_server/group_files/validYaml.conf | 2 + .../test_tool/mock_server/mock_server.py | 188 ++++++++++++++++++ .../mock_server/responses/agents.json | 1 + .../mock_server/responses/commands.json | 1 + .../responses/events_stateful.json | 1 + .../responses/events_stateless.json | 1 + .../responses/user_authenticate.json | 1 + 9 files changed, 278 insertions(+) create mode 100644 src/agent/test_tool/mock_server/README.md create mode 100644 src/agent/test_tool/mock_server/group_files/invalidYaml.conf create mode 100644 src/agent/test_tool/mock_server/group_files/validYaml.conf create mode 100644 src/agent/test_tool/mock_server/mock_server.py create mode 100644 src/agent/test_tool/mock_server/responses/agents.json create mode 100644 src/agent/test_tool/mock_server/responses/commands.json create mode 100644 src/agent/test_tool/mock_server/responses/events_stateful.json create mode 100644 src/agent/test_tool/mock_server/responses/events_stateless.json create mode 100644 src/agent/test_tool/mock_server/responses/user_authenticate.json diff --git a/src/agent/test_tool/mock_server/README.md b/src/agent/test_tool/mock_server/README.md new file mode 100644 index 0000000000..409366fd26 --- /dev/null +++ b/src/agent/test_tool/mock_server/README.md @@ -0,0 +1,80 @@ +# Mock Server + +This is a configurable mock server in Python that responds to specific endpoints with predefined responses or files. It can operate over HTTP or HTTPS based on provided arguments. + +## Features + +- **JWT Authentication**: The `/api/v1/authentication` endpoint generates and returns a JWT token with a configurable expiration time. +- **File Serving**: The `/api/v1/files` endpoint serves files from a specified directory. +- **Custom Endpoints**: Includes endpoints like `/security/user/authenticate`, `/agents`, `/api/v1/events/stateful`, and others, each returning mock JSON responses from local files. + +## Prerequisites + +- **Python 3.6+** +- **Libraries**: Install the required libraries using the following: + + ```bash + pip install pyjwt + ``` + +## Usage +### Generate SSL Certificates for HTTPS +To run the server with HTTPS, you need SSL certificates. You can create a self-signed certificate using OpenSSL: + +```bash +openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem +``` + +### Running the Server +You can configure and start the server by specifying the desired ports and SSL certificate paths through command-line arguments. + +#### Command-Line Arguments + - `--port1`: Port number for the first server instance (default: 55000) + - `--port2`: Port number for the second server instance (default: 27000) + - `--ssl_key`: Path to the SSL key file for HTTPS (default: key.pem) + - `--ssl_cert`: Path to the SSL certificate file for HTTPS (default: cert.pem) + - `--protocol`: Specify the protocol to use (http or https). Defaults to https. + +#### Examples +Run the server over HTTPS (default): + +```bash +python3 mock_server.py --port1 55000 --port2 27000 +``` + +Run the server over HTTP: + +```bash +python3 mock_server.py --port1 55000 --port2 27000 --protocol http +``` + +## Endpoints + +### POST Endpoints + - `/security/user/authenticate`: Returns a mock authentication response from `responses/user_authenticate.json`. + - `/agents`: Returns a mock response from `responses/agents.json`. + - `/api/v1/authentication`: Returns a generated JWT token with a configurable expiration time. + +### GET Endpoints + - `/api/v1/commands`: Returns a mock response from `responses/commands.json`. + - `/api/v1/files?file_name=`: Serves a specified file from the `group_files` directory. + +## Directory Structure +To serve files and responses, organize your directory structure as follows: + +``` +mock_server/ + responses/ + user_authenticate.json + agents.json + commands.json + ... + group_files/ + file1.txt + file2.conf + cert.pem + key.pem + mock_server.py +``` +## License +This project is open-source. Use it as a reference for your own mock server setup and customization. diff --git a/src/agent/test_tool/mock_server/group_files/invalidYaml.conf b/src/agent/test_tool/mock_server/group_files/invalidYaml.conf new file mode 100644 index 0000000000..4df55d5649 --- /dev/null +++ b/src/agent/test_tool/mock_server/group_files/invalidYaml.conf @@ -0,0 +1,3 @@ +test: + invalid: true + invalid: true diff --git a/src/agent/test_tool/mock_server/group_files/validYaml.conf b/src/agent/test_tool/mock_server/group_files/validYaml.conf new file mode 100644 index 0000000000..483d0f38e6 --- /dev/null +++ b/src/agent/test_tool/mock_server/group_files/validYaml.conf @@ -0,0 +1,2 @@ +test: + valid: true diff --git a/src/agent/test_tool/mock_server/mock_server.py b/src/agent/test_tool/mock_server/mock_server.py new file mode 100644 index 0000000000..4f844438b5 --- /dev/null +++ b/src/agent/test_tool/mock_server/mock_server.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +import json +import os +import ssl +import jwt +import threading +import argparse +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from datetime import datetime, timedelta +from urllib.parse import urlparse, parse_qs, unquote + + +# Configuration of paths +RESPONSES_DIR = 'responses' +GROUPS_FILES_DIR = 'group_files' + +# Secret key to sign the JWT (you can change it for a secure key) +SECRET_KEY = "my_secret_key" + +def generate_authentication_response(expiration_seconds=60): + # Set the expiration time in seconds + expiration_time = datetime.utcnow() + timedelta(seconds=expiration_seconds) + expiration_timestamp = int(expiration_time.timestamp()) + + # Generate the JWT token with the data and the configured expiration time + payload = { + "iss": "wazuh", + "aud": "Wazuh Communications API", + "iat": datetime.utcnow(), + "exp": expiration_time, + "uuid": "edab9ef6-f02d-4a4b-baa4-f2ad12789890" + } + + token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") + + response = { + "token": token, + "exp": expiration_timestamp, + "error": 0 + } + + return json.dumps(response) + +class MockHandler(BaseHTTPRequestHandler): + def _set_headers(self, code, content_type="application/json", content_length=None): + self.send_response(code) + self.send_header("Content-type", content_type) + if content_length: + self.send_header("Content-Length", str(content_length)) + self.send_header("Connection", "close") + self.end_headers() + + def _load_response(self, filename): + try: + with open(os.path.join(RESPONSES_DIR, filename), 'r') as f: + return f.read() + except FileNotFoundError: + return json.dumps({"error": "Response file not found"}) + + def do_POST(self): + response = None + + if self.path == "/security/user/authenticate": + response = self._load_response("user_authenticate.json") + self._set_headers(code=200, content_length=len(response)) + elif self.path == "/agents": + response = self._load_response("agents.json") + self._set_headers(code=200, content_length=len(response)) + elif self.path == "/api/v1/authentication": + response = generate_authentication_response() + self._set_headers(code=200, content_length=len(response)) + elif self.path == "/api/v1/events/stateful": + #response = self._load_response("events_stateful.json") + self._set_headers(code=200) # If an response is given, add the size of the response. + elif self.path == "/api/v1/events/stateless": + #response = self._load_response("events_stateless.json") + self._set_headers(code=200) # If an response is given, add the size of the response. + else: + self.send_error(404, f"Not found: {self.path}") + return + + if response: + self.wfile.write(response.encode('utf-8')) + self.wfile.flush() + self.close_connection = True + + def do_GET(self): + response = None + + if self.path == "/api/v1/commands": + response = self._load_response("commands.json") + self._set_headers(code=200, content_length=len(response)) + elif self.path.startswith("/api/v1/files"): + parsed_url = urlparse(self.path) + query_params = parse_qs(parsed_url.query) + file_name = query_params.get("file_name", [None])[0] + if file_name: + filepath = os.path.join(GROUPS_FILES_DIR, file_name) + filepath = unquote(filepath) + + if os.path.isfile(filepath): + try: + self.send_response(200) + self.send_header('Content-Type', 'application/octet-stream') + self.send_header('Content-Disposition', f'attachment; filename="{os.path.basename(filepath)}"') + self.send_header('Content-Length', str(os.path.getsize(filepath))) + self.send_header('Connection', 'close') + self.end_headers() + + with open(filepath, 'rb') as file: + file_data = file.read() + self.wfile.write(file_data) + + self.wfile.flush() + except Exception as e: + self.send_response(500) + self.end_headers() + self.wfile.write(f'Error reading file: {str(e)}'.encode()) + else: + self._set_headers(code=400) + else: + self.send_error(404, f"Not found: {self.path}") + + if response: + self.wfile.write(response.encode('utf-8')) + self.wfile.flush() + self.close_connection = True + +def run_server(port, ssl_key=None, ssl_cert=None, handler_class=MockHandler, use_https=True): + server_address = ('', port) + + # Use HTTPS if both an SSL certificate and key are provided and the protocol is HTTPS + if use_https and ssl_key and ssl_cert: + httpd = ThreadingHTTPServer(server_address, handler_class) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certfile=ssl_cert, keyfile=ssl_key) + httpd.socket = context.wrap_socket(httpd.socket, server_side=True) + protocol = "HTTPS" + else: + # If not using HTTPS, default to HTTP + httpd = ThreadingHTTPServer(server_address, handler_class) + protocol = "HTTP" + + print(f"Mock {protocol} server running on port {port}") + httpd.serve_forever() + +if __name__ == "__main__": + # Argument parser setup + parser = argparse.ArgumentParser(description="Configurable Mock Server") + + # Define command-line arguments + parser.add_argument( + "--port1", type=int, default=55000, + help="Port number for the first server (default: 55000)" + ) + parser.add_argument( + "--port2", type=int, default=27000, + help="Port number for the second server (default: 27000)" + ) + parser.add_argument( + "--ssl_key", type=str, default="key.pem", + help="Path to the SSL key file (default: key.pem)" + ) + parser.add_argument( + "--ssl_cert", type=str, default="cert.pem", + help="Path to the SSL certificate file (default: cert.pem)" + ) + parser.add_argument( + "--protocol", type=str, choices=["http", "https"], default="https", + help="Specify whether to use HTTP or HTTPS (default: https)" + ) + + # Parse the command-line arguments + args = parser.parse_args() + + use_https = args.protocol == "https" + + # Create threads for each server with the configured parameters + thread1 = threading.Thread(target=run_server, args=(args.port1, args.ssl_key, args.ssl_cert, MockHandler, use_https)) + thread2 = threading.Thread(target=run_server, args=(args.port2, args.ssl_key, args.ssl_cert, MockHandler, use_https)) + + # Start servers + thread1.start() + thread2.start() + + # Wait for both threads to finish + thread1.join() + thread2.join() diff --git a/src/agent/test_tool/mock_server/responses/agents.json b/src/agent/test_tool/mock_server/responses/agents.json new file mode 100644 index 0000000000..d44e37635d --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/agents.json @@ -0,0 +1 @@ +{"success":"success"} diff --git a/src/agent/test_tool/mock_server/responses/commands.json b/src/agent/test_tool/mock_server/responses/commands.json new file mode 100644 index 0000000000..cb71a8d79b --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/commands.json @@ -0,0 +1 @@ +{"commands":[{"id":"id","status":"sent","info":"string","args":["module","command",["arg1"]],"agent":{"id":"agentID"}}]} diff --git a/src/agent/test_tool/mock_server/responses/events_stateful.json b/src/agent/test_tool/mock_server/responses/events_stateful.json new file mode 100644 index 0000000000..81680ce7d7 --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/events_stateful.json @@ -0,0 +1 @@ +{"stateful":"stateful"} diff --git a/src/agent/test_tool/mock_server/responses/events_stateless.json b/src/agent/test_tool/mock_server/responses/events_stateless.json new file mode 100644 index 0000000000..fa5b13e335 --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/events_stateless.json @@ -0,0 +1 @@ +{"stateless":"stateless"} diff --git a/src/agent/test_tool/mock_server/responses/user_authenticate.json b/src/agent/test_tool/mock_server/responses/user_authenticate.json new file mode 100644 index 0000000000..74850ff3a7 --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/user_authenticate.json @@ -0,0 +1 @@ +{"data": {"token": "token"}} From c986a224576cdb3e4229064c09f986ddc478b62d Mon Sep 17 00:00:00 2001 From: Nicolas Gomez Palacios Date: Fri, 8 Nov 2024 10:21:32 -0300 Subject: [PATCH 2/3] test(mock-server): the possibility to save the logs of incoming queries has been added --- src/agent/test_tool/mock_server/README.md | 1 + .../test_tool/mock_server/mock_server.py | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/agent/test_tool/mock_server/README.md b/src/agent/test_tool/mock_server/README.md index 409366fd26..1346799e2b 100644 --- a/src/agent/test_tool/mock_server/README.md +++ b/src/agent/test_tool/mock_server/README.md @@ -34,6 +34,7 @@ You can configure and start the server by specifying the desired ports and SSL c - `--ssl_key`: Path to the SSL key file for HTTPS (default: key.pem) - `--ssl_cert`: Path to the SSL certificate file for HTTPS (default: cert.pem) - `--protocol`: Specify the protocol to use (http or https). Defaults to https. + - `--outfile`: File path to save incoming request logs #### Examples Run the server over HTTPS (default): diff --git a/src/agent/test_tool/mock_server/mock_server.py b/src/agent/test_tool/mock_server/mock_server.py index 4f844438b5..fe8889ff52 100644 --- a/src/agent/test_tool/mock_server/mock_server.py +++ b/src/agent/test_tool/mock_server/mock_server.py @@ -17,6 +17,8 @@ # Secret key to sign the JWT (you can change it for a secure key) SECRET_KEY = "my_secret_key" +log_file_path = None + def generate_authentication_response(expiration_seconds=60): # Set the expiration time in seconds expiration_time = datetime.utcnow() + timedelta(seconds=expiration_seconds) @@ -41,6 +43,14 @@ def generate_authentication_response(expiration_seconds=60): return json.dumps(response) +def log_request(method, endpoint, headers, body): + if log_file_path: + with open(log_file_path, 'a') as log_file: + timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + log_file.write(f"\n[{timestamp}] {method} {endpoint}\n") + log_file.write(f"Headers:\n{headers}\n") + log_file.write(f"Body:\n{body}\n\n") + class MockHandler(BaseHTTPRequestHandler): def _set_headers(self, code, content_type="application/json", content_length=None): self.send_response(code) @@ -58,8 +68,11 @@ def _load_response(self, filename): return json.dumps({"error": "Response file not found"}) def do_POST(self): - response = None + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length).decode('utf-8') + log_request("POST", self.path, self.headers, body) + response = None if self.path == "/security/user/authenticate": response = self._load_response("user_authenticate.json") self._set_headers(code=200, content_length=len(response)) @@ -85,8 +98,9 @@ def do_POST(self): self.close_connection = True def do_GET(self): - response = None + log_request("GET", self.path, self.headers, "") + response = None if self.path == "/api/v1/commands": response = self._load_response("commands.json") self._set_headers(code=200, content_length=len(response)) @@ -126,8 +140,10 @@ def do_GET(self): self.wfile.flush() self.close_connection = True -def run_server(port, ssl_key=None, ssl_cert=None, handler_class=MockHandler, use_https=True): +def run_server(port, ssl_key=None, ssl_cert=None, handler_class=MockHandler, use_https=True, outfile=None): server_address = ('', port) + global log_file_path + log_file_path = outfile # Use HTTPS if both an SSL certificate and key are provided and the protocol is HTTPS if use_https and ssl_key and ssl_cert: @@ -169,6 +185,10 @@ def run_server(port, ssl_key=None, ssl_cert=None, handler_class=MockHandler, use "--protocol", type=str, choices=["http", "https"], default="https", help="Specify whether to use HTTP or HTTPS (default: https)" ) + parser.add_argument( + "--outfile", type=str, + help="File path to save incoming request logs" + ) # Parse the command-line arguments args = parser.parse_args() @@ -176,8 +196,8 @@ def run_server(port, ssl_key=None, ssl_cert=None, handler_class=MockHandler, use use_https = args.protocol == "https" # Create threads for each server with the configured parameters - thread1 = threading.Thread(target=run_server, args=(args.port1, args.ssl_key, args.ssl_cert, MockHandler, use_https)) - thread2 = threading.Thread(target=run_server, args=(args.port2, args.ssl_key, args.ssl_cert, MockHandler, use_https)) + thread1 = threading.Thread(target=run_server, args=(args.port1, args.ssl_key, args.ssl_cert, MockHandler, use_https, args.outfile)) + thread2 = threading.Thread(target=run_server, args=(args.port2, args.ssl_key, args.ssl_cert, MockHandler, use_https, args.outfile)) # Start servers thread1.start() From 6d942c7ad6c000cb9a4bf9f1496ff6564f10e39b Mon Sep 17 00:00:00 2001 From: Nicolas Gomez Palacios Date: Fri, 8 Nov 2024 13:44:50 -0300 Subject: [PATCH 3/3] test(mock-server): sends responses one object at a time, deletes the sent response. Predefined responses were moved to a template file. --- .../test_tool/mock_server/mock_server.py | 30 ++++++++++++++----- .../mock_server/responses/agents.json | 1 - .../mock_server/responses/commands.json | 1 - .../responses/events_stateful.json | 1 - .../responses/events_stateless.json | 1 - .../responses/templates/agents.templates | 1 + .../responses/templates/commands.templates | 1 + .../templates/events_stateful.templates | 1 + .../templates/events_stateless.templates | 1 + .../templates/user_authenticate.templates | 1 + .../responses/user_authenticate.json | 1 - 11 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 src/agent/test_tool/mock_server/responses/templates/agents.templates create mode 100644 src/agent/test_tool/mock_server/responses/templates/commands.templates create mode 100644 src/agent/test_tool/mock_server/responses/templates/events_stateful.templates create mode 100644 src/agent/test_tool/mock_server/responses/templates/events_stateless.templates create mode 100644 src/agent/test_tool/mock_server/responses/templates/user_authenticate.templates diff --git a/src/agent/test_tool/mock_server/mock_server.py b/src/agent/test_tool/mock_server/mock_server.py index fe8889ff52..80ed0be568 100644 --- a/src/agent/test_tool/mock_server/mock_server.py +++ b/src/agent/test_tool/mock_server/mock_server.py @@ -61,11 +61,25 @@ def _set_headers(self, code, content_type="application/json", content_length=Non self.end_headers() def _load_response(self, filename): + file_path = os.path.join(RESPONSES_DIR, filename) + try: - with open(os.path.join(RESPONSES_DIR, filename), 'r') as f: - return f.read() - except FileNotFoundError: - return json.dumps({"error": "Response file not found"}) + with open(file_path, 'r') as f: + lines = f.readlines() + + if not lines: + return json.dumps({}) + + response_line = lines[0].strip() + response = json.loads(response_line) + + with open(file_path, 'w') as f: + f.writelines(lines[1:]) + + return json.dumps(response) + + except (FileNotFoundError, json.JSONDecodeError) as e: + return json.dumps({"error": f"File error: {str(e)}"}) def do_POST(self): content_length = int(self.headers.get('Content-Length', 0)) @@ -83,11 +97,11 @@ def do_POST(self): response = generate_authentication_response() self._set_headers(code=200, content_length=len(response)) elif self.path == "/api/v1/events/stateful": - #response = self._load_response("events_stateful.json") - self._set_headers(code=200) # If an response is given, add the size of the response. + response = self._load_response("events_stateful.json") + self._set_headers(code=200, content_length=len(response)) elif self.path == "/api/v1/events/stateless": - #response = self._load_response("events_stateless.json") - self._set_headers(code=200) # If an response is given, add the size of the response. + response = self._load_response("events_stateless.json") + self._set_headers(code=200, content_length=len(response)) else: self.send_error(404, f"Not found: {self.path}") return diff --git a/src/agent/test_tool/mock_server/responses/agents.json b/src/agent/test_tool/mock_server/responses/agents.json index d44e37635d..e69de29bb2 100644 --- a/src/agent/test_tool/mock_server/responses/agents.json +++ b/src/agent/test_tool/mock_server/responses/agents.json @@ -1 +0,0 @@ -{"success":"success"} diff --git a/src/agent/test_tool/mock_server/responses/commands.json b/src/agent/test_tool/mock_server/responses/commands.json index cb71a8d79b..e69de29bb2 100644 --- a/src/agent/test_tool/mock_server/responses/commands.json +++ b/src/agent/test_tool/mock_server/responses/commands.json @@ -1 +0,0 @@ -{"commands":[{"id":"id","status":"sent","info":"string","args":["module","command",["arg1"]],"agent":{"id":"agentID"}}]} diff --git a/src/agent/test_tool/mock_server/responses/events_stateful.json b/src/agent/test_tool/mock_server/responses/events_stateful.json index 81680ce7d7..e69de29bb2 100644 --- a/src/agent/test_tool/mock_server/responses/events_stateful.json +++ b/src/agent/test_tool/mock_server/responses/events_stateful.json @@ -1 +0,0 @@ -{"stateful":"stateful"} diff --git a/src/agent/test_tool/mock_server/responses/events_stateless.json b/src/agent/test_tool/mock_server/responses/events_stateless.json index fa5b13e335..e69de29bb2 100644 --- a/src/agent/test_tool/mock_server/responses/events_stateless.json +++ b/src/agent/test_tool/mock_server/responses/events_stateless.json @@ -1 +0,0 @@ -{"stateless":"stateless"} diff --git a/src/agent/test_tool/mock_server/responses/templates/agents.templates b/src/agent/test_tool/mock_server/responses/templates/agents.templates new file mode 100644 index 0000000000..d44e37635d --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/templates/agents.templates @@ -0,0 +1 @@ +{"success":"success"} diff --git a/src/agent/test_tool/mock_server/responses/templates/commands.templates b/src/agent/test_tool/mock_server/responses/templates/commands.templates new file mode 100644 index 0000000000..cb71a8d79b --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/templates/commands.templates @@ -0,0 +1 @@ +{"commands":[{"id":"id","status":"sent","info":"string","args":["module","command",["arg1"]],"agent":{"id":"agentID"}}]} diff --git a/src/agent/test_tool/mock_server/responses/templates/events_stateful.templates b/src/agent/test_tool/mock_server/responses/templates/events_stateful.templates new file mode 100644 index 0000000000..81680ce7d7 --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/templates/events_stateful.templates @@ -0,0 +1 @@ +{"stateful":"stateful"} diff --git a/src/agent/test_tool/mock_server/responses/templates/events_stateless.templates b/src/agent/test_tool/mock_server/responses/templates/events_stateless.templates new file mode 100644 index 0000000000..fa5b13e335 --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/templates/events_stateless.templates @@ -0,0 +1 @@ +{"stateless":"stateless"} diff --git a/src/agent/test_tool/mock_server/responses/templates/user_authenticate.templates b/src/agent/test_tool/mock_server/responses/templates/user_authenticate.templates new file mode 100644 index 0000000000..74850ff3a7 --- /dev/null +++ b/src/agent/test_tool/mock_server/responses/templates/user_authenticate.templates @@ -0,0 +1 @@ +{"data": {"token": "token"}} diff --git a/src/agent/test_tool/mock_server/responses/user_authenticate.json b/src/agent/test_tool/mock_server/responses/user_authenticate.json index 74850ff3a7..e69de29bb2 100644 --- a/src/agent/test_tool/mock_server/responses/user_authenticate.json +++ b/src/agent/test_tool/mock_server/responses/user_authenticate.json @@ -1 +0,0 @@ -{"data": {"token": "token"}}