diff --git a/swh/graphql/tests/functional/test_search.py b/swh/graphql/tests/functional/test_search.py --- a/swh/graphql/tests/functional/test_search.py +++ b/swh/graphql/tests/functional/test_search.py @@ -41,7 +41,7 @@ "targetType": "origin", } ], - "pageInfo": {"endCursor": "MQ==", "hasNextPage": True}, + "pageInfo": {"endCursor": "VFJLSkplR1IxSkpFSEROT0hU", "hasNextPage": True}, } } diff --git a/swh/graphql/tests/unit/utils/test_utils.py b/swh/graphql/tests/unit/utils/test_utils.py --- a/swh/graphql/tests/unit/utils/test_utils.py +++ b/swh/graphql/tests/unit/utils/test_utils.py @@ -5,6 +5,8 @@ import datetime +import pytest + from swh.graphql.utils import utils @@ -22,12 +24,22 @@ assert utils.get_encoded_cursor(None) is None assert utils.get_encoded_cursor("testing") == "dGVzdGluZw==" + def test_get_encoded_numeric_cursor(self): + assert utils.get_encoded_cursor("1003") == "VFJLSkplR1IxMDAzSkpFSEROT0hU" + def test_get_decoded_cursor_is_none(self): assert utils.get_decoded_cursor(None) is None def test_get_decoded_cursor(self): assert utils.get_decoded_cursor("dGVzdGluZw==") == "testing" + def test_get_decoded_numeric_cursor(self): + assert utils.get_decoded_cursor("VFJLSkplR1IxMDAzSkpFSEROT0hU") == "1003" + + @pytest.mark.parametrize("cursor", ["FOO", "00", "test"]) + def test_cursor_decode_encode_reverse(self, cursor): + assert cursor == utils.get_decoded_cursor(utils.get_encoded_cursor(cursor)) + def test_get_formatted_date(self): date = datetime.datetime( 2015, 8, 4, 22, 26, 14, 804009, tzinfo=datetime.timezone.utc diff --git a/swh/graphql/utils/utils.py b/swh/graphql/utils/utils.py --- a/swh/graphql/utils/utils.py +++ b/swh/graphql/utils/utils.py @@ -10,6 +10,8 @@ from swh.storage.interface import PagedResult ENCODING = "utf-8" +CURSOR_PREFIX = "TRKJJeGR" +CURSOR_SUFFIX = "JJEHDNOHT" def get_b64_string(source) -> str: @@ -21,13 +23,25 @@ def get_encoded_cursor(cursor: Optional[str]) -> Optional[str]: if cursor is None: return None + if type(cursor) == str and cursor.isnumeric(): + # Add two random strings to make cursor a bit more opaque + # add this only when the cursor is numeric, this check is + # to avoid making cursor unnecessarily longer in other situations + # (eg: in snapshot-branches the branch name is used as the cursor) + cursor = CURSOR_PREFIX + cursor + CURSOR_SUFFIX return get_b64_string(cursor) def get_decoded_cursor(cursor: Optional[str]) -> Optional[str]: if cursor is None: return None - return base64.b64decode(cursor, validate=True).decode() + # strip the added random strings from left and right + return ( + base64.b64decode(cursor, validate=True) + .decode() + .removeprefix(CURSOR_PREFIX) + .removesuffix(CURSOR_SUFFIX) + ) def get_formatted_date(date: datetime) -> str: