-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added role_requirements.Any\All behaviour to the authorize decorator (#7
) * added role_requirements.Any\All behavior to the authorized decorator * Added some docstrings * formatting * merge tests with parametrize * readme update
- Loading branch information
1 parent
563bb4f
commit 3465ca5
Showing
10 changed files
with
214 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import typing | ||
|
||
from aad_fastapi.roles.role_validator_interface import RoleValidatorInterface | ||
|
||
|
||
class AllRoleValidator(RoleValidatorInterface): | ||
"""Validate that all mandatory roles are present in the user roles""" | ||
|
||
def validate_roles( | ||
self, user_roles: typing.Sequence[str], mandatory_roles: typing.Sequence[str] | ||
) -> bool: | ||
return all(mandatory_role in user_roles for mandatory_role in mandatory_roles) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import typing | ||
|
||
from aad_fastapi.roles.role_validator_interface import RoleValidatorInterface | ||
|
||
|
||
class AnyRoleValidator(RoleValidatorInterface): | ||
"""Validate that at least one mandatory role is present in the user roles""" | ||
|
||
def validate_roles( | ||
self, user_roles: typing.Sequence[str], mandatory_roles: typing.Sequence[str] | ||
) -> bool: | ||
return any(mandatory_role in user_roles for mandatory_role in mandatory_roles) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from enum import Enum | ||
|
||
|
||
class RoleRequirement(Enum): | ||
"""Role requirement enum for authorization.""" | ||
|
||
ALL = "all" | ||
ANY = "any" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import typing | ||
|
||
from aad_fastapi.roles.all_role_validator import AllRoleValidator | ||
from aad_fastapi.roles.any_role_validator import AnyRoleValidator | ||
from aad_fastapi.roles.role_requirement import RoleRequirement | ||
|
||
|
||
class RoleValidator: | ||
"""Role validator class""" | ||
|
||
_validators = { | ||
RoleRequirement.ALL: AllRoleValidator, | ||
RoleRequirement.ANY: AnyRoleValidator, | ||
} | ||
|
||
def __init__( | ||
self, mandatory_roles: typing.List[str], role_requirement: RoleRequirement | ||
): | ||
self.mandatory_roles = mandatory_roles | ||
self.role_requirement = role_requirement | ||
self.validator_class = self._validators.get(role_requirement) | ||
if self.validator_class is None: | ||
raise ValueError(f"Invalid role requirement: {role_requirement}") | ||
|
||
def validate_roles(self, user_roles: typing.List[str]) -> bool: | ||
"""validate the user roles against the mandatory roles""" | ||
validator = self.validator_class() | ||
return validator.validate_roles(user_roles, self.mandatory_roles) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import typing | ||
|
||
|
||
class RoleValidatorInterface: | ||
def validate_roles( | ||
self, user_roles: typing.Sequence[str], mandatory_roles: typing.Sequence[str] | ||
) -> bool: | ||
"""validate the user roles against the mandatory roles""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import json | ||
|
||
import pytest | ||
|
||
from aad_fastapi import AzureAdSettings | ||
from tests.helpers import gen_payload | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"roles, expected_status_code", | ||
[ | ||
(["Admin", "Contributor"], 200), | ||
(["Admin"], 403), | ||
(["Contributor"], 403), | ||
([], 403), | ||
], | ||
) | ||
def test_isauth_with_impersonation_and_all_roles( | ||
mock_test_client, private_key, roles, expected_status_code | ||
): | ||
options = AzureAdSettings() | ||
payload = gen_payload(options, private_key, roles=roles, scp=["user_impersonation"]) | ||
token = json.loads(payload)["access_token"] | ||
response = mock_test_client.get( | ||
"/isauth_impersonation_all_roles", headers={"Authorization": f"Bearer {token}"} | ||
) | ||
assert response.status_code == expected_status_code | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"roles,expected_status_code", | ||
[ | ||
(["Admin", "Contributor"], 200), | ||
(["Admin"], 200), | ||
(["Contributor"], 200), | ||
([], 403), | ||
], | ||
) | ||
def test_valid_access_token_with_any_roles( | ||
mock_test_client, private_key, roles, expected_status_code | ||
): | ||
options = AzureAdSettings() | ||
payload = gen_payload(options, private_key, roles=roles, scp=["user_impersonation"]) | ||
token = json.loads(payload)["access_token"] | ||
response = mock_test_client.get( | ||
"/isauth_impersonation_any_roles", headers={"Authorization": f"Bearer {token}"} | ||
) | ||
assert response.status_code == expected_status_code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import pytest | ||
|
||
from aad_fastapi.roles.all_role_validator import AllRoleValidator | ||
from aad_fastapi.roles.any_role_validator import AnyRoleValidator | ||
from aad_fastapi.roles.role_requirement import RoleRequirement | ||
from aad_fastapi.roles.role_validator import RoleValidator | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"roles, requirement, user_roles, expected", | ||
[ | ||
(["admin", "editor"], RoleRequirement.ALL, ["admin", "editor"], True), | ||
(["admin", "editor"], RoleRequirement.ALL, ["admin"], False), | ||
(["admin", "editor"], RoleRequirement.ANY, ["admin"], True), | ||
(["admin", "editor"], RoleRequirement.ANY, ["guest"], False), | ||
], | ||
) | ||
def test_role_validator(roles, requirement, user_roles, expected): | ||
validator = RoleValidator(roles, requirement) | ||
assert validator.validate_roles(user_roles) == expected | ||
|
||
|
||
def test_role_validator_invalid_role_requirement(): | ||
with pytest.raises(ValueError): | ||
RoleValidator([], "invalid") | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"mandatory_roles, user_roles, expected", | ||
[ | ||
(["admin", "editor"], ["admin", "editor"], True), | ||
(["admin", "editor"], ["admin"], False), | ||
], | ||
) | ||
def test_all_role_validator(mandatory_roles, user_roles, expected): | ||
validator = AllRoleValidator() | ||
assert validator.validate_roles(user_roles, mandatory_roles) == expected | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"mandatory_roles, user_roles, expected", | ||
[ | ||
(["admin", "editor"], ["admin"], True), | ||
(["admin", "editor"], ["guest"], False), | ||
], | ||
) | ||
def test_any_role_validator(mandatory_roles, user_roles, expected): | ||
validator = AnyRoleValidator() | ||
assert validator.validate_roles(user_roles, mandatory_roles) == expected |