diff --git a/src/wps/core/repository.py b/src/wps/core/repository.py index 7ec5dfd..c6f9737 100644 --- a/src/wps/core/repository.py +++ b/src/wps/core/repository.py @@ -127,7 +127,7 @@ async def create( if work_type == WorkType.DOWNLOAD: if not await self._access.check_download_access(user_id, dataset_id): access_error = self.WorkPackageAccessError( - "Missing dataset access permission" + "Dataset access permission has not been granted" ) log.error(access_error, extra=extra) raise access_error @@ -257,6 +257,7 @@ async def work_order_token( - if check_valid is set and the work package has expired - if a work_package_access_token is specified and it does not match the token hash that is stored in the work package + - if the access permission has been revoked """ extra = { # only used for logging "work_package_id": work_package_id, @@ -277,6 +278,22 @@ async def work_order_token( log.error(access_error, extra=extra) raise access_error + if work_package.type == WorkType.DOWNLOAD: + # since the work package access token is long-lived, + # we double-check that the user still has access + if not await self._access.check_download_access( + work_package.user_id, work_package.dataset_id + ): + access_error = self.WorkPackageAccessError( + "Dataset access permission has been revoked" + ) + log.error(access_error, extra=extra) + raise access_error + else: + access_error = self.WorkPackageAccessError("Unsupported work type") + log.error(access_error, extra=extra) + raise access_error + user_public_crypt4gh_key = work_package.user_public_crypt4gh_key wot = WorkOrderToken( type=work_package.type, diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index 5af64ba..d0ad54e 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -47,7 +47,6 @@ "fixture_config", "fixture_repository", "fixture_client", - "fixture_consumer", "headers_for_token", "non_mocked_hosts", "Consumer", diff --git a/tests/test_api.py b/tests/test_api.py index 8af6559..8bc75c2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -82,7 +82,7 @@ async def test_create_work_order_token( mongodb_populated: MongoDbFixture, ): """Test that work order tokens can be properly created.""" - # mock the access check for the test dataset + # mock the access check for the test dataset to grant access httpx_mock.add_response( method="GET", @@ -186,29 +186,29 @@ async def test_create_work_order_token( ) assert response.status_code == status.HTTP_201_CREATED - token = response.json() - assert isinstance(token, str) + wot = response.json() + assert isinstance(wot, str) # decrypt the work order token - assert token and isinstance(token, str) and token.isascii() - token = decrypt(token) + assert wot and isinstance(wot, str) and wot.isascii() + wot = decrypt(wot) # validate the work order token - assert isinstance(token, str) - assert len(token) > 80 - assert token.count(".") == 2 - token_chars = token.replace(".", "").replace("-", "").replace("_", "") - assert token_chars.isalnum() - assert token_chars.isascii() - token_dict = decode_and_validate_token(token, SIGNING_KEY_PAIR.public()) + assert isinstance(wot, str) + assert len(wot) > 80 + assert wot.count(".") == 2 + wot_chars = wot.replace(".", "").replace("-", "").replace("_", "") + assert wot_chars.isalnum() + assert wot_chars.isascii() + wot_dict = decode_and_validate_token(wot, SIGNING_KEY_PAIR.public()) # check the content of the work order token - assert isinstance(token_dict, dict) - assert token_dict.pop("exp") - token_dict.pop("iat") == 30 - assert token_dict == { + assert isinstance(wot_dict, dict) + assert wot_dict.pop("exp") - wot_dict.pop("iat") == 30 + assert wot_dict == { "type": "download", "file_id": "file-id-3", "user_id": "john-doe@ghga.de", @@ -217,6 +217,24 @@ async def test_create_work_order_token( "email": "john@home.org", } + # mock the access check for the test dataset to revoke access + + httpx_mock.reset(assert_all_responses_were_requested=True) + httpx_mock.add_response( + method="GET", + url="http://access/users/john-doe@ghga.de/datasets/some-dataset-id", + text="false", + ) + + # try to fetch a work order token again + + response = await client.post( + f"/work-packages/{work_package_id}/files/file-id-3/work-order-tokens", + headers=headers_for_token(token), + ) + assert response.status_code == status.HTTP_403_FORBIDDEN + assert "Access has been revoked" in response.json()["detail"] + async def test_get_datasets_unauthenticated(client: AsyncTestClient): """Test that the list of accessible datasets cannot be fetched unauthenticated.""" diff --git a/tests/test_repository.py b/tests/test_repository.py index d920aa3..0e9991b 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -201,6 +201,23 @@ async def test_work_package_and_token_creation( "email": package.email, } + # revoke access and check that work order token cannot be created any more + async def check_download_access_patched(user_id: str, dataset_id: str) -> bool: + return (user_id, dataset_id) != (package.user_id, package.dataset_id) + + access = repository._access + _check_download_access_original = access.check_download_access + try: + access.check_download_access = check_download_access_patched # type: ignore + with pytest.raises(repository.WorkPackageAccessError): + await repository.work_order_token( + work_package_id=work_package_id, + file_id="file-id-1", + work_package_access_token=wpat, + ) + finally: + access.check_download_access = _check_download_access_original # type: ignore + async def test_checking_accessible_datasets( repository: WorkPackageRepository,