Skip to content

Aik 3191 : Also get django context #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions aikido_firewall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
load_dotenv()


def protect():
def protect(module="any"):
"""Start Aikido agent"""
# Import sources
import aikido_firewall.sources.django
import aikido_firewall.sources.flask

if module != "django":
import aikido_firewall.sources.flask

Check warning on line 24 in aikido_firewall/__init__.py

View check run for this annotation

Codecov / codecov/patch

aikido_firewall/__init__.py#L23-L24

Added lines #L23 - L24 were not covered by tests

# Import sinks
import aikido_firewall.sinks.pymysql
Expand Down
46 changes: 38 additions & 8 deletions aikido_firewall/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import threading

SUPPORTED_SOURCES = ["django", "flask"]
local = threading.local()


Expand All @@ -15,21 +16,50 @@
return None


def parse_headers(headers):
"""Parse EnvironHeaders object into a dict"""
if isinstance(headers, dict):
return headers
return dict(zip(headers.keys(), headers.values()))


class Context:
"""
A context object, it stores everything that is important
for vulnerability detection
"""

def __init__(self, req):
def __init__(self, req, source):
if not source in SUPPORTED_SOURCES:
raise ValueError(f"Source {source} not supported")

Check warning on line 34 in aikido_firewall/context/__init__.py

View check run for this annotation

Codecov / codecov/patch

aikido_firewall/context/__init__.py#L34

Added line #L34 was not covered by tests
self.source = source

self.method = req.method
self.remote_address = req.remote_addr
self.url = req.url
self.body = req.form
self.headers = req.headers
self.query = req.args
self.cookies = req.cookies
self.source = "flask"
if source == "flask":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is mss iets meer elegant om maar 1 if statement te hebbne.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both ways for me are fine, hele ding was dat er sommige delen gwn voor allebei hetzelfde waren en ik niet dingen 2x wou schrijven, maar in the end is het idd mss clearer als ik het anders doe, one sec

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this might be more extendable also for the future

self.remote_address = req.remote_addr
elif source == "django":
self.remote_address = req.META.get("REMOTE_ADDR")

if source == "flask":
self.url = req.url
elif source == "django":
self.url = req.build_absolute_uri()

if source == "flask":
self.body = req.form.to_dict()
elif source == "django":
self.body = dict(req.POST)

self.headers = parse_headers(req.headers)
if source == "flask":
self.query = req.args.to_dict()
elif source == "django":
self.query = dict(req.GET)

if source == "flask":
self.cookies = req.cookies.to_dict()
elif source == "django":
self.cookies = req.COOKIES

def __reduce__(self):
return (
Expand Down
68 changes: 48 additions & 20 deletions aikido_firewall/context/init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,64 @@
from aikido_firewall.context import Context, get_current_context


@pytest.fixture
def sample_request():
# Mock a sample request object for testing
class Request:
def __init__(self):
self.method = "GET"
self.remote_addr = "127.0.0.1"
self.url = "/test"
self.form = {}
self.headers = {}
self.args = {}
self.cookies = {}

return Request()


def test_get_current_context_no_context():
# Test get_current_context() when no context is set
assert get_current_context() is None


def test_set_as_current_context(sample_request):
def test_set_as_current_context(mocker):
# Test set_as_current_context() method
context = Context(sample_request)
sample_request = mocker.MagicMock()
context = Context(sample_request, "flask")
context.set_as_current_context()
assert get_current_context() == context


def test_get_current_context_with_context(sample_request):
def test_get_current_context_with_context(mocker):
# Test get_current_context() when a context is set
context = Context(sample_request)
sample_request = mocker.MagicMock()
context = Context(sample_request, "flask")
context.set_as_current_context()
assert get_current_context() == context


def test_context_init_flask(mocker):
req = mocker.MagicMock()
req.method = "GET"
req.remote_addr = "127.0.0.1"
req.url = "http://example.com"
req.form.to_dict.return_value = {"key": "value"}
req.headers = {"Content-Type": "application/json"}
req.args.to_dict.return_value = {"key": "value"}
req.cookies.to_dict.return_value = {"cookie": "value"}

context = Context(req, "flask")
assert context.source == "flask"
assert context.method == "GET"
assert context.remote_address == "127.0.0.1"
assert context.url == "http://example.com"
assert context.body == {"key": "value"}
assert context.headers == {"Content-Type": "application/json"}
assert context.query == {"key": "value"}
assert context.cookies == {"cookie": "value"}


def test_context_init_django(mocker):
req = mocker.MagicMock()
req.method = "POST"
req.META.get.return_value = "127.0.0.1"
req.build_absolute_uri.return_value = "http://example.com"
req.POST = {"key": "value"}
req.headers = {"Content-Type": "application/json"}
req.GET = {"key": "value"}
req.COOKIES = {"cookie": "value"}

context = Context(req, "django")
assert context.source == "django"
assert context.method == "POST"
assert context.remote_address == "127.0.0.1"
assert context.url == "http://example.com"
assert context.body == {"key": "value"}
assert context.headers == {"Content-Type": "application/json"}
assert context.query == {"key": "value"}
assert context.cookies == {"cookie": "value"}
3 changes: 3 additions & 0 deletions aikido_firewall/middleware/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from aikido_firewall.helpers.logging import logger
from aikido_firewall.context import Context

Check warning on line 7 in aikido_firewall/middleware/django.py

View check run for this annotation

Codecov / codecov/patch

aikido_firewall/middleware/django.py#L7

Added line #L7 was not covered by tests


class AikidoMiddleware:
Expand All @@ -16,6 +17,8 @@

def __call__(self, request, *args, **kwargs):
logger.debug("Aikido middleware for `django` was called : __call__")
context = Context(request, "django")
context.set_as_current_context()

Check warning on line 21 in aikido_firewall/middleware/django.py

View check run for this annotation

Codecov / codecov/patch

aikido_firewall/middleware/django.py#L20-L21

Added lines #L20 - L21 were not covered by tests
return self.get_response(request)

def process_exception(self, request, exception):
Expand Down
2 changes: 1 addition & 1 deletion aikido_firewall/sources/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
def dispatch(self, request, call_next):
"""Dispatch function"""
logger.debug("Aikido middleware for `flask` was called")
context = Context(request)
context = Context(request, "flask")

Check warning on line 24 in aikido_firewall/sources/flask.py

View check run for this annotation

Codecov / codecov/patch

aikido_firewall/sources/flask.py#L24

Added line #L24 was not covered by tests
context.set_as_current_context()

response = call_next(request)
Expand Down
2 changes: 2 additions & 0 deletions sample-apps/django-mysql/manage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import aikido_firewall # Aikido module
aikido_firewall.protect("django")

import os
import sys

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<h1>Create a Dog</h1>
<form action="/create" method="post">
<label for="dog_name">Dog Name:</label>
<input type="text" id="dog_name" name="dog_name" required>
<button type="submit">Create Dog</button>
</form>
5 changes: 3 additions & 2 deletions sample-apps/django-mysql/sample_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
urlpatterns = [
path("", views.index, name="index"),
path("dogpage/<int:dog_id>", views.dog_page, name="dog_page"),
path("create/<dog_name>", views.create_dogpage, name="create"),
]
path("create/<dog_name>", views.create_dogpage, name="create_old"),
path("create", views.create, name="create")
]
6 changes: 5 additions & 1 deletion sample-apps/django-mysql/sample_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ def create_dogpage(request, dog_name):
query = 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("%s", "N/A")' % dog_name
print("QUERY : ", query)
cursor.execute(query)
return HttpResponse("Dog page created")
return HttpResponse("Dog page created")

def create(request):
template = loader.get_template("app/create_dog.html")
return HttpResponse(template.render({}, request))
Loading