Skip to content

Commit

Permalink
Test that creation of work order tokens is secure (GSI-903) (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cito authored Jul 25, 2024
1 parent 8d91843 commit eec7668
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 767 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ repos:
- id: no-commit-to-branch
args: [--branch, dev, --branch, int, --branch, main]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.1
rev: v0.5.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
rev: v1.11.0
hooks:
- id: mypy
args: [--no-warn-unused-ignores]
4 changes: 2 additions & 2 deletions .pyproject_generation/pyproject_custom.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "wps"
version = "2.0.3"
version = "2.0.4"
description = "Work Package Service"
dependencies = [
"ghga-event-schemas~=3.3.1",
"ghga-service-commons[api,auth,crypt]>=3.1.5",
"hexkit[akafka,mongodb]>=3.3.0",
"hexkit[akafka,mongodb]>=3.4.0",
"httpx>=0.27",
"typer>=0.12",
]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@ We recommend using the provided Docker container.

A pre-build version is available at [docker hub](https://hub.docker.com/repository/docker/ghga/work-package-service):
```bash
docker pull ghga/work-package-service:2.0.3
docker pull ghga/work-package-service:2.0.4
```

Or you can build the container yourself from the [`./Dockerfile`](./Dockerfile):
```bash
# Execute in the repo's root dir:
docker build -t ghga/work-package-service:2.0.3 .
docker build -t ghga/work-package-service:2.0.4 .
```

For production-ready deployment, we recommend using Kubernetes, however,
for simple use cases, you could execute the service using docker
on a single server:
```bash
# The entrypoint is preconfigured:
docker run -p 8080:8080 ghga/work-package-service:2.0.3 --help
docker run -p 8080:8080 ghga/work-package-service:2.0.4 --help
```

If you prefer not to use containers, you may install the service from source:
Expand Down
734 changes: 300 additions & 434 deletions lock/requirements-dev.txt

Large diffs are not rendered by default.

476 changes: 169 additions & 307 deletions lock/requirements.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ components:
info:
description: A service managing work packages for the GHGA CLI
title: Work Package Service
version: 2.0.3
version: 2.0.4
openapi: 3.1.0
paths:
/health:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ classifiers = [
"Intended Audience :: Developers",
]
name = "wps"
version = "2.0.3"
version = "2.0.4"
description = "Work Package Service"
dependencies = [
"ghga-event-schemas~=3.3.1",
"ghga-service-commons[api,auth,crypt]>=3.1.5",
"hexkit[akafka,mongodb]>=3.3.0",
"hexkit[akafka,mongodb]>=3.4.0",
"httpx>=0.27",
"typer>=0.12",
]
Expand Down
1 change: 1 addition & 0 deletions src/wps/core/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion tests/fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"fixture_config",
"fixture_repository",
"fixture_client",
"fixture_consumer",
"headers_for_token",
"non_mocked_hosts",
"Consumer",
Expand Down
48 changes: 33 additions & 15 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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."""
Expand Down
19 changes: 19 additions & 0 deletions tests/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,25 @@ 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:
assert user_id == package.user_id
assert dataset_id == package.dataset_id
return False

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,
Expand Down

0 comments on commit eec7668

Please sign in to comment.