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
98 changes: 70 additions & 28 deletions eap/permissions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.contrib.auth.models import Permission
from rest_framework.permissions import BasePermission

from api.models import Country
from eap.models import EAPRegistration
from eap.utils import get_admin_ids


def has_country_permission(
Expand All @@ -12,15 +12,7 @@ def has_country_permission(
if user.is_superuser or user.has_perm("api.ifrc_admin"):
return True

country_admin_ids = [
int(codename.replace("country_admin_", ""))
for codename in Permission.objects.filter(
group__user=user,
codename__startswith="country_admin_",
).values_list("codename", flat=True)
]

return national_society_id in country_admin_ids
return national_society_id in get_admin_ids(user, "country_admin_")


def has_regional_permission(
Expand All @@ -30,22 +22,23 @@ def has_regional_permission(
if user.is_superuser or user.has_perm("api.ifrc_admin"):
return True

regional_admin_ids = [
int(codename.replace("region_admin_", ""))
for codename in Permission.objects.filter(
group__user=user,
codename__startswith="region_admin_",
).values_list("codename", flat=True)
]
return region_id in get_admin_ids(user, "region_admin_")


return region_id in regional_admin_ids
def has_creator_or_shared_permission(
user,
eap_registration: EAPRegistration,
) -> bool:
# NOTE: The creator and the users the EAP is shared with can update it, same as in Dref.
return eap_registration.created_by_id == user.id or eap_registration.users.filter(id=user.id).exists()


class EAPRegistrationPermissions(BasePermission):
message = "You need to be country admin or IFRC admin or superuser to create/update EAP Registration"

def has_permission(self, request, view) -> bool:
if request.method not in ["PUT", "PATCH", "POST"]:
# NOTE: Updates are checked per object instead, so shared users are allowed through.
if request.method != "POST":
return True

user = request.user
Expand All @@ -63,30 +56,65 @@ def has_permission(self, request, view) -> bool:
)
)


class EAPBasePermission(BasePermission):
message = "You don't have permission to create/update EAP"

def has_object_permission(self, request, view, obj) -> bool:
if request.method not in ["PUT", "PATCH", "POST"]:
return True

user = request.user
eap_reg_id = request.data.get("eap_registration", None) or obj.eap_registration_id
eap_registration = EAPRegistration.objects.filter(id=eap_reg_id).first()

assert eap_registration is not None, "EAP Registration does not exist"
national_society_id = eap_registration.national_society_id
return (
user.is_superuser
or has_creator_or_shared_permission(user=user, eap_registration=obj)
or has_country_permission(user=user, national_society_id=obj.national_society_id)
or has_regional_permission(
user=user,
region_id=obj.national_society.region.pk,
)
)


class EAPBasePermission(BasePermission):
message = "You don't have permission to create/update EAP"

def can_write(self, user, eap_reg_id) -> bool:
eap_registration = EAPRegistration.objects.filter(id=eap_reg_id).first()
if not eap_registration:
return False

return (
user.is_superuser
or has_country_permission(user=user, national_society_id=national_society_id)
or has_creator_or_shared_permission(user=user, eap_registration=eap_registration)
or has_country_permission(user=user, national_society_id=eap_registration.national_society_id)
or has_regional_permission(
user=user,
region_id=eap_registration.national_society.region.pk,
)
)

def has_permission(self, request, view) -> bool:
# NOTE: has_object_permission is never called on create, so the EAP Registration in the
# payload has to be checked here.
if request.method != "POST":
return True

return self.can_write(request.user, request.data.get("eap_registration"))

def has_object_permission(self, request, view, obj) -> bool:
if request.method not in ["PUT", "PATCH", "POST"]:
return True

return self.can_write(
request.user,
obj.eap_registration_id,
)


class EAPRevisePermission(EAPBasePermission):
message = "You don't have permission to revise this EAP"

def has_permission(self, request, view) -> bool:
return True


class EAPValidatedBudgetPermission(BasePermission):
message = "You don't have permission to upload validated budget file for this EAP"
Expand All @@ -96,3 +124,17 @@ def has_permission(self, request, view) -> bool:
if user.is_superuser or user.has_perm("api.ifrc_admin"):
return True
return False


def has_submit_permission(
user,
eap_registration: EAPRegistration,
) -> bool:
# NOTE: Submitting for review is open to the same people who can edit the EAP: the
# National Society's admins, its region's admins, the creator and the shared users.
return (
user.is_superuser
or has_creator_or_shared_permission(user=user, eap_registration=eap_registration)
or has_country_permission(user=user, national_society_id=eap_registration.national_society_id)
or has_regional_permission(user=user, region_id=eap_registration.national_society.region_id)
)
11 changes: 8 additions & 3 deletions eap/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
TimeFrame,
YearsTimeFrameChoices,
)
from eap.permissions import has_submit_permission
from eap.tasks import (
generate_eap_summary_pdf,
generate_export_diff_pdf,
Expand All @@ -56,7 +57,6 @@
send_technical_validation_email,
)
from eap.utils import (
has_country_permission,
is_user_ifrc_admin,
validate_file_extention,
validate_file_object,
Expand Down Expand Up @@ -1024,7 +1024,7 @@ def _validate_status(self, validated_data: dict[str, typing.Any]) -> dict[str, t

if (current_status, new_status) not in valid_transitions:
raise serializers.ValidationError(
gettext("EAP status cannot be changed from %s to %s.")
gettext("You cannot change EAP status from %s to %s.")
% (EAPRegistration.Status(current_status).label, EAPRegistration.Status(new_status).label)
)

Expand All @@ -1039,6 +1039,11 @@ def _validate_status(self, validated_data: dict[str, typing.Any]) -> dict[str, t
EAPRegistration.Status.UNDER_DEVELOPMENT,
EAPRegistration.Status.UNDER_REVIEW,
):
if not has_submit_permission(user, self.instance):
raise PermissionDenied(
gettext("You do not have permission to change status to %s.") % EAPRegistration.Status(new_status).label
)

if self.instance.get_eap_type_enum == EAPType.SIMPLIFIED_EAP:
self.instance.latest_simplified_eap.is_locked = True
self.instance.latest_simplified_eap.save(update_fields=["is_locked"])
Expand Down Expand Up @@ -1118,7 +1123,7 @@ def _validate_status(self, validated_data: dict[str, typing.Any]) -> dict[str, t
EAPRegistration.Status.NS_ADDRESSING_COMMENTS,
EAPRegistration.Status.UNDER_REVIEW,
):
if not (has_country_permission(user, self.instance.national_society_id) or is_user_ifrc_admin(user)):
if not has_submit_permission(user, self.instance):
raise PermissionDenied(
gettext("You do not have permission to change status to %s.") % EAPRegistration.Status(new_status).label
)
Expand Down
Loading
Loading