Skip to content

Commit

Permalink
Add support for IVAs (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cito authored Mar 20, 2024
1 parent 6a8e75f commit 83cb295
Show file tree
Hide file tree
Showing 21 changed files with 326 additions and 110 deletions.
6 changes: 3 additions & 3 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ services:
ARS_CONFIG_YAML: /workspace/.devcontainer/.dev_config.yaml

mongodb:
image: mongo:6.0.3
image: mongo:7.0.7
restart: unless-stopped
volumes:
- mongo_fs:/data/db

mongo-express:
image: mongo-express
image: mongo-express:1.0.2
restart: unless-stopped
ports:
- 8088:8081
Expand Down Expand Up @@ -69,7 +69,7 @@ services:
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1

kafka-ui:
image: provectuslabs/kafka-ui
image: provectuslabs/kafka-ui:master
restart: unless-stopped
depends_on:
- kafka
Expand Down
4 changes: 2 additions & 2 deletions .pyproject_generation/pyproject_custom.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ version = "2.0.0"
description = "Access Request Service"
dependencies = [
"ghga-event-schemas~=3.1.0",
"ghga-service-commons[api,auth]>=2.0.0, <3",
"hexkit[mongodb,akafka]>=2.1.0",
"ghga-service-commons[api,auth]>=3.1.1",
"hexkit[mongodb,akafka]>=2.1.1",
"httpx>=0.27.0",
"typer>=0.9.0",
]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ The service requires the following configuration parameters:

- **Items** *(string)*

- **`auth_check_claims`** *(object)*: A dict of all GHGA internal claims that shall be verified. Default: `{"name": null, "email": null, "iat": null, "exp": null}`.
- **`auth_check_claims`** *(object)*: A dict of all GHGA internal claims that shall be verified. Default: `{"id": null, "name": null, "email": null, "iat": null, "exp": null}`.

- **`auth_map_claims`** *(object)*: A mapping of claims to attributes in the GHGA auth context. Can contain additional properties. Default: `{}`.

Expand Down
1 change: 1 addition & 0 deletions config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
},
"auth_check_claims": {
"default": {
"id": null,
"name": null,
"email": null,
"iat": null,
Expand Down
1 change: 1 addition & 0 deletions example_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ auth_check_claims:
email: null
exp: null
iat: null
id: null
name: null
auth_key: '{}'
auth_map_claims: {}
Expand Down
20 changes: 10 additions & 10 deletions lock/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --refresh --generate-hashes --output-file /workspace/lock/requirements-dev.txt /tmp/tmp8rmzyh_v/pyproject.toml /workspace/lock/requirements-dev.in
# uv pip compile --refresh --generate-hashes --output-file /workspace/lock/requirements-dev.txt /tmp/tmpdto5qszn/pyproject.toml /workspace/lock/requirements-dev.in
aiokafka==0.8.1 \
--hash=sha256:1e24839088fd6d3ff481cc09a48ea487b997328df11630bc0a1b88255edbcfe9 \
--hash=sha256:1f43d2afd7d3e4407ada8d754895fad7c344ca00648a8a38418d76564eaaf6cd \
Expand Down Expand Up @@ -323,9 +323,9 @@ exceptiongroup==1.2.0 \
# via
# anyio
# pytest
fastapi==0.109.2 \
--hash=sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d \
--hash=sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73
fastapi==0.110.0 \
--hash=sha256:266775f0dcc95af9d3ef39bad55cff525329a931d5fd51930aadd4f428bf7ff3 \
--hash=sha256:87a1f6fb632a218222c5984be540055346a8f5d8a68e8f6fb647b1dc9934de4b
# via ghga-service-commons
filelock==3.13.1 \
--hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \
Expand All @@ -334,9 +334,9 @@ filelock==3.13.1 \
ghga-event-schemas==3.1.0 \
--hash=sha256:87706784895376314124d30a0ba77dd7cfebdbfbcbb98e88d2a836486f11c385 \
--hash=sha256:fa0048eda36002e7a79bc9084d2acdcc9eb9d38bcf263d6f68ad6fc453cae130
ghga-service-commons==2.0.1 \
--hash=sha256:957c44d8ad006da525c506d815210a701af2dc4ebf0e6473800c00f926f77ce8 \
--hash=sha256:9e7ea822ec692fdc6df93ea62ed65e8dd2bf48886bf1441c96697c0be2101c15
ghga-service-commons==3.1.1 \
--hash=sha256:6f758721a2673ac9c594d9cd052e0204b79950bf9f3e828ca5d870a6637d81b6 \
--hash=sha256:b4e696c350f14a983d9447c43b16a954f0d2bf0a057c3ecdf0a95e3f115c89b4
gprof2dot==2022.7.29 \
--hash=sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5 \
--hash=sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6
Expand Down Expand Up @@ -916,9 +916,9 @@ starlette==0.36.3 \
# via fastapi
stringcase==1.2.0 \
--hash=sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008
testcontainers==4.0.1 \
--hash=sha256:0359c1391124d594caeb96f0adddbf16fd07aeec8cea5bbc00f9c44a140e3b25 \
--hash=sha256:2c91b1fd8fc9901a08054206f1df108cb07685fc30232e6332ee12f292a17ea1
testcontainers==4.1.1 \
--hash=sha256:6c20bde6e040a0fea0ddfaa80492bcde105251b8a59a1325bb1a6fdffe0e62ad \
--hash=sha256:9105b1807cbd0a4e0bfe084c2159754554bc5264ff8173c32f88b0a91e6a5e7c
tomli==2.0.1 \
--hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
--hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
Expand Down
14 changes: 7 additions & 7 deletions lock/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --refresh --generate-hashes --output-file /workspace/lock/requirements.txt /tmp/tmp8rmzyh_v/pyproject.toml -c /workspace/lock/requirements-dev.txt
# uv pip compile --refresh --generate-hashes --output-file /workspace/lock/requirements.txt /tmp/tmpdto5qszn/pyproject.toml -c /workspace/lock/requirements-dev.txt
aiokafka==0.8.1 \
--hash=sha256:1e24839088fd6d3ff481cc09a48ea487b997328df11630bc0a1b88255edbcfe9 \
--hash=sha256:1f43d2afd7d3e4407ada8d754895fad7c344ca00648a8a38418d76564eaaf6cd \
Expand Down Expand Up @@ -162,16 +162,16 @@ exceptiongroup==1.2.0 \
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
--hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
# via anyio
fastapi==0.109.2 \
--hash=sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d \
--hash=sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73
fastapi==0.110.0 \
--hash=sha256:266775f0dcc95af9d3ef39bad55cff525329a931d5fd51930aadd4f428bf7ff3 \
--hash=sha256:87a1f6fb632a218222c5984be540055346a8f5d8a68e8f6fb647b1dc9934de4b
# via ghga-service-commons
ghga-event-schemas==3.1.0 \
--hash=sha256:87706784895376314124d30a0ba77dd7cfebdbfbcbb98e88d2a836486f11c385 \
--hash=sha256:fa0048eda36002e7a79bc9084d2acdcc9eb9d38bcf263d6f68ad6fc453cae130
ghga-service-commons==2.0.1 \
--hash=sha256:957c44d8ad006da525c506d815210a701af2dc4ebf0e6473800c00f926f77ce8 \
--hash=sha256:9e7ea822ec692fdc6df93ea62ed65e8dd2bf48886bf1441c96697c0be2101c15
ghga-service-commons==3.1.1 \
--hash=sha256:6f758721a2673ac9c594d9cd052e0204b79950bf9f3e828ca5d870a6637d81b6 \
--hash=sha256:b4e696c350f14a983d9447c43b16a954f0d2bf0a057c3ecdf0a95e3f115c89b4
h11==0.14.0 \
--hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \
--hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761
Expand Down
24 changes: 24 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ components:
description: The ID of the data steward who made the status change
title: Changed By
dataset_id:
description: ID of the dataset for which access is requested
title: Dataset Id
type: string
email:
Expand All @@ -35,6 +36,13 @@ components:
description: ID of the access request
title: Id
type: string
iva_id:
anyOf:
- type: string
- type: 'null'
description: ID of the IVA to be used for this request, but this can also
be specified later
title: Iva Id
request_created:
description: Creation date of the access request
format: date-time
Expand All @@ -57,6 +65,7 @@ components:
description: Last change date of the status of this request
title: Status Changed
user_id:
description: ID of the user who requests access
title: User Id
type: string
required:
Expand Down Expand Up @@ -86,17 +95,26 @@ components:
title: Access Starts
type: string
dataset_id:
description: ID of the dataset for which access is requested
title: Dataset Id
type: string
email:
description: Contact e-mail address of the requester
title: Email
type: string
iva_id:
anyOf:
- type: string
- type: 'null'
description: ID of the IVA to be used for this request, but this can also
be specified later
title: Iva Id
request_text:
description: Text note submitted with the request
title: Request Text
type: string
user_id:
description: ID of the user who requests access
title: User Id
type: string
required:
Expand All @@ -112,6 +130,12 @@ components:
additionalProperties: false
description: All data that describes an access request patch.
properties:
iva_id:
anyOf:
- type: string
- type: 'null'
description: ID of the IVA to be used for this request
title: Iva Id
status:
allOf:
- $ref: '#/components/schemas/AccessRequestStatus'
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ version = "2.0.0"
description = "Access Request Service"
dependencies = [
"ghga-event-schemas~=3.1.0",
"ghga-service-commons[api,auth]>=2.0.0, <3",
"hexkit[mongodb,akafka]>=2.1.0",
"ghga-service-commons[api,auth]>=3.1.1",
"hexkit[mongodb,akafka]>=2.1.1",
"httpx>=0.27.0",
"typer>=0.9.0",
]
Expand Down
24 changes: 11 additions & 13 deletions src/ars/adapters/inbound/fastapi_/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,42 @@

from fastapi import Depends, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from ghga_service_commons.auth.ghga import AuthContext, has_role, is_active
from ghga_service_commons.auth.ghga import AuthContext, has_role
from ghga_service_commons.auth.policies import require_auth_context_using_credentials

from ars.adapters.inbound.fastapi_ import dummies
from ars.core.roles import DATA_STEWARD_ROLE

__all__ = ["require_user_context", "require_steward_context"]
__all__ = ["UserAuthContext", "StewardAuthContext"]


async def _require_user_context(
async def require_auth_context(
credentials: Annotated[
HTTPAuthorizationCredentials, Depends(HTTPBearer(auto_error=True))
],
auth_provider: dummies.AuthProviderDummy,
) -> AuthContext:
"""Require an active GHGA auth context using FastAPI."""
return await require_auth_context_using_credentials(
credentials, auth_provider, is_active
)
"""Require a GHGA authentication and authorization context."""
return await require_auth_context_using_credentials(credentials, auth_provider)


is_steward = partial(has_role, role=DATA_STEWARD_ROLE)


async def _require_steward_context(
async def require_steward_context(
credentials: Annotated[
HTTPAuthorizationCredentials, Depends(HTTPBearer(auto_error=True))
],
auth_provider: dummies.AuthProviderDummy,
) -> AuthContext:
"""Require an active GHGA auth context of a data steward using FastAPI."""
"""Require a GHGA auth context with data steward role."""
return await require_auth_context_using_credentials(
credentials, auth_provider, is_steward
)


# policy for requiring an active user auth context
require_user_context = Security(_require_user_context)
# policy for requiring and getting an auth context
UserAuthContext = Annotated[AuthContext, Security(require_auth_context)]

# policy for requiring an active data steward auth context
require_steward_context = Security(_require_steward_context)
# policy for requiring and getting an auth context with data steward role
StewardAuthContext = Annotated[AuthContext, Security(require_steward_context)]
18 changes: 7 additions & 11 deletions src/ars/adapters/inbound/fastapi_/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,13 @@
"""Module containing the main FastAPI router and all route functions."""

import logging
from typing import Annotated, Optional
from typing import Optional

from fastapi import APIRouter, Response
from fastapi.exceptions import HTTPException

from ars.adapters.inbound.fastapi_ import dummies
from ars.adapters.inbound.fastapi_.auth import (
AuthContext,
require_steward_context,
require_user_context,
)
from ars.adapters.inbound.fastapi_.auth import StewardAuthContext, UserAuthContext
from ars.core.models import (
AccessRequest,
AccessRequestCreationData,
Expand Down Expand Up @@ -72,7 +68,7 @@ async def health():
async def create_access_request(
creation_data: AccessRequestCreationData,
repository: dummies.AccessRequestRepoDummy,
auth_context: Annotated[AuthContext, require_user_context],
auth_context: UserAuthContext,
) -> str:
"""Create an access request"""
try:
Expand Down Expand Up @@ -109,7 +105,7 @@ async def create_access_request(
)
async def get_access_requests(
repository: dummies.AccessRequestRepoDummy,
auth_context: Annotated[AuthContext, require_user_context],
auth_context: UserAuthContext,
dataset_id: Optional[str] = None,
user_id: Optional[str] = None,
status: Optional[AccessRequestStatus] = None,
Expand Down Expand Up @@ -150,13 +146,13 @@ async def patch_access_request(
access_request_id: str,
patch_data: AccessRequestPatchData,
repository: dummies.AccessRequestRepoDummy,
auth_context: Annotated[AuthContext, require_steward_context],
auth_context: StewardAuthContext,
) -> Response:
"""Set the status of an access request"""
status = patch_data.status
iva_id, status = patch_data.iva_id, patch_data.status
try:
await repository.update(
access_request_id, status=status, auth_context=auth_context
access_request_id, iva_id=iva_id, status=status, auth_context=auth_context
)
except repository.AccessRequestAuthorizationError as exc:
raise HTTPException(status_code=403, detail=str(exc)) from exc
Expand Down
7 changes: 4 additions & 3 deletions src/ars/adapters/outbound/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,16 @@ async def construct(
async with httpx.AsyncClient() as client:
yield cls(config=config, client=client)

async def grant_download_access(
async def grant_download_access( # noqa: PLR0913
self,
user_id: str,
iva_id: str,
dataset_id: str,
valid_from: UTCDatetime,
valid_until: UTCDatetime,
) -> None:
"""Grant download access to a user for a given dataset and duration."""
url = f"{self._url}/users/{user_id}/datasets/{dataset_id}"
"""Grant download access to a given user with an IVA for a given dataset."""
url = f"{self._url}/users/{user_id}/ivas/{iva_id}/datasets/{dataset_id}"
try:
validity = ClaimValidity(valid_from=valid_from, valid_until=valid_until)
except ValueError as error:
Expand Down
15 changes: 13 additions & 2 deletions src/ars/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,15 @@ class AccessRequestStatus(str, Enum):
class AccessRequestCreationData(BaseDto):
"""All data necessary to create an access request."""

user_id: str
dataset_id: str
user_id: str = Field(default=..., description="ID of the user who requests access")
iva_id: Optional[str] = Field(
default=None,
description="ID of the IVA to be used for this request,"
" but this can also be specified later",
)
dataset_id: str = Field(
default=..., description="ID of the dataset for which access is requested"
)
email: str = Field(
default=..., description="Contact e-mail address of the requester"
)
Expand Down Expand Up @@ -91,6 +98,10 @@ class AccessRequestData(AccessRequestCreationData):
class AccessRequestPatchData(BaseDto):
"""All data that describes an access request patch."""

iva_id: Optional[str] = Field(
default=None,
description="ID of the IVA to be used for this request",
)
status: AccessRequestStatus = Field(
default=...,
description="The new status of this access request",
Expand Down
Loading

0 comments on commit 83cb295

Please sign in to comment.