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
60 changes: 54 additions & 6 deletions tests/unit/authorization/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@

@pytest.fixture(name="dummy_auth_tuple")
def fixture_dummy_auth_tuple() -> AuthTuple:
"""Standard auth tuple for testing."""
"""
Provide a standard AuthTuple used by tests.

Returns:
AuthTuple: A tuple of (user_id, username, is_admin_flag, token) where
`user_id` and `username` are strings, `is_admin_flag` is a boolean, and
`token` is a mock token string.
"""
return ("user_id", "username", False, "mock_token")


Expand All @@ -35,7 +42,17 @@ class TestGetAuthorizationResolvers:

@pytest.fixture
def mock_configuration(self, mocker: MockerFixture) -> MockType:
"""Mock configuration object."""
"""
Create a mock configuration with empty authorization access rules and empty JWT role rules.

Parameters:
mocker (pytest_mock.MockerFixture): Fixture used to create the MagicMock.

Returns:
Mock: A MagicMock whose `authorization_configuration.access_rules` and
`authentication_configuration.jwk_configuration.jwt_configuration.role_rules`
are set to empty lists.
"""
config = mocker.MagicMock()
config.authorization_configuration.access_rules = []
config.authentication_configuration.jwk_configuration.jwt_configuration.role_rules = (
Expand All @@ -45,12 +62,24 @@ def mock_configuration(self, mocker: MockerFixture) -> MockType:

@pytest.fixture
def sample_access_rule(self) -> AccessRule:
"""Sample access rule for testing."""
"""
Create a sample AccessRule with role "test" and the Query action for use in tests.

Returns:
AccessRule: An AccessRule configured with role "test" and actions
containing `Action.QUERY`.
"""
return AccessRule(role="test", actions=[Action.QUERY])

@pytest.fixture
def sample_role_rule(self) -> JwtRoleRule:
"""Sample role rule for testing."""
"""
Create a sample role rule of type JwtRoleRule.

Returns:
JwtRoleRule: Configured with jsonpath "$.test", operator
`JsonPathOperator.EQUALS`, value "test", and roles ["test"].
"""
return JwtRoleRule(
jsonpath="$.test",
operator=JsonPathOperator.EQUALS,
Expand Down Expand Up @@ -170,7 +199,18 @@ class TestPerformAuthorizationCheck:

@pytest.fixture
def mock_resolvers(self, mocker: MockerFixture) -> tuple[MockType, MockType]:
"""Mock role and access resolvers."""
"""
Create paired mock role and access resolvers for tests.

Parameters:
mocker (MockerFixture): Pytest mocker fixture used to create mock objects.

Returns:
tuple: (role_resolver, access_resolver)
- role_resolver: an AsyncMock whose `resolve_roles` returns `{"employee"}`.
- access_resolver: a MagicMock with `check_access` returning
`True` and `get_actions` returning `{Action.QUERY}`.
"""
role_resolver = mocker.AsyncMock()
access_resolver = mocker.MagicMock()
role_resolver.resolve_roles.return_value = {"employee"}
Expand All @@ -191,7 +231,15 @@ async def test_access_denied(
dummy_auth_tuple: AuthTuple,
mock_resolvers: tuple[MockType, MockType],
) -> None:
"""Test HTTPException when access is denied."""
"""
Verify that auth.check raises an HTTP 403 with expected details when access is denied.

Patches the authorization resolvers to deny access, invokes
_perform_authorization_check for Action.ADMIN, and asserts the raised
HTTPException has status 403 and a detail payload containing a
`response` message about lack of permission and a `cause` that mentions
the user is not authorized to access the endpoint.
"""
role_resolver, access_resolver = mock_resolvers
access_resolver.check_access.return_value = False # Override to deny access

Expand Down
98 changes: 88 additions & 10 deletions tests/unit/authorization/test_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,20 @@


def claims_to_token(claims: dict) -> str:
"""Convert JWT claims dictionary to a JSON string token."""
"""Convert JWT claims dictionary to a JSON string token.

Create a JWT-like token by encoding the provided claims into a
base64url JSON payload and wrapping it with placeholder header
and signature.

Parameters:
claims (dict): JWT claims to serialize and encode.

Returns:
token (str): A token string in the form "foo_header.<base64url(JSON
claims)>.foo_signature", where the payload is base64url-encoded without
padding.
"""

string_claims = json.dumps(claims)
b64_encoded_claims = (
Expand All @@ -26,7 +39,18 @@ def claims_to_token(claims: dict) -> str:


def claims_to_auth_tuple(claims: dict) -> AuthTuple:
"""Convert JWT claims dictionary to an auth tuple."""
"""
Builds an AuthTuple from JWT claims for use in tests.

Parameters:
claims (dict): JWT claims to encode into the returned token.

Returns:
AuthTuple: A 4-tuple (username, token_id, expired, jwt_token) where
`username` is the fixed string "user", `token_id` is the fixed string
"token", `expired` is False, and `jwt_token` is the token produced from
`claims`.
"""
return ("user", "token", False, claims_to_token(claims))


Expand All @@ -35,7 +59,16 @@ class TestJwtRolesResolver:

@pytest.fixture
async def employee_role_rule(self) -> JwtRoleRule:
"""Role rule for RedHat employees."""
"""Role rule for RedHat employees.

JwtRoleRule that grants the "employee" role when
`realm_access.roles` contains "redhat:employees".

Returns:
JwtRoleRule: Configured to match `$.realm_access.roles[*]` with a
CONTAINS operator for the value `"redhat:employees"` and map
matches to the `["employee"]` role.
"""
return JwtRoleRule(
jsonpath="$.realm_access.roles[*]",
operator=JsonPathOperator.CONTAINS,
Expand All @@ -47,7 +80,17 @@ async def employee_role_rule(self) -> JwtRoleRule:
async def employee_resolver(
self, employee_role_rule: JwtRoleRule
) -> JwtRolesResolver:
"""JwtRolesResolver with a rule for RedHat employees."""
"""JwtRolesResolver with a rule for RedHat employees.

Create a JwtRolesResolver configured with the provided
employee role rule.

Parameters:
employee_role_rule (JwtRoleRule): Rule used to map JWT claims to the employee role.

Returns:
JwtRolesResolver: Resolver initialized with the given rule.
"""
return JwtRolesResolver([employee_role_rule])

@pytest.fixture
Expand All @@ -70,7 +113,14 @@ async def employee_claims(self) -> dict[str, Any]:

@pytest.fixture
async def non_employee_claims(self) -> dict[str, Any]:
"""JWT claims for a non-RedHat employee."""
"""JWT claims for a non-RedHat employee.

Provide JWT claims representing a non-Red Hat employee.

Returns:
dict: JWT claims where `realm_access.roles` does not include the Red Hat employee role
(e.g., contains `"uma_authorization"` and `"default-roles-example"`).
"""
return {
"exp": 1754489339,
"iat": 1754488439,
Expand Down Expand Up @@ -114,7 +164,13 @@ async def test_negate_operator(

@pytest.fixture
async def email_rule_resolver(self) -> JwtRolesResolver:
"""JwtRolesResolver with a rule for email domain."""
"""JwtRolesResolver with a rule for email domain.

Returns:
JwtRolesResolver: Resolver configured with a single MATCH rule on
`$.email` using the regex with RedHat domain that yields the
`redhat_employee` role.
"""
return JwtRolesResolver(
[
JwtRoleRule(
Expand All @@ -128,7 +184,13 @@ async def email_rule_resolver(self) -> JwtRolesResolver:

@pytest.fixture
async def equals_rule_resolver(self) -> JwtRolesResolver:
"""JwtRolesResolver with a rule for exact email match."""
"""JwtRolesResolver with a rule for exact email match.

Returns:
JwtRolesResolver: Resolver configured with one JwtRoleRule
(jsonpath="$.foo", operator=EQUALS, value=["bar"],
roles=["foobar"]).
"""
return JwtRolesResolver(
[
JwtRoleRule(
Expand All @@ -150,7 +212,12 @@ async def test_resolve_roles_equals_operator(

@pytest.fixture
async def in_rule_resolver(self) -> JwtRolesResolver:
"""JwtRolesResolver with a rule for IN operator."""
"""JwtRolesResolver with a rule for IN operator.

Returns:
JwtRolesResolver: Resolver that maps the JSONPath value ['bar'] or
['baz'] at "$.foo" to the role "in_role".
"""
return JwtRolesResolver(
[
JwtRoleRule(
Expand Down Expand Up @@ -281,12 +348,23 @@ class TestGenericAccessResolver:

@pytest.fixture
def admin_access_rules(self) -> list[AccessRule]:
"""Access rules with admin role for testing."""
"""Access rules with admin role for testing.

Returns:
list[AccessRule]: A list with one AccessRule for role "superuser"
whose actions include Action.ADMIN.
"""
return [AccessRule(role="superuser", actions=[Action.ADMIN])]

@pytest.fixture
def multi_role_access_rules(self) -> list[AccessRule]:
"""Access rules with multiple roles for testing."""
"""Access rules with multiple roles for testing.

Returns:
list[AccessRule]: A list containing two AccessRule instances — the
"user" role allowing `Action.QUERY` and `Action.GET_MODELS`, and
the "moderator" role allowing `Action.FEEDBACK`.
"""
return [
AccessRule(role="user", actions=[Action.QUERY, Action.GET_MODELS]),
AccessRule(role="moderator", actions=[Action.FEEDBACK]),
Expand Down
Loading
Loading