Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/system_tests/services/opa_config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
88 changes: 81 additions & 7 deletions tests/system_tests/services/tiled_config/dls.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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,
):
Expand All @@ -37,14 +47,55 @@ 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,
authn_access_tags: AccessTags | None,
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
Expand All @@ -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
2 changes: 1 addition & 1 deletion tests/system_tests/test_blueapi_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading