Skip to content

Commit

Permalink
[192988] Fix permissions (#3722)
Browse files Browse the repository at this point in the history
* Fix permissions

* Fix tests
  • Loading branch information
patryk-dabrowski authored Mar 27, 2024
1 parent 623dfb1 commit 1d5c21b
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 25 deletions.
34 changes: 16 additions & 18 deletions backend/hct_mis_api/apps/account/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,14 @@ def from_list(cls, data: List[BusinessAreaPartnerPermission]) -> "PartnerPermiss
return instance

def set_roles(self, business_area_id: str, roles: List[str]) -> None:
business_area_id = str(business_area_id)
permissions = self._permissions.get(business_area_id, BusinessAreaPartnerPermission(business_area_id))
permissions.roles = roles
self._permissions[business_area_id] = permissions

def set_program_areas(self, business_area_id: str, program_id: str, areas_ids: List[str]) -> None:
business_area_id = str(business_area_id)
program_id = str(program_id)
permissions = self._permissions.get(business_area_id, BusinessAreaPartnerPermission(business_area_id))
permissions.programs[program_id] = areas_ids
self._permissions[business_area_id] = permissions
Expand Down Expand Up @@ -379,30 +382,25 @@ def cached_has_user_roles_for_business_area_and_permission(
def has_permission(
self, permission: str, business_area: BusinessArea, program_id: Optional[UUID] = None, write: bool = False
) -> bool:
has_program_access = True
has_partner_roles = False

if not self.partner.is_unicef:
if program_id:
has_program_access = str(program_id) in self.get_partner_programs_areas_dict(
business_area_id=business_area.pk
)
has_partner_roles = self.cached_has_partner_roles_for_business_area_and_permission(
business_area=business_area,
permission=permission,
)

has_user_roles = self.cached_has_user_roles_for_business_area_and_permission(
business_area=business_area,
permission=permission,
)

if self.partner.is_unicef:
return has_program_access and (
has_user_roles or permission in DEFAULT_PERMISSIONS_LIST_FOR_IS_UNICEF_PARTNER
)
else:
return has_program_access and (has_user_roles or has_partner_roles)
return has_user_roles

has_partner_roles = self.cached_has_partner_roles_for_business_area_and_permission(
business_area=business_area,
permission=permission,
)
has_role_access = has_user_roles or has_partner_roles

if not program_id:
return has_role_access

has_program_access = str(program_id) in self.get_partner_programs_areas_dict(business_area_id=business_area.pk)
return has_program_access and has_role_access

def get_partner_areas_ids_per_program(self, program_id: UUID, business_area_id: UUID) -> List:
partner_areas_ids_per_program = self.get_partner_programs_areas_dict(business_area_id=business_area_id).get(
Expand Down
4 changes: 1 addition & 3 deletions backend/hct_mis_api/apps/account/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,7 @@ def check_permissions(user: Any, permissions: Iterable[Permissions], **kwargs: A
program_id = get_program_id_from_headers(kwargs)
# is_unicef has access to all Programs
if user.partner.is_unicef:
return any(perm in DEFAULT_PERMISSIONS_IS_UNICEF_PARTNER for perm in permissions) or any(
user.has_permission(permission.name, business_area, program_id) for permission in permissions
)
return any(user.has_permission(permission.name, business_area) for permission in permissions)
else:
if not compare_program_id_with_url(user, business_area, business_area_arg, kwargs.get("Referer")):
return False
Expand Down
123 changes: 123 additions & 0 deletions backend/hct_mis_api/apps/account/tests/test_check_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from django.contrib.auth.models import AnonymousUser
from django.test import TestCase

from hct_mis_api.apps.account.fixtures import PartnerFactory, RoleFactory, UserFactory
from hct_mis_api.apps.account.models import PartnerPermission, Role, User, UserRole
from hct_mis_api.apps.account.permissions import Permissions, check_permissions
from hct_mis_api.apps.core.fixtures import create_afghanistan
from hct_mis_api.apps.core.models import BusinessArea
from hct_mis_api.apps.core.utils import encode_id_base64_required
from hct_mis_api.apps.geo.fixtures import AreaFactory
from hct_mis_api.apps.program.fixtures import ProgramFactory
from hct_mis_api.apps.program.models import Program


class TestCheckPermissions(TestCase):
user: User
business_area: BusinessArea
program: Program
role: Role

@classmethod
def setUpTestData(cls) -> None:
cls.user = UserFactory()
cls.business_area = create_afghanistan()
cls.program = ProgramFactory(status=Program.DRAFT, business_area=cls.business_area)
cls.role = RoleFactory(
name="POPULATION VIEW INDIVIDUALS DETAILS", permissions=["POPULATION_VIEW_INDIVIDUALS_DETAILS"]
)
cls.area = AreaFactory(name="POPULATION")

def test_user_is_not_authenticated(self) -> None:
user = AnonymousUser()
self.assertFalse(check_permissions(user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS]))

def test_business_area_is_invalid(self) -> None:
arguments = {"business_area": "invalid"}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertFalse(result)

def test_user_is_unicef_and_has_access_to_business_area(self) -> None:
partner = PartnerFactory(name="UNICEF")
self.user.partner = partner
self.user.save()
UserRole.objects.create(business_area=self.business_area, user=self.user, role=self.role)

arguments = {
"business_area": self.business_area.slug,
"Program": encode_id_base64_required(self.program.id, "Program"),
}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertTrue(result)

def test_user_is_unicef_and_does_not_have_access_to_business_area(self) -> None:
partner = PartnerFactory(name="UNICEF")
self.user.partner = partner
self.user.save()
arguments = {
"business_area": self.business_area.slug,
"Program": encode_id_base64_required(self.program.id, "Program"),
}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertFalse(result)

def test_user_is_not_unicef_and_has_access_to_business_area_without_access_to_program(self) -> None:
partner = PartnerFactory(name="Partner")
self.user.partner = partner
self.user.save()

UserRole.objects.create(business_area=self.business_area, user=self.user, role=self.role)

arguments = {
"business_area": self.business_area.slug,
"Program": encode_id_base64_required(self.program.id, "Program"),
}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertFalse(result)

def test_user_is_not_unicef_and_has_access_to_business_area_and_program(self) -> None:
permissions = PartnerPermission()
permissions.set_program_areas(self.business_area.pk, self.program.pk, [str(self.area.pk)])
partner = PartnerFactory(name="Partner", permissions=permissions.to_dict())
self.user.partner = partner
self.user.save()

UserRole.objects.create(business_area=self.business_area, user=self.user, role=self.role)

arguments = {
"business_area": self.business_area.slug,
"Program": encode_id_base64_required(self.program.id, "Program"),
}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertTrue(result)

def test_user_is_not_unicef_and_dont_have_access_to_business_area_but_has_access_to_business_area_via_partner(
self,
) -> None:
permissions = PartnerPermission()
permissions.set_program_areas(self.business_area.pk, self.program.pk, [str(self.area.pk)])
permissions.set_roles(self.business_area.pk, [str(self.role.pk)])
partner = PartnerFactory(name="Partner", permissions=permissions.to_dict())
self.user.partner = partner
self.user.save()

arguments = {
"business_area": self.business_area.slug,
"Program": encode_id_base64_required(self.program.id, "Program"),
}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertTrue(result)

def test_user_is_not_unicef_and_dont_have_access_to_business_area_at_all(self) -> None:
permissions = PartnerPermission()
permissions.set_program_areas(self.business_area.pk, self.program.pk, [str(self.area.pk)])
partner = PartnerFactory(name="Partner", permissions=permissions.to_dict())
self.user.partner = partner
self.user.save()

arguments = {
"business_area": self.business_area.slug,
"Program": encode_id_base64_required(self.program.id, "Program"),
}
result = check_permissions(self.user, [Permissions.POPULATION_VIEW_INDIVIDUALS_DETAILS], **arguments)
self.assertFalse(result)
3 changes: 3 additions & 0 deletions backend/hct_mis_api/apps/account/tests/test_user_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def setUpTestData(cls) -> None:
# user with UNICEF partner
partner_unicef = PartnerFactory(name="UNICEF")
cls.user_with_unicef_partner = UserFactory(partner=partner_unicef, username="unicef_user")
cls.create_user_role_with_permissions(
cls.user_with_unicef_partner, [Permissions.USER_MANAGEMENT_VIEW_LIST], business_area
)

# user with access to BA
user_in_ba = UserFactory(username="user_in_ba", partner=None)
Expand Down
2 changes: 2 additions & 0 deletions backend/hct_mis_api/apps/grievance/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,8 @@ def resolve_all_grievance_ticket(self, info: Any, **kwargs: Any) -> QuerySet:

def resolve_cross_area_filter_available(self, info: Any, **kwargs: Any) -> bool:
user = info.context.user
if not user.is_authenticated:
return False
business_area = BusinessArea.objects.get(slug=info.context.headers.get("Business-Area"))
program_id = get_program_id_from_headers(info.context.headers)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def setUpTestData(cls) -> None:

def test_cross_area_filter_available_for_unicef_partner(self) -> None:
user = UserFactory(partner=self.partner_unicef)
self.create_user_role_with_permissions(user, [Permissions.GRIEVANCES_CROSS_AREA_FILTER], self.business_area)

self.snapshot_graphql_request(
request_string=CROSS_AREA_FILTER_AVAILABLE_QUERY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from parameterized import parameterized

from hct_mis_api.apps.account.fixtures import PartnerFactory, UserFactory
from hct_mis_api.apps.account.fixtures import PartnerFactory, RoleFactory, UserFactory
from hct_mis_api.apps.account.models import UserRole
from hct_mis_api.apps.account.permissions import Permissions
from hct_mis_api.apps.core.base_test_case import APITestCase
from hct_mis_api.apps.core.fixtures import create_afghanistan
Expand Down Expand Up @@ -39,13 +40,16 @@
class TestCrossAreaFilterAvailable(APITestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.business_area = create_afghanistan()
cls.program = ProgramFactory(business_area=cls.business_area, status=Program.ACTIVE)

partner_unicef = PartnerFactory(name="UNICEF")
cls.user = UserFactory(partner=partner_unicef)
role = RoleFactory(name="GRIEVANCES CROSS AREA FILTER", permissions=["GRIEVANCES_CROSS_AREA_FILTER"])
UserRole.objects.create(business_area=cls.business_area, user=cls.user, role=role)

cls.admin_area1 = AreaFactory(name="Admin Area 1", level=2)
cls.admin_area2 = AreaFactory(name="Admin Area 2", level=2)
cls.business_area = create_afghanistan()
cls.program = ProgramFactory(business_area=cls.business_area, status=Program.ACTIVE)

individual1_from_area1 = IndividualFactory(business_area=cls.business_area, household=None)
individual2_from_area1 = IndividualFactory(business_area=cls.business_area, household=None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,9 @@ def test_household_admin_area_1_is_filtered(self, _: Any, permissions: List[Perm
def test_households_area_filtered_when_partner_is_unicef(self, _: Any) -> None:
partner = PartnerFactory(name="UNICEF")
user = UserFactory(partner=partner)

self.create_user_role_with_permissions(
user, [Permissions.POPULATION_VIEW_HOUSEHOLDS_LIST], self.business_area_afghanistan
)
self.snapshot_graphql_request(
request_string=ALL_HOUSEHOLD_QUERY,
context={
Expand Down

0 comments on commit 1d5c21b

Please sign in to comment.