Revised the next URI utilities to apply URLSafeSerializer for encoding and decoding the next URI, in order to prevent tampering with the next URI.

This commit is contained in:
2023-05-23 08:24:12 +08:00
parent 822c8fc49b
commit 818c357613
14 changed files with 426 additions and 251 deletions

View File

@ -22,11 +22,12 @@ from urllib.parse import quote_plus
import httpx
from flask import Flask, request
from itsdangerous import URLSafeSerializer
from accounting.utils.next_uri import append_next, inherit_next, or_next
from accounting.utils.pagination import Pagination, DEFAULT_PAGE_SIZE
from accounting.utils.query import parse_query_keywords
from testlib import TEST_SERVER, create_test_app, get_csrf_token
from testlib import TEST_SERVER, create_test_app, get_csrf_token, NEXT_URI
class NextUriTestCase(unittest.TestCase):
@ -40,6 +41,8 @@ class NextUriTestCase(unittest.TestCase):
:return: None.
"""
self.app: Flask = create_test_app()
self.serializer: URLSafeSerializer \
= URLSafeSerializer(self.app.config["SECRET_KEY"])
def test_next_uri(self) -> None:
"""Tests the next URI utilities with the next URI.
@ -51,12 +54,12 @@ class NextUriTestCase(unittest.TestCase):
current_uri: str = request.full_path if request.query_string \
else request.path
self.assertEqual(append_next(self.TARGET),
f"{self.TARGET}?next={quote_plus(current_uri)}")
f"{self.TARGET}?next={self.__encode(current_uri)}")
next_uri: str = request.form["next"] if request.method == "POST" \
else request.args["next"]
self.assertEqual(inherit_next(self.TARGET),
f"{self.TARGET}?next={quote_plus(next_uri)}")
self.assertEqual(or_next(self.TARGET), next_uri)
f"{self.TARGET}?next={next_uri}")
self.assertEqual(or_next(self.TARGET), self.__decode(next_uri))
return ""
self.app.add_url_rule("/test-next", view_func=test_next_uri_view,
@ -66,10 +69,11 @@ class NextUriTestCase(unittest.TestCase):
csrf_token: str = get_csrf_token(client)
response: httpx.Response
response = client.get("/test-next?next=/next&q=abc&page-no=4")
encoded_uri: str = self.__encode(NEXT_URI)
response = client.get(f"/test-next?next={encoded_uri}&q=abc&page-no=4")
self.assertEqual(response.status_code, 200)
response = client.post("/test-next", data={"csrf_token": csrf_token,
"next": "/next",
"next": encoded_uri,
"name": "viewer"})
self.assertEqual(response.status_code, 200)
@ -80,10 +84,6 @@ class NextUriTestCase(unittest.TestCase):
"""
def test_no_next_uri_view() -> str:
"""The test view without the next URI."""
current_uri: str = request.full_path if request.query_string \
else request.path
self.assertEqual(append_next(self.TARGET),
f"{self.TARGET}?next={quote_plus(current_uri)}")
self.assertEqual(inherit_next(self.TARGET), self.TARGET)
self.assertEqual(or_next(self.TARGET), self.TARGET)
return ""
@ -108,10 +108,8 @@ class NextUriTestCase(unittest.TestCase):
"""
def test_invalid_next_uri_view() -> str:
"""The test view without the next URI."""
self.assertEqual(inherit_next(self.TARGET),
request.args.get("inherit-expected"))
self.assertEqual(or_next(self.TARGET),
request.args.get("or-expected"))
self.assertEqual(inherit_next(self.TARGET), self.TARGET)
self.assertEqual(or_next(self.TARGET), self.TARGET)
return ""
self.app.add_url_rule("/test-invalid-next",
@ -127,19 +125,29 @@ class NextUriTestCase(unittest.TestCase):
# A foreign URI
next_uri = "https://example.com"
expected1 = self.TARGET
expected2 = self.TARGET
response = client.get(f"/test-invalid-next?next={quote_plus(next_uri)}"
f"&inherit-expected={quote_plus(expected1)}"
f"&or-expected={quote_plus(expected2)}")
response = client.get(f"/test-invalid-next?next={quote_plus(next_uri)}")
self.assertEqual(response.status_code, 200)
response = client.post("/test-invalid-next"
f"?inherit-expected={quote_plus(expected1)}"
f"&or-expected={quote_plus(expected2)}",
response = client.post("/test-invalid-next",
data={"csrf_token": csrf_token,
"next": next_uri})
self.assertEqual(response.status_code, 200)
def __encode(self, uri: str) -> str:
"""Encodes the next URI.
:param uri: The next URI.
:return: The encoded next URI.
"""
return self.serializer.dumps(uri, "next")
def __decode(self, uri: str) -> str:
"""Decodes the next URI.
:param uri: The encoded next URI.
:return: The next URI.
"""
return self.serializer.loads(uri, "next")
class QueryKeywordParserTestCase(unittest.TestCase):
"""The test case for the query keyword parser."""