diff --git a/.github/workflows/end2end.yml b/.github/workflows/end2end.yml
index 92fabfeb..9604570a 100644
--- a/.github/workflows/end2end.yml
+++ b/.github/workflows/end2end.yml
@@ -24,17 +24,16 @@ jobs:
strategy:
matrix:
app:
- - { name: django-mysql, testfile: end2end/django_mysql_test.py }
- - { name: django-mysql-gunicorn, testfile: end2end/django_mysql_gunicorn_test.py }
- - { name: django-postgres-gunicorn, testfile: end2end/django_postgres_gunicorn_test.py }
- - { name: flask-mongo, testfile: end2end/flask_mongo_test.py }
- - { name: flask-mysql, testfile: end2end/flask_mysql_test.py }
- - { name: flask-mysql-uwsgi, testfile: end2end/flask_mysql_uwsgi_test.py }
- - { name: flask-postgres, testfile: end2end/flask_postgres_test.py }
- - { name: flask-postgres-xml, testfile: end2end/flask_postgres_xml_test.py }
- - { name: flask-postgres-xml, testfile: end2end/flask_postgres_xml_lxml_test.py }
- - { name: quart-postgres-uvicorn, testfile: end2end/quart_postgres_uvicorn_test.py }
- - { name: starlette-postgres-uvicorn, testfile: end2end/starlette_postgres_uvicorn_test.py }
+ - { name: django-mysql, testfile: end2end/django_mysql.py }
+ - { name: django-mysql-gunicorn, testfile: end2end/django_mysql_gunicorn.py }
+ - { name: django-postgres-gunicorn, testfile: end2end/django_postgres_gunicorn.py }
+ - { name: flask-mongo, testfile: end2end/flask_mongo.py }
+ - { name: flask-mysql, testfile: end2end/flask_mysql.py }
+ - { name: flask-mysql-uwsgi, testfile: end2end/flask_mysql_uwsgi.py }
+ - { name: flask-postgres, testfile: end2end/flask_postgres.py }
+ - { name: flask-postgres-xml, testfile: end2end/flask_postgres_xml.py }
+ - { name: quart-postgres-uvicorn, testfile: end2end/quart_postgres_uvicorn.py }
+ - { name: starlette-postgres-uvicorn, testfile: end2end/starlette_postgres_uvicorn.py }
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Install packages
@@ -64,7 +63,7 @@ jobs:
- name: Start application
working-directory: ./sample-apps/${{ matrix.app.name }}
run: |
- nohup make run > output.log & tail -f output.log & sleep 20
- nohup make runZenDisabled & sleep 20
+ nohup make run > output.log & tail -f output.log & sleep 30
+ nohup make runZenDisabled & sleep 30
- name: Run end2end tests for application
- run: tail -f ./sample-apps/${{ matrix.app.name }}/output.log & poetry run pytest ./${{ matrix.app.testfile }}
+ run: tail -f ./sample-apps/${{ matrix.app.name }}/output.log & python ./${{ matrix.app.testfile }}
diff --git a/end2end/__init__.py b/end2end/__init__.py
index e69de29b..ec6ca485 100644
--- a/end2end/__init__.py
+++ b/end2end/__init__.py
@@ -0,0 +1,4 @@
+import json
+
+with open('end2end/attack_events.json', 'r') as file:
+ events = json.load(file)
diff --git a/end2end/attack_events.json b/end2end/attack_events.json
new file mode 100644
index 00000000..fc220469
--- /dev/null
+++ b/end2end/attack_events.json
@@ -0,0 +1,124 @@
+{
+ "django_mysql_attack_sql": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES (\"Dangerous bobby\", 1); -- \", \"N/A\")"
+ },
+ "operation": "MySQLdb.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Dangerous bobby\\\", 1); -- \"",
+ "source": "body"
+ },
+ "django_mysql_attack_shell": {
+ "blocked": true,
+ "kind": "shell_injection",
+ "metadata": {
+ "command": "ls -la"
+ },
+ "operation": "subprocess.Popen",
+ "pathToPayload": ".[0]",
+ "payload": "\"ls -la\"",
+ "source": "route_params"
+ },
+ "django_postgres_attack": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO sample_app_Dogs (dog_name, is_admin) VALUES ('Dangerous bobby', TRUE); -- ', FALSE)"
+ },
+ "operation": "psycopg2.Connection.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Dangerous bobby', TRUE); -- \"",
+ "source": "body"
+ },
+ "quart_postgres_attack": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous Bobby', TRUE); -- ', FALSE)"
+ },
+ "operation": "asyncpg.connection.Connection.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Dangerous Bobby', TRUE); -- \"",
+ "source": "body"
+ },
+ "flask_xml_attack": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Malicious dog', TRUE); -- ', FALSE)"
+ },
+ "operation": "psycopg2.Connection.Cursor.execute",
+ "pathToPayload": ".dog_name.[0]",
+ "payload": "\"Malicious dog', TRUE); -- \"",
+ "source": "xml"
+ },
+ "flask_mysql_attack": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES (\"Dangerous bobby\", 1); -- \", 0)"
+ },
+ "operation": "pymysql.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Dangerous bobby\\\", 1); -- \"",
+ "source": "body"
+ },
+ "flask_mongo_attack": {
+ "blocked": true,
+ "kind": "nosql_injection",
+ "metadata": {
+ "filter": "{\"dog_name\": \"bobby_tables\", \"pswd\": {\"$ne\": \"\"}}"
+ },
+ "operation": "pymongo.collection.Collection.find",
+ "pathToPayload": ".pswd",
+ "payload": "{\"$ne\": \"\"}",
+ "source": "body"
+ },
+ "flask_postgres_attack_body": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous Bobby', TRUE); -- ', FALSE)"
+ },
+ "operation": "psycopg2.Connection.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Dangerous Bobby', TRUE); -- \"",
+ "source": "body"
+ },
+ "flask_postgres_attack_cookie": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Bobby', TRUE) --', FALSE)"
+ },
+ "operation": "psycopg2.Connection.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Bobby', TRUE) --\"",
+ "source": "cookies"
+ },
+ "flask_mysql_attack_sql": {
+ "blocked": true,
+ "kind": "sql_injection",
+ "metadata": {
+ "sql": "INSERT INTO dogs (dog_name, isAdmin) VALUES (\"Dangerous bobby\", 1); -- \", 0)"
+ },
+ "operation": "pymysql.Cursor.execute",
+ "pathToPayload": ".dog_name",
+ "payload": "\"Dangerous bobby\\\", 1); -- \"",
+ "source": "body"
+ },
+ "flask_mysql_attack_shell": {
+ "blocked": true,
+ "kind": "shell_injection",
+ "metadata": {
+ "command": "ls -la"
+ },
+ "operation": "subprocess.Popen",
+ "pathToPayload": ".command",
+ "payload": "\"ls -la\"",
+ "source": "route_params",
+ "user_id": "456"
+ }
+}
diff --git a/end2end/django_mysql.py b/end2end/django_mysql.py
new file mode 100644
index 00000000..dbd19b53
--- /dev/null
+++ b/end2end/django_mysql.py
@@ -0,0 +1,17 @@
+from __init__ import events
+from utils import App, Request
+
+django_mysql_app = App(8080)
+
+django_mysql_app.add_payload(
+ "sql", test_event=events["django_mysql_attack_sql"],
+ safe_request=Request(route="/app/create", body={'dog_name': 'Bobby'}, data_type="form"),
+ unsafe_request=Request(route="/app/create", body={'dog_name': 'Dangerous bobby", 1); -- '}, data_type="form")
+)
+django_mysql_app.add_payload(
+ "shell", test_event=events["django_mysql_attack_shell"],
+ safe_request=Request(route="/app/shell/bobby", method="GET"),
+ unsafe_request=Request(route="/app/shell/ls -la", method="GET")
+)
+
+django_mysql_app.test_all_payloads()
diff --git a/end2end/django_mysql_gunicorn.py b/end2end/django_mysql_gunicorn.py
new file mode 100644
index 00000000..29017955
--- /dev/null
+++ b/end2end/django_mysql_gunicorn.py
@@ -0,0 +1,12 @@
+from __init__ import events
+from utils import App, Request
+
+django_mysql_gunicorn_app = App(8082)
+
+django_mysql_gunicorn_app.add_payload(
+ "sql", test_event=events["django_mysql_attack_sql"],
+ safe_request=Request(route="/app/create/", body={'dog_name': 'Bobby'}, data_type="form"),
+ unsafe_request=Request(route="/app/create/", body={'dog_name': 'Dangerous bobby", 1); -- '}, data_type="form")
+)
+
+django_mysql_gunicorn_app.test_all_payloads()
diff --git a/end2end/django_mysql_gunicorn_test.py b/end2end/django_mysql_gunicorn_test.py
deleted file mode 100644
index 4637a343..00000000
--- a/end2end/django_mysql_gunicorn_test.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import pytest
-import requests
-import time
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for django_mysql_gunicorn sample app
-post_url_fw = "http://localhost:8082/app/create/"
-post_url_nofw = "http://localhost:8083/app/create/"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["gunicorn", "django"])
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'},
- 'operation': 'MySQLdb.Cursor.execute',
- 'pathToPayload': '.dog_name',
- 'payload': '"Dangerous bobby\\", 1); -- "',
- 'source': "body",
- 'user': None
- }
-
-def test_dangerous_response_without_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
diff --git a/end2end/django_mysql_test.py b/end2end/django_mysql_test.py
deleted file mode 100644
index 7e499be4..00000000
--- a/end2end/django_mysql_test.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type, validate_heartbeat
-
-# e2e tests for django_mysql sample app
-base_url_fw = "http://localhost:8080/app"
-base_url_nofw = "http://localhost:8081/app"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["django", "mysqlclient"])
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 200
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 500
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("Dangerous bobby", 1); -- ", "N/A")'},
- 'operation': 'MySQLdb.Cursor.execute',
- 'pathToPayload': '.dog_name',
- 'payload': '"Dangerous bobby\\", 1); -- "',
- 'source': "body",
- 'user': None
- }
-
-def test_dangerous_response_with_firewall_shell():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.get(base_url_fw + "/shell/ls -la")
- assert res.status_code == 500
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 2
- del attacks[0] # Previous attack
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "shell_injection",
- 'metadata': {'command': 'ls -la'},
- 'operation': 'subprocess.Popen',
- 'pathToPayload': '.[0]',
- 'payload': '"ls -la"',
- 'source': "route_params",
- 'user': None
- }
-
-def test_dangerous_response_without_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 200
-
-def test_initial_heartbeat():
- time.sleep(55) # Sleep 5 + 55 seconds for heartbeat
- events = fetch_events_from_mock("http://localhost:5000")
- heartbeat_events = filter_on_event_type(events, "heartbeat")
- assert len(heartbeat_events) == 1
- validate_heartbeat(heartbeat_events[0],
- [{
- "apispec": {},
- "hits": 1,
- "hits_delta_since_sync": 1,
- "method": "POST",
- "path": "/app/create"
- }],
- {"aborted":0,"attacksDetected":{"blocked":2,"total":2},"total":0}
- )
diff --git a/end2end/django_postgres_gunicorn.py b/end2end/django_postgres_gunicorn.py
new file mode 100644
index 00000000..cfdbbd15
--- /dev/null
+++ b/end2end/django_postgres_gunicorn.py
@@ -0,0 +1,12 @@
+from __init__ import events
+from utils import App, Request
+
+django_postgres_gunicorn_app = App(8100)
+
+django_postgres_gunicorn_app.add_payload(
+ "sql", test_event=events["django_postgres_attack"],
+ safe_request=Request(route="/app/create", body={'dog_name': 'Bobby'}, data_type="form"),
+ unsafe_request=Request(route="/app/create", body={'dog_name': "Dangerous bobby', TRUE); -- "}, data_type="form")
+)
+
+django_postgres_gunicorn_app.test_all_payloads()
diff --git a/end2end/django_postgres_gunicorn_test.py b/end2end/django_postgres_gunicorn_test.py
deleted file mode 100644
index 4815558d..00000000
--- a/end2end/django_postgres_gunicorn_test.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import pytest
-import requests
-import time
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for django_postgres_gunicorn sample app
-post_url_fw = "http://localhost:8100/app/create"
-post_url_nofw = "http://localhost:8101/app/create"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["gunicorn", "django", "psycopg2-binary"])
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- dog_name = "Dangerous bobby', TRUE); -- "
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': "INSERT INTO sample_app_Dogs (dog_name, is_admin) VALUES ('Dangerous bobby', TRUE); -- ', FALSE)"},
- 'operation': "psycopg2.Connection.Cursor.execute",
- 'pathToPayload': '.dog_name',
- 'payload': "\"Dangerous bobby', TRUE); -- \"",
- 'source': "body",
- 'user': None
- }
-
-def test_dangerous_response_without_firewall():
- dog_name = "Dangerous bobby', TRUE); -- "
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
diff --git a/end2end/flask_mongo.py b/end2end/flask_mongo.py
new file mode 100644
index 00000000..2c3dbe26
--- /dev/null
+++ b/end2end/flask_mongo.py
@@ -0,0 +1,24 @@
+from __init__ import events
+from utils import App, Request
+
+flask_mongo_app = App(8094)
+
+# Create dog :
+status_code_create = Request(route="/create", body={
+ "dog_name": "bobby_tables", "pswd": "bobby123"
+}, data_type="form").execute(flask_mongo_app.urls["enabled"])
+assert status_code_create == 200
+
+# Payloads :
+flask_mongo_app.add_payload(
+ "nosql", test_event=events["flask_mongo_attack"],
+ safe_request=Request("/auth", body={"dog_name": "bobby_tables", "pswd": "bobby123"}),
+ unsafe_request=Request("/auth", body={"dog_name": "bobby_tables", "pswd": { "$ne": ""}})
+)
+flask_mongo_app.add_payload(
+ "nosql_force", test_event=events["flask_mongo_attack"],
+ safe_request=Request("/auth_force", body={"dog_name": "bobby_tables", "pswd": "bobby123"}),
+ unsafe_request=Request("/auth_force", body={"dog_name": "bobby_tables", "pswd": { "$ne": ""}})
+)
+
+flask_mongo_app.test_all_payloads()
diff --git a/end2end/flask_mongo_test.py b/end2end/flask_mongo_test.py
deleted file mode 100644
index bb32589c..00000000
--- a/end2end/flask_mongo_test.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import json
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_mysql sample app
-post_url_fw = "http://localhost:8094/create"
-post_url_nofw = "http://localhost:8095/create"
-
-post_json_url_fw = "http://localhost:8094/auth"
-post_json_url_nofw = "http://localhost:8095/auth"
-
-
-# Create dogs:
-def test_create_dog_fw():
- dog_name = "bobby_tables"
- pswd = "bobby123"
- res = requests.post(post_url_fw, data={'dog_name': dog_name, 'pswd': pswd})
- print(res.text)
- assert "created successfully" in res.text
- assert res.status_code == 200
-def test_create_dog_no_fw():
- dog_name = "bobby_tables2"
- pswd = "bobby123"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name, 'pswd': pswd})
- print(res.text)
- assert "created successfully" in res.text
- assert res.status_code == 200
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["flask", "pymongo"])
-
-# Auth dogs with right password:
-def test_safe_auth_fw():
- dog_name = "bobby_tables"
- pswd = "bobby123"
- res = requests.post(post_json_url_fw, json={'dog_name': dog_name, "pswd": pswd})
- assert res.ok
- assert res.text == "Dog with name bobby_tables authenticated successfully"
- assert res.status_code == 200
-def test_safe_auth_nofw():
- dog_name = "bobby_tables"
- pswd = "bobby123"
- res = requests.post(post_json_url_nofw, json={'dog_name': dog_name, "pswd": pswd})
- assert res.ok
- assert res.text == "Dog with name bobby_tables authenticated successfully"
- assert res.status_code == 200
-
-# Auth dogs with wrong password:
-def test_safe_auth_wrong_pswd_fw():
- dog_name = "bobby_tables"
- pswd = "WrongPassword"
- res = requests.post(post_json_url_fw, json={'dog_name': dog_name, "pswd": pswd})
- assert res.ok
- assert res.text == "Auth failed"
- assert res.status_code == 200
-def test_safe_auth_wrong_pswd_nofw():
- dog_name = "bobby_tables"
- pswd = "WrongPassword"
- res = requests.post(post_json_url_nofw, json={'dog_name': dog_name, "pswd": pswd})
- assert res.ok
- assert res.text == "Auth failed"
- assert res.status_code == 200
-
-# Test NoSQL injection:
-def test_dangerous_auth_fw():
- dog_name = "bobby_tables"
- pswd = { "$ne": ""}
- res = requests.post(post_json_url_fw, json={'dog_name': dog_name, "pswd": pswd})
-
- assert not res.ok
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "nosql_injection",
- 'metadata': {'filter': '{"dog_name": "bobby_tables", "pswd": {"$ne": ""}}'},
- 'operation': "pymongo.collection.Collection.find",
- 'pathToPayload': ".pswd",
- 'payload': '{"$ne": ""}',
- 'source': "body",
- 'user': None
- }
-
-def test_dangerous_auth_nofw():
- dog_name = "bobby_tables"
- pswd = { "$ne": ""}
- res = requests.post(post_json_url_nofw, json={'dog_name': dog_name, "pswd": pswd})
- assert res.ok
- assert res.text == "Dog with name bobby_tables authenticated successfully"
- assert res.status_code == 200
-
-
-def test_dangerous_auth_fw_force():
- dog_name = "bobby_tables"
- pswd = {"$ne": ""}
- json_data = json.dumps({'dog_name': dog_name, "pswd": pswd})
- res = requests.post(post_json_url_fw + "_force", data=json_data)
-
- assert not res.ok
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 2
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "nosql_injection",
- 'metadata': {'filter': '{"dog_name": "bobby_tables", "pswd": {"$ne": ""}}'},
- 'operation': "pymongo.collection.Collection.find",
- 'pathToPayload': ".pswd",
- 'payload': '{"$ne": ""}',
- 'source': "body",
- 'user': None
- }
diff --git a/end2end/flask_mysql.py b/end2end/flask_mysql.py
new file mode 100644
index 00000000..5e9a2b9f
--- /dev/null
+++ b/end2end/flask_mysql.py
@@ -0,0 +1,18 @@
+from __init__ import events
+from utils import App, Request
+
+flask_mysql_app = App(8086)
+
+flask_mysql_app.add_payload(
+ "sql", test_event=events["flask_mysql_attack_sql"],
+ safe_request=Request("/create", body={"dog_name": "Bobby"}, data_type="form"),
+ unsafe_request=Request("/create", body={"dog_name": "Dangerous bobby\", 1); -- "}, data_type="form")
+)
+flask_mysql_app.add_payload(
+ "shell", test_event=events["flask_mysql_attack_shell"],
+ safe_request=Request(route="/shell/bobby", method="GET"),
+ unsafe_request=Request(route="/shell/ls -la", method="GET", headers={"user": "456"})
+)
+
+flask_mysql_app.test_all_payloads()
+flask_mysql_app.test_rate_limiting()
diff --git a/end2end/flask_mysql_test.py b/end2end/flask_mysql_test.py
deleted file mode 100644
index 2365521e..00000000
--- a/end2end/flask_mysql_test.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import pytest
-import requests
-import time
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-# e2e tests for flask_mysql sample app
-base_url_fw = "http://localhost:8086"
-base_url_nofw = "http://localhost:8087"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["flask", "pymysql"])
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- events = fetch_events_from_mock("http://localhost:5000")
- assert len(filter_on_event_type(events, "detected_attack")) == 0
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_fw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"]["blocked"] == True
- assert attacks[0]["attack"]["kind"] == "sql_injection"
- assert attacks[0]["attack"]["metadata"]["sql"] == 'INSERT INTO dogs (dog_name, isAdmin) VALUES ("Dangerous bobby", 1); -- ", 0)'
- assert attacks[0]["attack"]["operation"] == 'pymysql.Cursor.execute'
- assert attacks[0]["attack"]["pathToPayload"] == '.dog_name'
- assert attacks[0]["attack"]["payload"] == '"Dangerous bobby\\", 1); -- "'
- assert attacks[0]["attack"]["source"] == "body"
- assert attacks[0]["attack"]["user"]["id"] == "123"
- assert attacks[0]["attack"]["user"]["name"] == "John Doe"
-
-
-def test_dangerous_response_with_firewall_route_params():
- events = fetch_events_from_mock("http://localhost:5000")
- assert len(filter_on_event_type(events, "detected_attack")) == 1
- res = requests.get(base_url_fw + "/shell/ls -la")
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 2
- del attacks[0]
- assert attacks[0]["attack"]["blocked"] == True
- assert attacks[0]["attack"]["kind"] == "shell_injection"
- assert attacks[0]["attack"]['metadata']['command'] == 'ls -la'
- assert attacks[0]["attack"]["operation"] == 'subprocess.Popen'
- assert attacks[0]["attack"]["pathToPayload"] == '.command'
- assert attacks[0]["attack"]["payload"] == '"ls -la"'
- assert attacks[0]["attack"]["source"] == "route_params"
- assert attacks[0]["attack"]["user"]["id"] == "123"
- assert attacks[0]["attack"]["user"]["name"] == "John Doe"
-
-
-def test_dangerous_response_without_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(base_url_nofw + "/create", data={'dog_name': dog_name})
- assert res.status_code == 200
-
-def test_ratelimiting_1_route():
- # First request :
- res = requests.get(base_url_fw + "/test_ratelimiting_1")
- assert res.status_code == 200
- # Second request :
- res = requests.get(base_url_fw + "/test_ratelimiting_1")
- assert res.status_code == 200
- # Third request :
- res = requests.get(base_url_fw + "/test_ratelimiting_1")
- assert res.status_code == 429
- # Fourth request :
- res = requests.get(base_url_fw + "/test_ratelimiting_1")
- assert res.status_code == 429
-
- time.sleep(5) # Wait until window expires
-
- # Fifth request :
- res = requests.get(base_url_fw + "/test_ratelimiting_1")
- assert res.status_code == 200
-
-
-def test_set_ip_forwarded_for():
- # IP allowed :
- res = requests.get(base_url_fw + "/", headers={
- "X-Forwarded-For": "1.1.1.1"
- })
- assert res.status_code == 200
- # IP Geo-blocked :
- res = requests.get(base_url_fw + "/", headers={
- "X-Forwarded-For": "1.2.3.4"
- })
- assert res.status_code == 403
- assert res.text == "Your IP address is blocked due to geo restrictions (Your IP: 1.2.3.4)"
diff --git a/end2end/flask_mysql_uwsgi.py b/end2end/flask_mysql_uwsgi.py
new file mode 100644
index 00000000..f940ee95
--- /dev/null
+++ b/end2end/flask_mysql_uwsgi.py
@@ -0,0 +1,12 @@
+from __init__ import events
+from utils import App, Request
+
+flask_mysql_uwsgi_app = App(8088)
+
+flask_mysql_uwsgi_app.add_payload(
+ "sql", test_event=events["flask_mysql_attack"],
+ safe_request=Request(route="/create", body={'dog_name': 'Bobby'}, data_type="form"),
+ unsafe_request=Request(route="/create", body={'dog_name': 'Dangerous bobby", 1); -- '}, data_type="form")
+)
+
+flask_mysql_uwsgi_app.test_all_payloads()
diff --git a/end2end/flask_mysql_uwsgi_test.py b/end2end/flask_mysql_uwsgi_test.py
deleted file mode 100644
index a1917a79..00000000
--- a/end2end/flask_mysql_uwsgi_test.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_mysql_uwsgi sample app
-post_url_fw = "http://localhost:8088/create"
-post_url_nofw = "http://localhost:8089/create"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["flask", "pymysql", "uwsgi"])
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': 'INSERT INTO dogs (dog_name, isAdmin) VALUES ("Dangerous bobby", 1); -- ", 0)'},
- 'operation': 'pymysql.Cursor.execute',
- 'pathToPayload': '.dog_name',
- 'payload': '"Dangerous bobby\\", 1); -- "',
- 'source': "body",
- 'user': None
- }
-
-def test_dangerous_response_without_firewall():
- dog_name = 'Dangerous bobby", 1); -- '
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
diff --git a/end2end/flask_postgres.py b/end2end/flask_postgres.py
new file mode 100644
index 00000000..48989c09
--- /dev/null
+++ b/end2end/flask_postgres.py
@@ -0,0 +1,17 @@
+from __init__ import events
+from utils import App, Request
+
+flask_postgres_app = App(8090)
+
+flask_postgres_app.add_payload(
+ "sql", test_event=events["flask_postgres_attack_body"],
+ safe_request=Request("/create", body={"dog_name": "Bobby Tables"}, data_type="form"),
+ unsafe_request=Request("/create", body={"dog_name": "Dangerous Bobby', TRUE); -- "}, data_type="form")
+)
+flask_postgres_app.add_payload(
+ "sql_cookie", test_event=events["flask_postgres_attack_cookie"],
+ safe_request=Request("/create_with_cookie", method="GET", cookies={"dog_name": "Bobby Tables"}),
+ unsafe_request=Request("/create_with_cookie", method="GET", cookies={"dog_name": "Bobby', TRUE) -- "})
+)
+
+flask_postgres_app.test_all_payloads()
diff --git a/end2end/flask_postgres_test.py b/end2end/flask_postgres_test.py
deleted file mode 100644
index 16be5222..00000000
--- a/end2end/flask_postgres_test.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import time
-import pytest
-import json
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_postgres sample app
-post_url_fw = "http://localhost:8090/create"
-post_url_nofw = "http://localhost:8091/create"
-get_url_cookie_fw = "http://localhost:8090/create_with_cookie"
-get_url_cookie_nofw = "http://localhost:8091/create_with_cookie"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["flask", "psycopg2-binary"])
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- dog_name = "Dangerous Bobby', TRUE); -- "
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 500
-
-def test_dangerous_response_without_firewall():
- dog_name = "Dangerous Bobby', TRUE); -- "
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 200
-
-
-def test_safe_cookie_creation_with_firewall():
- cookies = {
- "dog_name": "Bobby Tables",
- "corrupt_data": ";;;;;;;;;;;;;"
- }
- res = requests.get(get_url_cookie_fw, cookies=cookies)
- assert res.status_code == 200
-
-def test_safe_cookie_creation_without_firewall():
- cookies = {
- "dog_name": "Bobby Tables",
- "corrupt_data": ";;;;;;;;;;;;;"
-
- }
- res = requests.get(get_url_cookie_nofw, cookies=cookies)
- assert res.status_code == 200
-
-
-def test_dangerous_cookie_creation_with_firewall():
- cookies = {
- "dog_name": "Bobby', TRUE) -- ",
- "corrupt_data": ";;;;;;;;;;;;;"
- }
- res = requests.get(get_url_cookie_fw, cookies=cookies)
- assert res.status_code == 500
-
-def test_dangerous_cookie_creation_without_firewall():
- cookies = {
- "dog_name": "Bobby', TRUE) -- ",
- "corrupt_data": ";;;;;;;;;;;;;"
- }
- res = requests.get(get_url_cookie_nofw, cookies=cookies)
- assert res.status_code == 200
-
-def test_attacks_detected():
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 2
- del attacks[0]["attack"]["stack"]
- del attacks[1]["attack"]["stack"]
-
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous Bobby', TRUE); -- ', FALSE)"},
- 'operation': "psycopg2.Connection.Cursor.execute",
- 'pathToPayload': '.dog_name',
- 'payload': '"Dangerous Bobby\', TRUE); -- "',
- 'source': "body",
- 'user': None
- }
- assert attacks[1]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Bobby', TRUE) --', FALSE)"},
- 'operation': "psycopg2.Connection.Cursor.execute",
- 'pathToPayload': '.dog_name',
- 'payload': "\"Bobby', TRUE) --\"",
- 'source': "cookies",
- 'user': None
- }
diff --git a/end2end/flask_postgres_xml.py b/end2end/flask_postgres_xml.py
new file mode 100644
index 00000000..3e2a3ded
--- /dev/null
+++ b/end2end/flask_postgres_xml.py
@@ -0,0 +1,17 @@
+from __init__ import events
+from utils import App, Request
+
+flask_xml_app = App(8092)
+
+flask_xml_app.add_payload(
+ "sql_with_xml", test_event=events["flask_xml_attack"],
+ safe_request=Request(route="/xml_post", body='', data_type="form"),
+ unsafe_request=Request(route="/xml_post", body='', data_type="form")
+)
+flask_xml_app.add_payload(
+ "sql_with_lxml", test_event=events["flask_xml_attack"],
+ safe_request=Request(route="/xml_post_lxml", body='', data_type="form"),
+ unsafe_request=Request(route="/xml_post_lxml", body='', data_type="form")
+)
+
+flask_xml_app.test_all_payloads()
diff --git a/end2end/flask_postgres_xml_lxml_test.py b/end2end/flask_postgres_xml_lxml_test.py
deleted file mode 100644
index e4455248..00000000
--- a/end2end/flask_postgres_xml_lxml_test.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_postgres sample app
-post_url_fw = "http://localhost:8092/xml_post_lxml"
-post_url_nofw = "http://localhost:8093/xml_post_lxml"
-
-def test_safe_response_with_firewall():
- xml_data = ''
- res = requests.post(post_url_fw, data=xml_data)
- assert res.status_code == 200
-
-def test_safe_response_without_firewall():
- xml_data = ''
- res = requests.post(post_url_nofw, data=xml_data)
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- xml_data = ''
- res = requests.post(post_url_fw, data=xml_data)
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
-
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Malicious dog', TRUE); -- ', FALSE)"},
- 'operation': "psycopg2.Connection.Cursor.execute",
- 'pathToPayload': ".dog_name.[0]",
- 'payload': "\"Malicious dog', TRUE); -- \"",
- 'source': "xml",
- 'user': None
- }
-
-def test_dangerous_response_without_firewall():
- xml_data = ''
- res = requests.post(post_url_nofw, data=xml_data)
- assert res.status_code == 200
-
diff --git a/end2end/flask_postgres_xml_test.py b/end2end/flask_postgres_xml_test.py
deleted file mode 100644
index 47b31f21..00000000
--- a/end2end/flask_postgres_xml_test.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_postgres sample app
-post_url_fw = "http://localhost:8092/xml_post"
-post_url_nofw = "http://localhost:8093/xml_post"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], ["flask", "psycopg2-binary", "lxml"])
-
-def test_safe_response_with_firewall():
- xml_data = ''
- res = requests.post(post_url_fw, data=xml_data)
- assert res.status_code == 200
-
-def test_safe_response_without_firewall():
- xml_data = ''
- res = requests.post(post_url_nofw, data=xml_data)
- assert res.status_code == 200
-
-
-def test_dangerous_response_with_firewall():
- xml_data = ''
- res = requests.post(post_url_fw, data=xml_data)
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
-
- assert attacks[0]["attack"] == {
- "blocked": True,
- "kind": "sql_injection",
- 'metadata': {'sql': "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Malicious dog', TRUE); -- ', FALSE)"},
- 'operation': "psycopg2.Connection.Cursor.execute",
- 'pathToPayload': ".dog_name.[0]",
- 'payload': "\"Malicious dog', TRUE); -- \"",
- 'source': "xml",
- 'user': None
- }
-def test_dangerous_response_without_firewall():
- xml_data = ''
- res = requests.post(post_url_nofw, data=xml_data)
- assert res.status_code == 200
-
diff --git a/end2end/quart_postgres_uvicorn.py b/end2end/quart_postgres_uvicorn.py
new file mode 100644
index 00000000..6143d047
--- /dev/null
+++ b/end2end/quart_postgres_uvicorn.py
@@ -0,0 +1,13 @@
+from __init__ import events
+from utils import App, Request
+
+quart_postgres_app = App(8096, status_code_valid=201)
+
+quart_postgres_app.add_payload(
+ "sql", test_event=events["quart_postgres_attack"],
+ safe_request=Request(route="/create", body={'dog_name': 'Bobby'}, data_type="form"),
+ unsafe_request=Request(route="/create", body={'dog_name': "Dangerous Bobby', TRUE); -- "}, data_type="form")
+)
+
+quart_postgres_app.test_all_payloads()
+quart_postgres_app.test_rate_limiting()
diff --git a/end2end/quart_postgres_uvicorn_test.py b/end2end/quart_postgres_uvicorn_test.py
deleted file mode 100644
index ebea4a9a..00000000
--- a/end2end/quart_postgres_uvicorn_test.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_postgres sample app
-post_url_fw = "http://localhost:8096/create"
-post_url_nofw = "http://localhost:8097/create"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], None)
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 201
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 201
-
-
-def test_dangerous_response_with_firewall():
- dog_name = "Dangerous Bobby', TRUE); -- "
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"]["blocked"] == True
- assert attacks[0]["attack"]["kind"] == "sql_injection"
- assert attacks[0]["attack"]["metadata"]["sql"] == "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous Bobby', TRUE); -- ', FALSE)"
- assert attacks[0]["attack"]["operation"] == "asyncpg.connection.Connection.execute"
- assert attacks[0]["attack"]["pathToPayload"] == '.dog_name'
- assert attacks[0]["attack"]["payload"] == "\"Dangerous Bobby', TRUE); -- \""
- assert attacks[0]["attack"]["source"] == "body"
- assert attacks[0]["attack"]["user"]["id"] == "user123"
- assert attacks[0]["attack"]["user"]["name"] == "John Doe"
-
-
-def test_dangerous_response_without_firewall():
- dog_name = "Dangerous Bobby', TRUE); -- "
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 201
-
diff --git a/end2end/server/check_events_from_mock.py b/end2end/server/check_events_from_mock.py
deleted file mode 100644
index af8724be..00000000
--- a/end2end/server/check_events_from_mock.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import requests
-import json
-
-def fetch_events_from_mock(url):
- mock_events_url = f"{url}/mock/events"
- res = requests.get(mock_events_url, timeout=5)
- json_events = json.loads(res.content.decode("utf-8"))
- return json_events
-
-def filter_on_event_type(events, type):
- return [event for event in events if event["type"] == type]
-
-def validate_started_event(event, stack, dry_mode=False, serverless=False, os_name="Linux", platform="CPython"):
- assert event["agent"]["dryMode"] == dry_mode
- assert event["agent"]["library"] == "firewall-python"
- assert event["agent"]["nodeEnv"] == ""
- assert event["agent"]["os"]["name"] == os_name
- assert event["agent"]["platform"]["name"] == platform
- assert event["agent"]["serverless"] == serverless
- # # Check for packages is disabled until we start using them in core :
- # if stack is not None:
- # assert set(event["agent"]["stack"]) == set(stack)
-
-def validate_heartbeat(event, routes, req_stats):
- assert event["type"] == "heartbeat"
- assert event["routes"] == routes
- assert event["stats"]["requests"] == req_stats
diff --git a/end2end/server/mock_aikido_core.py b/end2end/server/mock_aikido_core.py
index 4fbc2748..5fb48af5 100644
--- a/end2end/server/mock_aikido_core.py
+++ b/end2end/server/mock_aikido_core.py
@@ -84,6 +84,12 @@ def mock_set_config():
def mock_get_events():
return jsonify(events)
+@app.route('/mock/reset', methods=['GET'])
+def mock_reset():
+ global events
+ events = [] # Reset events
+ return jsonify({})
+
if __name__ == '__main__':
if len(sys.argv) < 2 or len(sys.argv) > 3:
diff --git a/end2end/starlette_postgres_uvicorn.py b/end2end/starlette_postgres_uvicorn.py
new file mode 100644
index 00000000..f64d72e4
--- /dev/null
+++ b/end2end/starlette_postgres_uvicorn.py
@@ -0,0 +1,13 @@
+from __init__ import events
+from utils import App, Request
+
+starlette_postgres_app = App(8102, status_code_valid=201)
+
+starlette_postgres_app.add_payload(
+ "sql", test_event=events["quart_postgres_attack"],
+ safe_request=Request(route="/create", body={'dog_name': 'Bobby'}, data_type="form"),
+ unsafe_request=Request(route="/create", body={'dog_name': "Dangerous Bobby', TRUE); -- "}, data_type="form")
+)
+
+starlette_postgres_app.test_all_payloads()
+starlette_postgres_app.test_rate_limiting()
diff --git a/end2end/starlette_postgres_uvicorn_test.py b/end2end/starlette_postgres_uvicorn_test.py
deleted file mode 100644
index 331bedcd..00000000
--- a/end2end/starlette_postgres_uvicorn_test.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import time
-import pytest
-import requests
-from .server.check_events_from_mock import fetch_events_from_mock, validate_started_event, filter_on_event_type
-
-# e2e tests for flask_postgres sample app
-post_url_fw = "http://localhost:8102/create"
-post_url_nofw = "http://localhost:8103/create"
-sync_route_fw = "http://localhost:8102/sync_route"
-sync_route_nofw = "http://localhost:8103/sync_route"
-
-def test_firewall_started_okay():
- events = fetch_events_from_mock("http://localhost:5000")
- started_events = filter_on_event_type(events, "started")
- assert len(started_events) == 1
- validate_started_event(started_events[0], None) # Don't assert stack
-
-def test_safe_response_with_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 201
-
-def test_safe_response_without_firewall():
- dog_name = "Bobby Tables"
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 201
-
-
-def test_dangerous_response_with_firewall():
- dog_name = "Dangerous Bobby', TRUE); -- "
- res = requests.post(post_url_fw, data={'dog_name': dog_name})
- assert res.status_code == 500
-
- time.sleep(5) # Wait for attack to be reported
- events = fetch_events_from_mock("http://localhost:5000")
- attacks = filter_on_event_type(events, "detected_attack")
-
- assert len(attacks) == 1
- del attacks[0]["attack"]["stack"]
- assert attacks[0]["attack"]["blocked"] == True
- assert attacks[0]["attack"]["kind"] == "sql_injection"
- assert attacks[0]["attack"]["metadata"]["sql"] == "INSERT INTO dogs (dog_name, isAdmin) VALUES ('Dangerous Bobby', TRUE); -- ', FALSE)"
- assert attacks[0]["attack"]["operation"] == "asyncpg.connection.Connection.execute"
- assert attacks[0]["attack"]["pathToPayload"] == ".dog_name"
- assert attacks[0]["attack"]["payload"] == "\"Dangerous Bobby', TRUE); -- \""
- assert attacks[0]["attack"]["source"] == "body"
- assert attacks[0]["attack"]["user"]["id"] == "user123"
- assert attacks[0]["attack"]["user"]["name"] == "John Doe"
-
-
-def test_dangerous_response_without_firewall():
- dog_name = "Dangerous Bobby', TRUE); -- "
- res = requests.post(post_url_nofw, data={'dog_name': dog_name})
- assert res.status_code == 201
-
-
-def test_sync_route_with_firewall():
- res = requests.get(sync_route_fw)
- assert res.status_code == 200
-
-def test_sync_route_without_firewall():
- res = requests.get(sync_route_nofw)
- assert res.status_code == 200
diff --git a/end2end/utils/EventHandler.py b/end2end/utils/EventHandler.py
new file mode 100644
index 00000000..d0ccfa61
--- /dev/null
+++ b/end2end/utils/EventHandler.py
@@ -0,0 +1,20 @@
+import time
+import requests
+import json
+
+class EventHandler:
+ def __init__(self, url="http://localhost:5000"):
+ self.url = url
+ def reset(self):
+ print("Resetting stored events on mock server")
+ res = requests.get(self.url + "/mock/reset", timeout=5)
+ time.sleep(1)
+ def fetch_events_from_mock(self):
+ res = requests.get(self.url + "/mock/events", timeout=5)
+ json_events = json.loads(res.content.decode("utf-8"))
+ return json_events
+ def fetch_attacks(self):
+ return filter_on_event_type(self.fetch_events_from_mock(), "detected_attack")
+
+def filter_on_event_type(events, type):
+ return [event for event in events if event["type"] == type]
\ No newline at end of file
diff --git a/end2end/utils/__init__.py b/end2end/utils/__init__.py
new file mode 100644
index 00000000..92532ecd
--- /dev/null
+++ b/end2end/utils/__init__.py
@@ -0,0 +1,62 @@
+import time
+
+from .EventHandler import EventHandler
+from .assert_equals import assert_eq
+from .request import Request
+from .test_bot_blocking import test_bot_blocking
+from .test_ip_blocking import test_ip_blocking
+from .test_ratelimiting import test_ratelimiting, test_ratelimiting_per_user
+from .test_payloads_safe_vs_unsafe import test_payloads_safe_vs_unsafe
+
+class App:
+ def __init__(self, port, status_code_valid=200):
+ self.urls = {
+ "enabled": f"http://localhost:{port}",
+ "disabled": f"http://localhost:{port + 1}"
+ }
+ self.payloads = {}
+ self.event_handler = EventHandler()
+ self.status_code_valid = status_code_valid
+
+ def add_payload(self,key, safe_request, unsafe_request, test_event=None):
+ self.payloads[key] = {
+ "safe": safe_request,
+ "unsafe": unsafe_request,
+ "test_event": test_event
+ }
+
+ def test_payload(self, key):
+ if key not in self.payloads:
+ raise Exception("Payload " + key + " not found.")
+ payload = self.payloads.get(key)
+
+ self.event_handler.reset()
+ test_payloads_safe_vs_unsafe(payload, self.urls, status_code1=self.status_code_valid)
+ print("✅ Tested payload: " + key)
+
+ if payload["test_event"]:
+ time.sleep(5)
+ attacks = self.event_handler.fetch_attacks()
+ assert_eq(len(attacks), equals=1)
+ for k, v in payload["test_event"].items():
+ if k == "user_id": # exemption rule for user ids
+ assert_eq(attacks[0]["attack"]["user"]["id"], v)
+ else:
+ assert_eq(attacks[0]["attack"][k], equals=v)
+ print("✅ Tested accurate event reporting for: " + key)
+
+ def test_all_payloads(self):
+ for key in self.payloads.keys():
+ self.test_payload(key)
+
+ def test_blocking(self):
+ #test_bot_blocking(self.urls["enabled"])
+ #print("✅ Tested bot blocking")
+ test_ip_blocking(self.urls["enabled"])
+ print("✅ Tested IP Blocking")
+
+ def test_rate_limiting(self, route="/test_ratelimiting_1"):
+ test_ratelimiting(self.urls["enabled"] + route)
+ print("✅ Tested rate-limiting")
+ test_ratelimiting_per_user(self.urls["enabled"] + route)
+ print("✅ Tested rate-limiting (User)")
diff --git a/end2end/utils/assert_equals.py b/end2end/utils/assert_equals.py
new file mode 100644
index 00000000..70260a12
--- /dev/null
+++ b/end2end/utils/assert_equals.py
@@ -0,0 +1,4 @@
+def assert_eq(val1, equals, val2=None):
+ assert val1 == equals, f"Assertion failed: Expected {equals} != {val1}"
+ if val2 is not None:
+ assert val2 == equals, f"Assertion failed: Expected {equals} != {val2}"
\ No newline at end of file
diff --git a/end2end/utils/request.py b/end2end/utils/request.py
new file mode 100644
index 00000000..f35d7a84
--- /dev/null
+++ b/end2end/utils/request.py
@@ -0,0 +1,30 @@
+import requests
+
+class Request:
+ def __init__(self, route, method='POST', headers=None, data_type='json', body=None, cookies={}):
+ self.method = method
+ self.route = route
+ self.headers = headers if headers is not None else {}
+ self.data_type = data_type # 'json' or 'form'
+ self.body = body
+ self.cookies = cookies
+
+ def execute(self, base_url):
+ """Execute the request and return the status code."""
+ url = f"{base_url}{self.route}"
+
+ if self.method.upper() == 'POST':
+ if self.data_type == 'json':
+ response = requests.post(url, json=self.body, headers=self.headers, cookies=self.cookies)
+ elif self.data_type == 'form':
+ response = requests.post(url, data=self.body, headers=self.headers, cookies=self.cookies)
+ else:
+ raise ValueError("Unsupported data type. Use 'json' or 'form'.")
+ elif self.method.upper() == 'GET':
+ response = requests.get(url, headers=self.headers, cookies=self.cookies)
+ else:
+ raise ValueError("Unsupported HTTP method. Use 'GET' or 'POST'.")
+
+ return response.status_code
+ def __str__(self):
+ return f"Request(method={self.method}, route={self.route}, headers={self.headers}, data_type={self.data_type}, body={self.body})"
diff --git a/end2end/utils/test_bot_blocking.py b/end2end/utils/test_bot_blocking.py
new file mode 100644
index 00000000..12509786
--- /dev/null
+++ b/end2end/utils/test_bot_blocking.py
@@ -0,0 +1,36 @@
+import requests
+from utils.assert_equals import assert_eq
+
+def test_bot_blocking(url):
+ # Allowed User-Agents :
+ res = requests.get(url, headers={
+ 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.0 Chrome/91.0.4472.114 Mobile Safari/537.36"
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # Blocked User-Agent :
+ res = requests.get(url, headers={
+ 'User-Agent': "BYTESPIDER"
+ })
+ assert_eq(res.status_code, equals=403)
+ assert_eq(res.text, equals="You are not allowed to access this resource because you have been identified as a bot.")
+
+ # More complex blocked User-Agent :
+ res = requests.get(url, headers={
+ 'User-Agent': "Mozilla/5.0 (compatible; Bytespider/1.0; +http://bytespider.com/bot.html)"
+ })
+ assert_eq(res.status_code, equals=403)
+ assert_eq(res.text, equals="You are not allowed to access this resource because you have been identified as a bot.")
+ res = requests.get(url, headers={
+ 'User-Agent': "Mozilla/5.0 (compatible; AI2Bot/1.0; +http://www.aaai.org/Press/Reports/AI2Bot)"
+ })
+ assert_eq(res.status_code, equals=403)
+ assert_eq(res.text, equals="You are not allowed to access this resource because you have been identified as a bot.")
diff --git a/end2end/utils/test_ip_blocking.py b/end2end/utils/test_ip_blocking.py
new file mode 100644
index 00000000..0b65cf8e
--- /dev/null
+++ b/end2end/utils/test_ip_blocking.py
@@ -0,0 +1,41 @@
+import requests
+from utils.assert_equals import assert_eq
+
+def test_ip_blocking(url):
+ # Allowed IP :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # Blocked IP :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "1.2.3.4"
+ })
+ assert_eq(res.status_code, equals=403)
+ assert_eq(res.text, equals="Your IP address is blocked due to geo restrictions (Your IP: 1.2.3.4)")
+
+ # More complex X-Forwarded-For :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "invalid.ip.here.now, 1.2.3.4 "
+ })
+ assert_eq(res.status_code, equals=403)
+ assert_eq(res.text, equals="Your IP address is blocked due to geo restrictions (Your IP: 1.2.3.4)")
+
+ # More complex but safe X-Forwarded-For :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "invalid.ip.here.now, 192.168.1.1 "
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # It should only use the first valid ip
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1, 1.2.3.4"
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # It should work with an empty X-Forwarded-For
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': ""
+ })
+ assert_eq(res.status_code, equals=200)
diff --git a/end2end/utils/test_payloads_safe_vs_unsafe.py b/end2end/utils/test_payloads_safe_vs_unsafe.py
new file mode 100644
index 00000000..d885efce
--- /dev/null
+++ b/end2end/utils/test_payloads_safe_vs_unsafe.py
@@ -0,0 +1,13 @@
+from . import assert_eq
+
+def test_payloads_safe_vs_unsafe(payloads, urls, status_code1=200):
+ print("Safe req to : (1) " + urls["enabled"] + payloads["safe"].route)
+ assert_eq(val1=payloads["safe"].execute(urls["enabled"]), equals=status_code1)
+
+ print("Safe req to : (0) " + urls["disabled"] + payloads["safe"].route)
+ assert_eq(val1=payloads["safe"].execute(urls["disabled"]), equals=status_code1)
+
+ print("Unsafe req to : (1) " + urls["enabled"] + payloads["unsafe"].route)
+ assert_eq(val1=payloads["unsafe"].execute(urls["enabled"]), equals=500)
+ print("Unsafe req to : (0) " + urls["disabled"] + payloads["unsafe"].route)
+ assert_eq(val1=payloads["unsafe"].execute(urls["disabled"]), equals=status_code1)
diff --git a/end2end/utils/test_ratelimiting.py b/end2end/utils/test_ratelimiting.py
new file mode 100644
index 00000000..a06765c0
--- /dev/null
+++ b/end2end/utils/test_ratelimiting.py
@@ -0,0 +1,160 @@
+import time
+import requests
+from utils.assert_equals import assert_eq
+
+def test_ratelimiting(url):
+ # Test route (First req & 2nd Req) :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # 3rd & 4th (blocked) requests :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen. (Your IP: 192.168.1.1)")
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen. (Your IP: 192.168.1.1)")
+
+ # Now do the same but with a different IP in the same time block :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen. (Your IP: 192.168.1.2)")
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen. (Your IP: 192.168.1.2)")
+
+ # Now wait 5 seconds so your window is over and re-request :
+ time.sleep(5)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=200)
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.1"
+ })
+ assert_eq(res.status_code, equals=200)
+
+
+def test_ratelimiting_per_user(url):
+ # Test route (First req & 2nd Req) :
+ res = requests.get(url, headers={
+ 'user': 'id1'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'user': 'id1'
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # 3rd & 4th (blocked) requests :
+ res = requests.get(url, headers={
+ 'user': 'id1'
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen.")
+ res = requests.get(url, headers={
+ 'user': 'id1'
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen.")
+
+ # Now do the same but with a different User in the same time block :
+ res = requests.get(url, headers={
+ 'user': 'id2'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'user': 'id2'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'user': 'id2'
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen.")
+ res = requests.get(url, headers={
+ 'user': 'id2'
+ })
+ assert_eq(res.status_code, equals=429)
+ assert_eq(res.text, equals="You are rate limited by Zen.")
+
+ # Now wait 5 seconds so your window is over and re-request :
+ time.sleep(5)
+ res = requests.get(url, headers={
+ 'user': 'id2'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'user': 'id1'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'user': 'id2'
+ })
+ assert_eq(res.status_code, equals=200)
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'user': 'id1'
+ })
+ assert_eq(res.status_code, equals=200)
+
+ # Test it prefers user over IP :
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2"
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2",
+ 'user': 'id3'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2",
+ 'user': 'id4'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2",
+ 'user': 'id5'
+ })
+ assert_eq(res.status_code, equals=200)
+ res = requests.get(url, headers={
+ 'X-Forwarded-For': "192.168.1.2",
+ 'user': 'id6'
+ })
+ assert_eq(res.status_code, equals=200)
\ No newline at end of file
diff --git a/sample-apps/flask-mysql/app.py b/sample-apps/flask-mysql/app.py
index e96f47ae..21c0d43c 100644
--- a/sample-apps/flask-mysql/app.py
+++ b/sample-apps/flask-mysql/app.py
@@ -13,6 +13,7 @@
import subprocess
from flask import Flask, render_template, request
from flaskext.mysql import MySQL
+from werkzeug.wrappers import Request
import requests
import subprocess
@@ -25,7 +26,9 @@ class SetUserMiddleware:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
- aikido_zen.set_user({"id": "123", "name": "John Doe"})
+ req = Request(environ, shallow=True)
+ if req.headers.get("USER"):
+ aikido_zen.set_user({"id": req.headers.get("USER"), "name": "John Doe"})
return self.app(environ, start_response)
app.wsgi_app = AikidoFlaskMiddleware(app.wsgi_app)
app.wsgi_app = SetUserMiddleware(app.wsgi_app)
diff --git a/sample-apps/quart-postgres-uvicorn/app.py b/sample-apps/quart-postgres-uvicorn/app.py
index 65b59725..a1385a52 100644
--- a/sample-apps/quart-postgres-uvicorn/app.py
+++ b/sample-apps/quart-postgres-uvicorn/app.py
@@ -11,7 +11,9 @@ def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
- aikido_zen.set_user({"id": "user123", "name": "John Doe"})
+ for header, value in scope['headers']:
+ if header.decode("utf-8").upper() == 'USER':
+ aikido_zen.set_user({"id": value.decode("utf-8"), "name": "John Doe"})
return await self.app(scope, receive, send)
app.asgi_app = AikidoQuartMiddleware(app.asgi_app)
@@ -64,6 +66,10 @@ async def create_dog():
return jsonify({"message": f'Dog {dog_name} created successfully'}), 201
+@app.route("/test_ratelimiting_1", methods=['GET'])
+async def test_ratelimiting_1():
+ return jsonify({"message": "OK"})
+
@app.route("/create_many", methods=['POST'])
async def create_dog_many():
data = await request.form
diff --git a/sample-apps/starlette-postgres-uvicorn/app.py b/sample-apps/starlette-postgres-uvicorn/app.py
index d349154c..104cab15 100644
--- a/sample-apps/starlette-postgres-uvicorn/app.py
+++ b/sample-apps/starlette-postgres-uvicorn/app.py
@@ -73,7 +73,9 @@ def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
- aikido_zen.set_user({"id": "user123", "name": "John Doe"})
+ for header, value in scope['headers']:
+ if header.decode("utf-8").upper() == 'USER':
+ aikido_zen.set_user({"id": value.decode("utf-8"), "name": "John Doe"})
return await self.app(scope, receive, send)
middleware.append(Middleware(SetUserMiddleware))
middleware.append(Middleware(AikidoStarletteMiddleware))
@@ -87,6 +89,7 @@ async def __call__(self, scope, receive, send):
Route("/create", create_dog, methods=["POST"]),
Route("/sync_route", sync_route),
Route("/just", just, methods=["GET"]),
+ Route("/test_ratelimiting_1", just, methods=["GET"]),
Route("/delayed_route", delayed_route, methods=["GET"])
]
if len(middleware) != 0: