diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ec57599..121c64d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -60,7 +60,7 @@ jobs: if: steps.changed-models-or-api.outputs.any_changed == 'true' run: | echo "${{ secrets.SERVICE_ACCOUNT_JSON }}" > /tmp/edutap-wallet-google-credentials.json - echo "EDUTAP_WALLET_GOOGLE_ISSUER_ID=${{ secrets.ISSUER_ID }}" > .env + echo "EDUTAP_WALLET_GOOGLE_TEST_ISSUER_ID=${{ secrets.ISSUER_ID }}" > .env echo "EDUTAP_WALLET_GOOGLE_CREDENTIALS_FILE=/tmp/edutap-wallet-google-credentials.json" >> .env echo "EDUTAP_WALLET_GOOGLE_INTEGRATION_TEST_PREFIX=${{ github.run_id }}" >> .env - name: Test with tox diff --git a/src/edutap/wallet_google/api.py b/src/edutap/wallet_google/api.py index 9d6292b..21e1815 100644 --- a/src/edutap/wallet_google/api.py +++ b/src/edutap/wallet_google/api.py @@ -290,13 +290,9 @@ def listing( raise ValueError("resource_id of a class must be given to list its objects") params["classId"] = resource_id elif name.endswith("Class"): - is_pageable = True if not issuer_id: - issuer_id = session_manager.settings.issuer_id - if not issuer_id: - raise ValueError( - "'issuer_id' must be passed as keyword argument or set in environment" - ) + raise ValueError("issuer_id must be given to list classes") + is_pageable = True params["issuerId"] = issuer_id if is_pageable: diff --git a/src/edutap/wallet_google/handlers/fastapi.py b/src/edutap/wallet_google/handlers/fastapi.py index b91d854..8945859 100644 --- a/src/edutap/wallet_google/handlers/fastapi.py +++ b/src/edutap/wallet_google/handlers/fastapi.py @@ -145,7 +145,6 @@ async def handle_image(request: Request, encrypted_image_id: str): # needs to be included after the routers are defined router = APIRouter( prefix=session_manager.settings.handler_prefix, - tags=["edutap", "google_wallet"], ) router.include_router(router_callback) router.include_router(router_images) diff --git a/src/edutap/wallet_google/handlers/validate.py b/src/edutap/wallet_google/handlers/validate.py index b7d039b..231745e 100644 --- a/src/edutap/wallet_google/handlers/validate.py +++ b/src/edutap/wallet_google/handlers/validate.py @@ -138,11 +138,18 @@ def verified_signed_message(data: CallbackData) -> SignedMessage: Verifies the signature of the callback data. and returns the parsed SignedMessage """ + # parse message + message = SignedMessage.model_validate_json(data.signedMessage) + # get issuer_id + if not message.classId or "." not in message.classId: + raise ValueError("Missing classId") + issuer_id = message.classId.split(".")[0] + + # shortcut if signature validation is disabled settings = session_manager.settings if settings.handler_callback_verify_signature == "0": - # shortcut if signature validation is disabled - return SignedMessage.model_validate_json(data.signedMessage) + return message if data.protocolVersion != PROTOCOL_VERSION: raise ValueError("Invalid protocolVersion") @@ -166,7 +173,7 @@ def verified_signed_message(data: CallbackData) -> SignedMessage: signature = base64.decodebytes(bytes(data.signature, "utf-8")) signed_data = _construct_signed_data( "GooglePayWallet", - settings.issuer_id, + issuer_id, PROTOCOL_VERSION, data.signedMessage, ) @@ -175,4 +182,4 @@ def verified_signed_message(data: CallbackData) -> SignedMessage: except (ValueError, InvalidSignature): raise ValueError("Invalid signature") - return SignedMessage.model_validate_json(data.signedMessage) + return message diff --git a/src/edutap/wallet_google/settings.py b/src/edutap/wallet_google/settings.py index ee384b7..3046fb9 100644 --- a/src/edutap/wallet_google/settings.py +++ b/src/edutap/wallet_google/settings.py @@ -1,5 +1,4 @@ from pathlib import Path -from pydantic import EmailStr from pydantic import Field from pydantic import HttpUrl from pydantic_settings import BaseSettings @@ -47,8 +46,7 @@ class Settings(BaseSettings): credentials_file: Path = ROOT_DIR / "tests" / "data" / "credentials_fake.json" credentials_scopes: list[str] = SCOPES - issuer_account_email: EmailStr | None = None - issuer_id: str = Field(default="") + test_issuer_id: str = Field(default="") fernet_encryption_key: str = "" diff --git a/tests/conftest.py b/tests/conftest.py index a42ca07..628afbe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -99,3 +99,10 @@ def mock_settings(): @pytest.fixture def mock_fernet_encryption_key(mock_settings): mock_settings.fernet_encryption_key = "TDTPJVv24gha-jRX0apPgPpMDN2wX1kVSNNZdWXcz8E=" + + +@pytest.fixture +def test_issuer_id(): + from edutap.wallet_google.session import session_manager + + yield session_manager.settings.test_issuer_id diff --git a/tests/data/test_wallet_google_plugins/plugins.py b/tests/data/test_wallet_google_plugins/plugins.py index 812415b..3e71029 100644 --- a/tests/data/test_wallet_google_plugins/plugins.py +++ b/tests/data/test_wallet_google_plugins/plugins.py @@ -24,6 +24,8 @@ async def image_by_id(self, image_id: str) -> ImageData: class TestCallbackHandler: """ Implementation of edutap.wallet_google.protocols.CallbackHandler + + Used in tests to simulate a callback handler and possible errors. """ async def handle( @@ -35,8 +37,8 @@ async def handle( count: int, nonce: str, ) -> None: - if class_id == "TIMEOUT": + if class_id.startswith("TIMEOUT"): await asyncio.sleep(exp_time_millis / 1000) - elif class_id: + elif nonce: return - raise ValueError("class_id is required") + raise ValueError("test case errors if nonce is 0") diff --git a/tests/test_api_save_link.py b/tests/test_api_save_link.py index 9b05b80..ca185c0 100644 --- a/tests/test_api_save_link.py +++ b/tests/test_api_save_link.py @@ -68,7 +68,6 @@ def test_create_claims(): def test_api_save_link(mock_settings): from edutap.wallet_google.settings import ROOT_DIR - mock_settings.issuer_id = "1234567890123456789" mock_settings.credentials_file = ( ROOT_DIR / "tests" / "data" / "credentials_fake.json" ) @@ -78,15 +77,21 @@ def test_api_save_link(mock_settings): link = api.save_link( [ api.new( - "Reference", {"id": "test-1.edutap.eu", "model_name": "GenericObject"} + "Reference", + { + "id": "1234567890123456789.test-1.edutap.eu", + "model_name": "GenericObject", + }, ), api.new( "OfferObject", - {"id": "test-2.edutap.eu", "classId": "test-class-1.edutap.eu"}, + { + "id": "1234567890123456789.test-2.edutap.eu", + "classId": "1234567890123456789.test-class-1.edutap.eu", + }, ), ] ) - assert ( - link - == "https://pay.google.com/gp/v/save/eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3OCJ9.eyJpc3MiOiAiZWR1dGFwLXRlc3QtZXhhbXBsZUBzb2RpdW0tcmF5LTEyMzQ1Ni5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsICJhdWQiOiAiZ29vZ2xlIiwgInR5cCI6ICJzYXZldHRvd2FsbGV0IiwgImlhdCI6ICIxNzM3MTE4NDQwIiwgInBheWxvYWQiOiB7Im9mZmVyT2JqZWN0cyI6IFt7ImlkIjogInRlc3QtMi5lZHV0YXAuZXUiLCAiY2xhc3NJZCI6ICJ0ZXN0LWNsYXNzLTEuZWR1dGFwLmV1IiwgInN0YXRlIjogIlNUQVRFX1VOU1BFQ0lGSUVEIiwgImhhc0xpbmtlZERldmljZSI6IGZhbHNlLCAiZGlzYWJsZUV4cGlyYXRpb25Ob3RpZmljYXRpb24iOiBmYWxzZSwgIm5vdGlmeVByZWZlcmVuY2UiOiAiTk9USUZJQ0FUSU9OX1NFVFRJTkdTX0ZPUl9VUERBVEVTX1VOU1BFQ0lGSUVEIn1dLCAiZ2VuZXJpY09iamVjdHMiOiBbeyJpZCI6ICJ0ZXN0LTEuZWR1dGFwLmV1In1dfSwgIm9yaWdpbnMiOiBbXX0.X5nE4Zh4kvxGZ4LxnEw8_9i2tCq-JJUmSCRxpf8ckQJVNumsA_uQze0uyuCPTBSVsdHcnMxozFdiCktrwIdvBYDjSLf91acoADMtY1n-HIPbCexlpTpHgyDpyC8giVx27lAvkjaDtHzvEoJ1nAkwh-_-322uKwEWOllR_voV162lNLMIE5QY2eDJ9yfcFa5LXqxSW64UDTZSYX0DsJtGf-Oa1HOgnG77D6RZfMYUq18duNmcBp5wREVrE27vPjnVRC2kEsrWfQNw4gkN_aSp6ZhEAG-exQqUQQXeR7nPqYm-DM7uVOCh8pbk8WqwA7fKcsZIRK8dsWnqnDuNdwP7Gw" - ) + + expected = "https://pay.google.com/gp/v/save/eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3OCJ9.eyJpc3MiOiAiZWR1dGFwLXRlc3QtZXhhbXBsZUBzb2RpdW0tcmF5LTEyMzQ1Ni5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsICJhdWQiOiAiZ29vZ2xlIiwgInR5cCI6ICJzYXZldHRvd2FsbGV0IiwgImlhdCI6ICIxNzM3MTE4NDQwIiwgInBheWxvYWQiOiB7Im9mZmVyT2JqZWN0cyI6IFt7ImlkIjogIjEyMzQ1Njc4OTAxMjM0NTY3ODkudGVzdC0yLmVkdXRhcC5ldSIsICJjbGFzc0lkIjogIjEyMzQ1Njc4OTAxMjM0NTY3ODkudGVzdC1jbGFzcy0xLmVkdXRhcC5ldSIsICJzdGF0ZSI6ICJTVEFURV9VTlNQRUNJRklFRCIsICJoYXNMaW5rZWREZXZpY2UiOiBmYWxzZSwgImRpc2FibGVFeHBpcmF0aW9uTm90aWZpY2F0aW9uIjogZmFsc2UsICJub3RpZnlQcmVmZXJlbmNlIjogIk5PVElGSUNBVElPTl9TRVRUSU5HU19GT1JfVVBEQVRFU19VTlNQRUNJRklFRCJ9XSwgImdlbmVyaWNPYmplY3RzIjogW3siaWQiOiAiMTIzNDU2Nzg5MDEyMzQ1Njc4OS50ZXN0LTEuZWR1dGFwLmV1In1dfSwgIm9yaWdpbnMiOiBbXX0.BtXrnlnl63ukrz9WdkRBqGUEsoBwc9HzrwyQsLTZf9iKzT9LTVy3tXL_UCkpEDhOg1buMM8hEfbnXCmyh2-W1F0dSgTqt7ZZVrGlTYoHFquSNL-vqGu0-7A1x3NXTJeSRF5u28f8Sb8n174XvOy826trRA8yCJb_4HWB0Obs0LRzY5yOo_e47CIVJf0bxU9P6w9XnSlGDSWenihMc_wxNQI2pWDXSABNdW9MEQnw6AN1eEnU8Du1zqJD-j4u8tULzv2oquEMeVOHWli_D5Ob1qMrxf_R-d-wtCxfFma_nIbnZGqmU3LerQU-PqrrfsPQhcoukHOIhNdfPqk4a5KtPQ" + assert link == expected diff --git a/tests/test_handler_fastapi.py b/tests/test_handler_fastapi.py index 1454860..ba89b1d 100644 --- a/tests/test_handler_fastapi.py +++ b/tests/test_handler_fastapi.py @@ -43,7 +43,7 @@ def test_callback_disabled_signature_check_ERROR(mock_settings): mock_settings.handler_callback_verify_signature = "0" callback_data = CallbackData(**real_callback_data) - callback_data.signedMessage = '{"classId":"","objectId":"","eventType":"save","expTimeMillis":1734366082269,"count":1,"nonce":""}' + callback_data.signedMessage = '{"classId":"1.x","objectId":"1.y","eventType":"save","expTimeMillis":1734366082269,"count":1,"nonce":""}' from edutap.wallet_google.handlers.fastapi import router @@ -73,7 +73,7 @@ def raise_not_implemented(): ) callback_data = CallbackData(**real_callback_data) - callback_data.signedMessage = '{"classId":"","objectId":"","eventType":"save","expTimeMillis":1734366082269,"count":1,"nonce":""}' + callback_data.signedMessage = '{"classId":"1.x","objectId":"1.y","eventType":"save","expTimeMillis":1734366082269,"count":1,"nonce":"abcde"}' from edutap.wallet_google.handlers.fastapi import router @@ -96,7 +96,7 @@ def test_callback_disabled_signature_check_TIMEOUT(mock_settings): mock_settings.handlers_callback_timeout = 0.1 callback_data = CallbackData(**real_callback_data) - callback_data.signedMessage = '{"classId":"TIMEOUT","objectId":"","eventType":"save","expTimeMillis":250,"count":1,"nonce":""}' + callback_data.signedMessage = '{"classId":"TIMEOUT.x","objectId":"1.x","eventType":"save","expTimeMillis":250,"count":1,"nonce":"abcde"}' from edutap.wallet_google.handlers.fastapi import router diff --git a/tests/test_settings.py b/tests/test_settings.py index 51da492..4c56bd9 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -19,7 +19,7 @@ def test_base_settings(): def test_local_settings(monkeypatch): monkeypatch.setenv( - "EDUTAP_WALLET_GOOGLE_ISSUER_ID", + "EDUTAP_WALLET_GOOGLE_TEST_ISSUER_ID", "1234567890123456789", ) monkeypatch.setenv( @@ -29,7 +29,7 @@ def test_local_settings(monkeypatch): settings = Settings() - assert settings.issuer_id == "1234567890123456789" + assert settings.test_issuer_id == "1234567890123456789" assert settings.credentials_file.exists()