Skip to content

Commit 6de3909

Browse files
authored
Merge pull request #1031 from major/rspeed-2229-rh-identity-request-state
RSPEED-2326: feat(auth): store RH Identity data in request.state
2 parents 47ded02 + 9b6d515 commit 6de3909

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

src/authentication/rh_identity.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,14 @@ def get_username(self) -> str:
125125
return identity["user"]["username"]
126126
return identity["account_number"]
127127

128+
def get_org_id(self) -> str:
129+
"""Extract organization ID from identity data.
130+
131+
Returns:
132+
Organization ID string, or empty string if not present
133+
"""
134+
return self.identity_data["identity"].get("org_id", "")
135+
128136
def has_entitlement(self, service: str) -> bool:
129137
"""Check if user has a specific service entitlement.
130138
@@ -239,6 +247,9 @@ async def __call__(self, request: Request) -> AuthTuple:
239247
# Validate entitlements if configured
240248
rh_identity.validate_entitlements()
241249

250+
# Store identity data in request.state for downstream access
251+
request.state.rh_identity_data = rh_identity
252+
242253
# Extract user data
243254
user_id = rh_identity.get_user_id()
244255
username = rh_identity.get_username()

tests/unit/authentication/test_rh_identity.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def create_auth_header(identity_data: dict) -> str:
9393
return base64.b64encode(json_str.encode("utf-8")).decode("utf-8")
9494

9595

96-
def create_request_with_header(header_value: Optional[str]) -> Request:
96+
def create_request_with_header(header_value: Optional[str]) -> Mock:
9797
"""Helper to create mock Request with x-rh-identity header.
9898
9999
Create a mock FastAPI Request with an `x-rh-identity` header for tests.
@@ -109,6 +109,7 @@ def create_request_with_header(header_value: Optional[str]) -> Request:
109109
"""
110110
request = Mock(spec=Request)
111111
request.headers = {"x-rh-identity": header_value} if header_value else {}
112+
request.state = Mock()
112113
return request
113114

114115

@@ -129,6 +130,27 @@ def test_system_type_extraction(self, system_identity_data: dict) -> None:
129130
assert rh_identity.get_user_id() == "c87dcb4c-8af1-40dd-878e-60c744edddd0"
130131
assert rh_identity.get_username() == "123"
131132

133+
@pytest.mark.parametrize(
134+
"fixture_name", ["user_identity_data", "system_identity_data"]
135+
)
136+
def test_get_org_id(
137+
self, fixture_name: str, request: pytest.FixtureRequest
138+
) -> None:
139+
"""Test org_id extraction for both identity types."""
140+
identity_data = request.getfixturevalue(fixture_name)
141+
rh_identity = RHIdentityData(identity_data)
142+
assert rh_identity.get_org_id() == "321"
143+
144+
def test_get_org_id_missing(self, user_identity_data: dict) -> None:
145+
"""Test org_id returns empty string when not present."""
146+
identity_data = {
147+
**user_identity_data,
148+
"identity": {**user_identity_data["identity"]},
149+
}
150+
identity_data["identity"].pop("org_id", None)
151+
rh_identity = RHIdentityData(identity_data)
152+
assert rh_identity.get_org_id() == ""
153+
132154
@pytest.mark.parametrize(
133155
"service,expected",
134156
[
@@ -309,6 +331,31 @@ async def test_system_authentication_success(
309331
assert skip_check is False
310332
assert token == NO_USER_TOKEN
311333

334+
@pytest.mark.asyncio
335+
@pytest.mark.parametrize(
336+
"fixture_name,expected_user_id",
337+
[
338+
("user_identity_data", "abc123"),
339+
("system_identity_data", "c87dcb4c-8af1-40dd-878e-60c744edddd0"),
340+
],
341+
)
342+
async def test_rh_identity_stored_in_request_state(
343+
self, fixture_name: str, expected_user_id: str, request: pytest.FixtureRequest
344+
) -> None:
345+
"""Test RH Identity data is stored in request.state for downstream access."""
346+
identity_data = request.getfixturevalue(fixture_name)
347+
auth_dep = RHIdentityAuthDependency()
348+
header_value = create_auth_header(identity_data)
349+
mock_request = create_request_with_header(header_value)
350+
351+
await auth_dep(mock_request)
352+
353+
assert hasattr(mock_request.state, "rh_identity_data")
354+
rh_identity = mock_request.state.rh_identity_data
355+
assert isinstance(rh_identity, RHIdentityData)
356+
assert rh_identity.get_user_id() == expected_user_id
357+
assert rh_identity.get_org_id() == "321"
358+
312359
@pytest.mark.asyncio
313360
async def test_missing_header(self) -> None:
314361
"""Test authentication fails when header is missing."""

0 commit comments

Comments
 (0)