Skip to content

Commit bc4a348

Browse files
authored
Expose token entity/job info through BfabricUser (#485)
1 parent ae52d19 commit bc4a348

6 files changed

Lines changed: 101 additions & 0 deletions

File tree

bfabric_asgi_auth/src/bfabric_asgi_auth/middleware.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ async def _handle_landing(self, scope: Scope, receive: ASGIReceiveCallable, send
136136
bfabric_instance=result.token_data.caller,
137137
bfabric_auth_login=result.token_data.user,
138138
bfabric_auth_password=result.token_data.user_ws_password.get_secret_value(),
139+
entity_class=result.token_data.entity_class,
140+
entity_id=result.token_data.entity_id,
141+
job_id=result.token_data.job_id,
142+
application_id=result.token_data.application_id,
139143
)
140144

141145
# Store session data by modifying scope["session"] directly, this is supported by starlette's SessionMiddleware

bfabric_asgi_auth/src/bfabric_asgi_auth/session_data.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@ class SessionData(BaseModel):
1313
:ivar bfabric_instance: Bfabric instance URL
1414
:ivar bfabric_auth_login: Bfabric authentication login
1515
:ivar bfabric_auth_password: Bfabric authentication password
16+
:ivar entity_class: Target entity class name (e.g. "Workunit")
17+
:ivar entity_id: Target entity ID
18+
:ivar job_id: ID of the associated job
19+
:ivar application_id: ID of the B-Fabric application
1620
"""
1721

1822
bfabric_instance: str
1923
bfabric_auth_login: str
2024
bfabric_auth_password: str
25+
entity_class: str
26+
entity_id: int
27+
job_id: int
28+
application_id: int

bfabric_asgi_auth/src/bfabric_asgi_auth/user.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ def login(self) -> str:
4242
def instance(self) -> str:
4343
return self._session_data.bfabric_instance
4444

45+
@property
46+
def entity_class(self) -> str:
47+
return self._session_data.entity_class
48+
49+
@property
50+
def entity_id(self) -> int:
51+
return self._session_data.entity_id
52+
53+
@property
54+
def job_id(self) -> int:
55+
return self._session_data.job_id
56+
57+
@property
58+
def application_id(self) -> int:
59+
return self._session_data.application_id
60+
4561
def get_bfabric_client(self) -> Bfabric:
4662
"""Create a Bfabric client authenticated as this user."""
4763
config = ConfigData(

bfabric_asgi_auth/tests/bdd/conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ async def user_info(request: Request):
6868
"identity": user.identity,
6969
"login": user.login,
7070
"instance": user.instance,
71+
"entity_class": user.entity_class,
72+
"entity_id": user.entity_id,
73+
"job_id": user.job_id,
74+
"application_id": user.application_id,
7175
}
7276
)
7377

@@ -523,6 +527,12 @@ def session_bfabric_session_has_required_fields(context, client):
523527
# Verify password content matches (not just length)
524528
assert session_data["bfabric_auth_password"] == token_data.user_ws_password.get_secret_value()
525529

530+
# Verify token context fields
531+
assert session_data["entity_class"] == token_data.entity_class
532+
assert session_data["entity_id"] == token_data.entity_id
533+
assert session_data["job_id"] == token_data.job_id
534+
assert session_data["application_id"] == token_data.application_id
535+
526536

527537
@then("the hook should have received token data")
528538
def hook_received_token_data(context):
@@ -664,3 +674,27 @@ def scope_user_instance(context, instance):
664674
def websocket_scope_user_set(context):
665675
"""Check WebSocket scope has user set."""
666676
assert context["websocket_response"]["has_user"] is True
677+
678+
679+
@then(parsers.parse('the scope user entity_class should be "{value}"'))
680+
def scope_user_entity_class(context, value):
681+
"""Check scope user entity_class."""
682+
assert context["user_info"]["entity_class"] == value
683+
684+
685+
@then(parsers.parse("the scope user entity_id should be {value:d}"))
686+
def scope_user_entity_id(context, value):
687+
"""Check scope user entity_id."""
688+
assert context["user_info"]["entity_id"] == value
689+
690+
691+
@then(parsers.parse("the scope user job_id should be {value:d}"))
692+
def scope_user_job_id(context, value):
693+
"""Check scope user job_id."""
694+
assert context["user_info"]["job_id"] == value
695+
696+
697+
@then(parsers.parse("the scope user application_id should be {value:d}"))
698+
def scope_user_application_id(context, value):
699+
"""Check scope user application_id."""
700+
assert context["user_info"]["application_id"] == value
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Feature: Token context on BfabricUser
2+
As an app developer
3+
I want to access entity_class, entity_id, job_id, and application_id from BfabricUser
4+
So that I don't need to use the on_success hook to capture token context
5+
6+
Background:
7+
Given the application is configured with auth middleware
8+
9+
Scenario: Token context fields are accessible on BfabricUser
10+
Given I am authenticated with token "valid_test123"
11+
When I request the user info endpoint
12+
Then the scope user entity_class should be "Workunit"
13+
And the scope user entity_id should be 2
14+
And the scope user application_id should be 1
15+
16+
Scenario: Token context round-trips through session
17+
Given I am authenticated with token "valid_test123"
18+
When I request the user info endpoint
19+
Then the scope user should be a BfabricUser
20+
And the scope user login should be "test123"
21+
And the scope user entity_class should be "Workunit"
22+
And the scope user entity_id should be 2
23+
And the scope user application_id should be 1

bfabric_asgi_auth/tests/unit/test_user.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def session_data() -> SessionData:
1515
bfabric_instance="https://fgcz-bfabric.uzh.ch/bfabric/",
1616
bfabric_auth_login="testuser",
1717
bfabric_auth_password="a" * 32,
18+
entity_class="Workunit",
19+
entity_id=42,
20+
job_id=123,
21+
application_id=7,
1822
)
1923

2024

@@ -39,6 +43,18 @@ def test_display_name(self, user: BfabricUser) -> None:
3943
def test_identity(self, user: BfabricUser) -> None:
4044
assert user.identity == "testuser@https://fgcz-bfabric.uzh.ch/bfabric/"
4145

46+
def test_entity_class(self, user: BfabricUser) -> None:
47+
assert user.entity_class == "Workunit"
48+
49+
def test_entity_id(self, user: BfabricUser) -> None:
50+
assert user.entity_id == 42
51+
52+
def test_job_id(self, user: BfabricUser) -> None:
53+
assert user.job_id == 123
54+
55+
def test_application_id(self, user: BfabricUser) -> None:
56+
assert user.application_id == 7
57+
4258
def test_get_bfabric_client(self, user: BfabricUser) -> None:
4359
client = user.get_bfabric_client()
4460
assert isinstance(client, Bfabric)

0 commit comments

Comments
 (0)