From 0d05c43f90d6122a62409aa73e856dee44cf2dd6 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Fri, 30 Jan 2026 01:21:26 +0000 Subject: [PATCH 1/8] Add new policy --- .../system_tests/services/tiled_config/dls.py | 115 ++++++++++++++++-- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/tests/system_tests/services/tiled_config/dls.py b/tests/system_tests/services/tiled_config/dls.py index d5df5b3db..4b4f24956 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 ( + NO_ACCESS, + ExternalPolicyDecisionPoint, + ResultHolder, +) +from tiled.access_control.scopes import NO_SCOPES +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 | None = "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: + logger.info( + "Node access_blob not modified;" + f" access_blob is identical: {access_blob}" + ) + return (False, node.access_blob) + 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 @@ -52,12 +103,52 @@ def build_input( ): _input["token"] = principal.access_token.get_secret_value() - if ( - access_blob is not None - 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 access_blob is not None and "tags" in access_blob: + if isinstance(access_blob["tags"], list): + blob = self._type_adapter.validate_json(access_blob["tags"][0]) + _input.update(blob.model_dump()) + elif isinstance(access_blob["tags"], int): + _input["session"] = access_blob["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[str]], + ) + if tags is not None: + if tags.result == ["*"]: + return [] + return [AccessBlobFilter(tags=tags.result, user_id=None)] + else: + return NO_ACCESS + + async def allowed_scopes( + self, + node: BaseAdapter, + principal: Principal, + authn_access_tags: AccessTags | None, + authn_scopes: Scopes, + ) -> Scopes: + scopes = await self._get_external_decision( + self._node_scopes, + self.build_input( + principal, + authn_access_tags, + authn_scopes, + getattr(node, "access_blob", None), + ), + ResultHolder[set[str]], + ) + if scopes: + return scopes.result + return NO_SCOPES From 930eaf112f32bea832fe8f7d7134242a95d5ba4e Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Fri, 30 Jan 2026 01:37:38 +0000 Subject: [PATCH 2/8] Update policy --- tests/system_tests/services/opa_config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system_tests/services/opa_config/config.yaml b/tests/system_tests/services/opa_config/config.yaml index 67f073435..dab7cdc5f 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.19 polling: min_delay_seconds: 30 max_delay_seconds: 120 From 36dbd5e2eff0b29fc229013510e81aa04aeba422 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Fri, 30 Jan 2026 01:45:15 +0000 Subject: [PATCH 3/8] Fix minor changes --- tests/system_tests/services/tiled_config/dls.py | 6 +++--- tests/system_tests/test_blueapi_system.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/system_tests/services/tiled_config/dls.py b/tests/system_tests/services/tiled_config/dls.py index 4b4f24956..0028ad10d 100644 --- a/tests/system_tests/services/tiled_config/dls.py +++ b/tests/system_tests/services/tiled_config/dls.py @@ -73,12 +73,12 @@ async def modify_node( authn_scopes: Scopes, access_blob: AccessBlob | None, ) -> tuple[bool, AccessBlob | None]: - if access_blob == node.access_blob: + 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) + 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), @@ -130,7 +130,7 @@ async def filters( return [] return [AccessBlobFilter(tags=tags.result, user_id=None)] else: - return NO_ACCESS + return NO_ACCESS # type: ignore async def allowed_scopes( self, diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index c1b7ecc4c..b60686581 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -582,6 +582,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) From 496f654b133527216a7c797cfdf731e82967c7a6 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:35:58 +0000 Subject: [PATCH 4/8] Update policy --- tests/system_tests/compose.yaml | 2 +- .../services/opa_config/opa_data/data.json | 2 +- tests/system_tests/services/tiled_config/dls.py | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/system_tests/compose.yaml b/tests/system_tests/compose.yaml index a24ce1fe7..eab7513d7 100644 --- a/tests/system_tests/compose.yaml +++ b/tests/system_tests/compose.yaml @@ -40,7 +40,7 @@ services: start_period: 30s tiled: - image: ghcr.io/bluesky/tiled:0.2.3 + image: ghcr.io/zohebshaikh/tiled:0.10.0 network_mode: host environment: - PYTHONPATH=/deploy/ diff --git a/tests/system_tests/services/opa_config/opa_data/data.json b/tests/system_tests/services/opa_config/opa_data/data.json index 1c19ce3fa..165b8241d 100644 --- a/tests/system_tests/services/opa_config/opa_data/data.json +++ b/tests/system_tests/services/opa_config/opa_data/data.json @@ -4,7 +4,7 @@ "beamlines": { "adsim": { "sessions": [ - "1" + 1 ] } }, diff --git a/tests/system_tests/services/tiled_config/dls.py b/tests/system_tests/services/tiled_config/dls.py index 0028ad10d..3e2ea0008 100644 --- a/tests/system_tests/services/tiled_config/dls.py +++ b/tests/system_tests/services/tiled_config/dls.py @@ -3,6 +3,7 @@ from pydantic import BaseModel, HttpUrl, TypeAdapter from tiled.access_control.access_policies import ( + ALL_ACCESS, NO_ACCESS, ExternalPolicyDecisionPoint, ResultHolder, @@ -30,7 +31,7 @@ def __init__( create_node_endpoint: str = "tiled/user_session", allowed_tags_endpoint: str = "tiled/user_sessions", scopes_endpoint: str = "tiled/scopes", - modify_node_endpoint: str | None = "tiled/modify_session", + modify_node_endpoint: str = "tiled/modify_session", empty_access_blob_public: bool = True, provider: str | None = None, ): @@ -62,7 +63,7 @@ async def init_node( ResultHolder[int], ) if decision and decision.result is not None: - return (True, {"tags": decision.result}) + return (True, {"tags": [decision.result]}) raise ValueError("Permission denied not able to add the node") async def modify_node( @@ -104,10 +105,10 @@ def build_input( _input["token"] = principal.access_token.get_secret_value() if access_blob is not None and "tags" in access_blob: - if isinstance(access_blob["tags"], list): + if isinstance(access_blob["tags"][0], str): blob = self._type_adapter.validate_json(access_blob["tags"][0]) _input.update(blob.model_dump()) - elif isinstance(access_blob["tags"], int): + elif isinstance(access_blob["tags"][0], int): _input["session"] = access_blob["tags"] return json.dumps({"input": _input}) @@ -123,12 +124,12 @@ async def filters( tags = await self._get_external_decision( self._user_tags, self.build_input(principal, authn_access_tags, authn_scopes), - ResultHolder[list[str]], + ResultHolder[list[int | str]], ) if tags is not None: if tags.result == ["*"]: - return [] - return [AccessBlobFilter(tags=tags.result, user_id=None)] + return ALL_ACCESS # type: ignore + return [AccessBlobFilter(tags=tags.result, user_id=None)] # type: ignore else: return NO_ACCESS # type: ignore From aa409ef05a154270661566b84b9c40b53ae550e0 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:01:37 +0000 Subject: [PATCH 5/8] make policy easier to read --- .../system_tests/services/tiled_config/dls.py | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/tests/system_tests/services/tiled_config/dls.py b/tests/system_tests/services/tiled_config/dls.py index 3e2ea0008..187cddfb3 100644 --- a/tests/system_tests/services/tiled_config/dls.py +++ b/tests/system_tests/services/tiled_config/dls.py @@ -8,7 +8,6 @@ ExternalPolicyDecisionPoint, ResultHolder, ) -from tiled.access_control.scopes import NO_SCOPES from tiled.adapters.protocols import BaseAdapter from tiled.queries import AccessBlobFilter from tiled.server.schemas import Principal, PrincipalType @@ -104,12 +103,16 @@ def build_input( ): _input["token"] = principal.access_token.get_secret_value() - if access_blob is not None and "tags" in access_blob: - if isinstance(access_blob["tags"][0], str): - blob = self._type_adapter.validate_json(access_blob["tags"][0]) + if ( + access_blob is not None + and "tags" in access_blob + and len(access_blob["tags"]) > 0 + ): + if isinstance(tags := access_blob["tags"][0], str): + blob = self._type_adapter.validate_json(tags) _input.update(blob.model_dump()) - elif isinstance(access_blob["tags"][0], int): - _input["session"] = access_blob["tags"] + elif isinstance(tags, int): + _input["session"] = tags return json.dumps({"input": _input}) @@ -132,24 +135,3 @@ async def filters( return [AccessBlobFilter(tags=tags.result, user_id=None)] # type: ignore else: return NO_ACCESS # type: ignore - - async def allowed_scopes( - self, - node: BaseAdapter, - principal: Principal, - authn_access_tags: AccessTags | None, - authn_scopes: Scopes, - ) -> Scopes: - scopes = await self._get_external_decision( - self._node_scopes, - self.build_input( - principal, - authn_access_tags, - authn_scopes, - getattr(node, "access_blob", None), - ), - ResultHolder[set[str]], - ) - if scopes: - return scopes.result - return NO_SCOPES From 7e274e35aa720f9405a012b419e9fb5b51b98bf9 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:40:25 +0000 Subject: [PATCH 6/8] Update all access to [] --- tests/system_tests/compose.yaml | 2 +- tests/system_tests/services/opa_config/opa_data/data.json | 2 +- tests/system_tests/services/tiled_config/dls.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/system_tests/compose.yaml b/tests/system_tests/compose.yaml index eab7513d7..a24ce1fe7 100644 --- a/tests/system_tests/compose.yaml +++ b/tests/system_tests/compose.yaml @@ -40,7 +40,7 @@ services: start_period: 30s tiled: - image: ghcr.io/zohebshaikh/tiled:0.10.0 + image: ghcr.io/bluesky/tiled:0.2.3 network_mode: host environment: - PYTHONPATH=/deploy/ diff --git a/tests/system_tests/services/opa_config/opa_data/data.json b/tests/system_tests/services/opa_config/opa_data/data.json index 165b8241d..1c19ce3fa 100644 --- a/tests/system_tests/services/opa_config/opa_data/data.json +++ b/tests/system_tests/services/opa_config/opa_data/data.json @@ -4,7 +4,7 @@ "beamlines": { "adsim": { "sessions": [ - 1 + "1" ] } }, diff --git a/tests/system_tests/services/tiled_config/dls.py b/tests/system_tests/services/tiled_config/dls.py index 187cddfb3..34a782239 100644 --- a/tests/system_tests/services/tiled_config/dls.py +++ b/tests/system_tests/services/tiled_config/dls.py @@ -3,7 +3,6 @@ from pydantic import BaseModel, HttpUrl, TypeAdapter from tiled.access_control.access_policies import ( - ALL_ACCESS, NO_ACCESS, ExternalPolicyDecisionPoint, ResultHolder, @@ -131,7 +130,7 @@ async def filters( ) if tags is not None: if tags.result == ["*"]: - return ALL_ACCESS # type: ignore + return [] # ALL_ACCESS return [AccessBlobFilter(tags=tags.result, user_id=None)] # type: ignore else: return NO_ACCESS # type: ignore From df9493a3e7657d59f20a74297ede616bb38c14b0 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:01:51 +0000 Subject: [PATCH 7/8] Update policy --- tests/system_tests/services/opa_config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system_tests/services/opa_config/config.yaml b/tests/system_tests/services/opa_config/config.yaml index dab7cdc5f..162eeeca3 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/zohebshaikh/authz-policy:0.1.19 + resource: ghcr.io/zohebshaikh/authz-policy:0.1.20 polling: min_delay_seconds: 30 max_delay_seconds: 120 From c0bf4704dc112a28f8e9e21afe10a8b904fec740 Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh <26975142+ZohebShaikh@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:31:31 +0000 Subject: [PATCH 8/8] minor tweaks --- tests/system_tests/services/opa_config/config.yaml | 2 +- tests/system_tests/services/tiled_config/dls.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/system_tests/services/opa_config/config.yaml b/tests/system_tests/services/opa_config/config.yaml index 162eeeca3..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/zohebshaikh/authz-policy:0.1.20 + 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 34a782239..ecb7c93ff 100644 --- a/tests/system_tests/services/tiled_config/dls.py +++ b/tests/system_tests/services/tiled_config/dls.py @@ -3,6 +3,7 @@ from pydantic import BaseModel, HttpUrl, TypeAdapter from tiled.access_control.access_policies import ( + ALL_ACCESS, NO_ACCESS, ExternalPolicyDecisionPoint, ResultHolder, @@ -111,7 +112,7 @@ def build_input( blob = self._type_adapter.validate_json(tags) _input.update(blob.model_dump()) elif isinstance(tags, int): - _input["session"] = tags + _input["session"] = str(tags) return json.dumps({"input": _input}) @@ -130,7 +131,7 @@ async def filters( ) if tags is not None: if tags.result == ["*"]: - return [] # ALL_ACCESS + return ALL_ACCESS return [AccessBlobFilter(tags=tags.result, user_id=None)] # type: ignore else: return NO_ACCESS # type: ignore