diff --git a/tests/system_tests/services/opa_config/config.yaml b/tests/system_tests/services/opa_config/config.yaml index 67f073435..16710cc72 100644 --- a/tests/system_tests/services/opa_config/config.yaml +++ b/tests/system_tests/services/opa_config/config.yaml @@ -5,7 +5,7 @@ services: bundles: diamond-policies: service: ghcr - resource: ghcr.io/diamondlightsource/authz-policy:0.0.18 + resource: ghcr.io/zohebshaikh/authz-policy:0.1.21 polling: min_delay_seconds: 30 max_delay_seconds: 120 diff --git a/tests/system_tests/services/tiled_config/dls.py b/tests/system_tests/services/tiled_config/dls.py index d5df5b3db..ecb7c93ff 100644 --- a/tests/system_tests/services/tiled_config/dls.py +++ b/tests/system_tests/services/tiled_config/dls.py @@ -1,9 +1,19 @@ import json +import logging from pydantic import BaseModel, HttpUrl, TypeAdapter -from tiled.access_control.access_policies import ExternalPolicyDecisionPoint +from tiled.access_control.access_policies import ( + ALL_ACCESS, + NO_ACCESS, + ExternalPolicyDecisionPoint, + ResultHolder, +) +from tiled.adapters.protocols import BaseAdapter +from tiled.queries import AccessBlobFilter from tiled.server.schemas import Principal, PrincipalType -from tiled.type_aliases import AccessBlob, AccessTags, Scopes +from tiled.type_aliases import AccessBlob, AccessTags, Filters, Scopes + +logger = logging.getLogger(__name__) class DiamondAccessBlob(BaseModel): @@ -17,10 +27,10 @@ def __init__( self, authorization_provider: HttpUrl, token_audience: str, - create_node_endpoint: str = "session/write_to_beamline_visit", + create_node_endpoint: str = "tiled/user_session", allowed_tags_endpoint: str = "tiled/user_sessions", scopes_endpoint: str = "tiled/scopes", - modify_node_endpoint: str | None = None, + modify_node_endpoint: str = "tiled/modify_session", empty_access_blob_public: bool = True, provider: str | None = None, ): @@ -37,6 +47,47 @@ def __init__( empty_access_blob_public=empty_access_blob_public, ) + async def init_node( + self, + principal: Principal, + authn_access_tags: AccessTags | None, + authn_scopes: Scopes, + access_blob: AccessBlob | None = None, + ) -> tuple[bool, AccessBlob | None]: + if access_blob is None and self._empty_access_blob_public is not None: + return self._empty_access_blob_public, access_blob + decision = await self._get_external_decision( + self._create_node, + self.build_input(principal, authn_access_tags, authn_scopes, access_blob), + ResultHolder[int], + ) + if decision and decision.result is not None: + return (True, {"tags": [decision.result]}) + raise ValueError("Permission denied not able to add the node") + + async def modify_node( + self, + node: BaseAdapter, + principal: Principal, + authn_access_tags: AccessTags | None, + authn_scopes: Scopes, + access_blob: AccessBlob | None, + ) -> tuple[bool, AccessBlob | None]: + if access_blob == node.access_blob: # type: ignore + logger.info( + "Node access_blob not modified;" + f" access_blob is identical: {access_blob}" + ) + return (False, node.access_blob) # type: ignore + decision = await self._get_external_decision( + self._modify_node, + self.build_input(principal, authn_access_tags, authn_scopes, access_blob), + ResultHolder[bool], + ) + if decision: + return (decision.result, access_blob) + raise ValueError("Permission denied not able to add the node") + def build_input( self, principal: Principal, @@ -44,7 +95,7 @@ def build_input( authn_scopes: Scopes, access_blob: AccessBlob | None = None, ) -> str: - _input = {"audience": self._token_audience} + _input: dict[str, str | int] = {"audience": self._token_audience} if ( principal.type is PrincipalType.external @@ -57,7 +108,30 @@ def build_input( and "tags" in access_blob and len(access_blob["tags"]) > 0 ): - blob = self._type_adapter.validate_json(access_blob["tags"][0]) - _input.update(blob.model_dump()) + if isinstance(tags := access_blob["tags"][0], str): + blob = self._type_adapter.validate_json(tags) + _input.update(blob.model_dump()) + elif isinstance(tags, int): + _input["session"] = str(tags) return json.dumps({"input": _input}) + + async def filters( + self, + node: BaseAdapter, + principal: Principal, + authn_access_tags: AccessTags | None, + authn_scopes: Scopes, + scopes: Scopes, + ) -> Filters: + tags = await self._get_external_decision( + self._user_tags, + self.build_input(principal, authn_access_tags, authn_scopes), + ResultHolder[list[int | str]], + ) + if tags is not None: + if tags.result == ["*"]: + return ALL_ACCESS + return [AccessBlobFilter(tags=tags.result, user_id=None)] # type: ignore + else: + return NO_ACCESS # type: ignore diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index a2b7c1782..4c591b171 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -627,6 +627,6 @@ def on_event(event: AnyEvent) -> None: with pytest.raises( BlueskyStreamingError, - match="404: No such entry", + match="403: Access policy rejects the provided access blob.", ): client_with_stomp.run_task(task, on_event)