Skip to content
Merged
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
4 changes: 4 additions & 0 deletions bfabric_asgi_auth/src/bfabric_asgi_auth/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ async def _handle_landing(self, scope: Scope, receive: ASGIReceiveCallable, send
bfabric_instance=result.token_data.caller,
bfabric_auth_login=result.token_data.user,
bfabric_auth_password=result.token_data.user_ws_password.get_secret_value(),
entity_class=result.token_data.entity_class,
entity_id=result.token_data.entity_id,
job_id=result.token_data.job_id,
application_id=result.token_data.application_id,
)

# Store session data by modifying scope["session"] directly, this is supported by starlette's SessionMiddleware
Expand Down
8 changes: 8 additions & 0 deletions bfabric_asgi_auth/src/bfabric_asgi_auth/session_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ class SessionData(BaseModel):
:ivar bfabric_instance: Bfabric instance URL
:ivar bfabric_auth_login: Bfabric authentication login
:ivar bfabric_auth_password: Bfabric authentication password
:ivar entity_class: Target entity class name (e.g. "Workunit")
:ivar entity_id: Target entity ID
:ivar job_id: ID of the associated job
:ivar application_id: ID of the B-Fabric application
"""

bfabric_instance: str
bfabric_auth_login: str
bfabric_auth_password: str
entity_class: str
entity_id: int
job_id: int
application_id: int
16 changes: 16 additions & 0 deletions bfabric_asgi_auth/src/bfabric_asgi_auth/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ def login(self) -> str:
def instance(self) -> str:
return self._session_data.bfabric_instance

@property
def entity_class(self) -> str:
return self._session_data.entity_class

@property
def entity_id(self) -> int:
return self._session_data.entity_id

@property
def job_id(self) -> int:
return self._session_data.job_id

@property
def application_id(self) -> int:
return self._session_data.application_id

def get_bfabric_client(self) -> Bfabric:
"""Create a Bfabric client authenticated as this user."""
config = ConfigData(
Expand Down
34 changes: 34 additions & 0 deletions bfabric_asgi_auth/tests/bdd/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ async def user_info(request: Request):
"identity": user.identity,
"login": user.login,
"instance": user.instance,
"entity_class": user.entity_class,
"entity_id": user.entity_id,
"job_id": user.job_id,
"application_id": user.application_id,
}
)

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

# Verify token context fields
assert session_data["entity_class"] == token_data.entity_class
assert session_data["entity_id"] == token_data.entity_id
assert session_data["job_id"] == token_data.job_id
assert session_data["application_id"] == token_data.application_id


@then("the hook should have received token data")
def hook_received_token_data(context):
Expand Down Expand Up @@ -664,3 +674,27 @@ def scope_user_instance(context, instance):
def websocket_scope_user_set(context):
"""Check WebSocket scope has user set."""
assert context["websocket_response"]["has_user"] is True


@then(parsers.parse('the scope user entity_class should be "{value}"'))
def scope_user_entity_class(context, value):
"""Check scope user entity_class."""
assert context["user_info"]["entity_class"] == value


@then(parsers.parse("the scope user entity_id should be {value:d}"))
def scope_user_entity_id(context, value):
"""Check scope user entity_id."""
assert context["user_info"]["entity_id"] == value


@then(parsers.parse("the scope user job_id should be {value:d}"))
def scope_user_job_id(context, value):
"""Check scope user job_id."""
assert context["user_info"]["job_id"] == value


@then(parsers.parse("the scope user application_id should be {value:d}"))
def scope_user_application_id(context, value):
"""Check scope user application_id."""
assert context["user_info"]["application_id"] == value
23 changes: 23 additions & 0 deletions bfabric_asgi_auth/tests/bdd/features/token_context.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Feature: Token context on BfabricUser
As an app developer
I want to access entity_class, entity_id, job_id, and application_id from BfabricUser
So that I don't need to use the on_success hook to capture token context

Background:
Given the application is configured with auth middleware

Scenario: Token context fields are accessible on BfabricUser
Given I am authenticated with token "valid_test123"
When I request the user info endpoint
Then the scope user entity_class should be "Workunit"
And the scope user entity_id should be 2
And the scope user application_id should be 1

Scenario: Token context round-trips through session
Given I am authenticated with token "valid_test123"
When I request the user info endpoint
Then the scope user should be a BfabricUser
And the scope user login should be "test123"
And the scope user entity_class should be "Workunit"
And the scope user entity_id should be 2
And the scope user application_id should be 1
16 changes: 16 additions & 0 deletions bfabric_asgi_auth/tests/unit/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def session_data() -> SessionData:
bfabric_instance="https://fgcz-bfabric.uzh.ch/bfabric/",
bfabric_auth_login="testuser",
bfabric_auth_password="a" * 32,
entity_class="Workunit",
entity_id=42,
job_id=123,
application_id=7,
)


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

def test_entity_class(self, user: BfabricUser) -> None:
assert user.entity_class == "Workunit"

def test_entity_id(self, user: BfabricUser) -> None:
assert user.entity_id == 42

def test_job_id(self, user: BfabricUser) -> None:
assert user.job_id == 123

def test_application_id(self, user: BfabricUser) -> None:
assert user.application_id == 7

def test_get_bfabric_client(self, user: BfabricUser) -> None:
client = user.get_bfabric_client()
assert isinstance(client, Bfabric)
Expand Down
Loading