|
1 | 1 | # Copyright (c) 2025 LINE Corporation |
2 | 2 | # These sources are released under the terms of the MIT license: see LICENSE |
| 3 | +from django.contrib.auth.models import User |
| 4 | +from django.db.models import Q |
3 | 5 | from django.utils.itercompat import is_iterable |
| 6 | +from guardian.shortcuts import get_objects_for_user |
| 7 | +from rest_framework import permissions |
4 | 8 | from rest_framework.permissions import BasePermission |
5 | 9 |
|
| 10 | +from promgen import models |
| 11 | + |
6 | 12 |
|
7 | 13 | class PromgenModelPermissions(BasePermission): |
8 | 14 | """ |
@@ -41,3 +47,153 @@ def has_permission(self, request, view): |
41 | 47 | return any(request.user.has_perm(perm) for perm in perm_list) |
42 | 48 | else: |
43 | 49 | return all(request.user.has_perm(perm) for perm in perm_list) |
| 50 | + |
| 51 | + |
| 52 | +class ReadOnlyForAuthenticatedUserOrIsSuperuser(BasePermission): |
| 53 | + """ |
| 54 | + Customize Django REST Framework's base permission class to only allow read-only access for |
| 55 | + authenticated users and full access for superusers. |
| 56 | + """ |
| 57 | + |
| 58 | + def has_permission(self, request, view): |
| 59 | + if request.user.is_superuser: |
| 60 | + return True |
| 61 | + return bool( |
| 62 | + request.user |
| 63 | + and request.user.is_authenticated |
| 64 | + and request.method in permissions.SAFE_METHODS |
| 65 | + ) |
| 66 | + |
| 67 | + |
| 68 | +def get_check_permission_objects(obj): |
| 69 | + # Because we only define permission codes for Service, Group, and Project, |
| 70 | + # we need to map other objects to these. |
| 71 | + if isinstance(obj, (models.Service, models.Group)): |
| 72 | + return [obj] |
| 73 | + if isinstance(obj, models.Project): |
| 74 | + return [obj, obj.service] |
| 75 | + if isinstance(obj, (models.Exporter, models.URL, models.Farm)): |
| 76 | + return [obj.project, obj.project.service] |
| 77 | + if isinstance(obj, models.Host): |
| 78 | + return [obj.farm.project, obj.farm.project.service] |
| 79 | + if isinstance(obj, (models.Rule, models.Sender)): |
| 80 | + content_obj = getattr(obj, "content_object", None) |
| 81 | + if isinstance(content_obj, models.Project): |
| 82 | + return [content_obj, content_obj.service] |
| 83 | + elif content_obj is not None: |
| 84 | + return [content_obj] |
| 85 | + return None |
| 86 | + |
| 87 | + |
| 88 | +def has_perm(user: User, perms: list[str], obj) -> bool: |
| 89 | + # Superusers always have permission |
| 90 | + if user.is_active and user.is_superuser: |
| 91 | + return True |
| 92 | + |
| 93 | + check_permission_objects = get_check_permission_objects(obj) |
| 94 | + if not check_permission_objects: |
| 95 | + return False |
| 96 | + |
| 97 | + for check_obj in check_permission_objects: |
| 98 | + # If the check_obj is the user itself, return True. |
| 99 | + # Otherwise, check permissions. |
| 100 | + # This also returns True if the user belongs to a Group that has the permission. |
| 101 | + has_permission = user == check_obj or any(user.has_perm(perm, check_obj) for perm in perms) |
| 102 | + if has_permission: |
| 103 | + return True |
| 104 | + return False |
| 105 | + |
| 106 | + |
| 107 | +def get_objects_for_user_with_perms(user: User, perms: list[str], klass): |
| 108 | + """ |
| 109 | + Wrapper around guardian.shortcuts.get_objects_for_user to get objects |
| 110 | + for a user with specific permissions. |
| 111 | +
|
| 112 | + Some important parameters are set according to Promgen's permission model: |
| 113 | + - any_perm=True: Return objects that match any of the specified permissions. |
| 114 | + - use_groups=True: Consider permissions assigned via both user and group of users. |
| 115 | + - accept_global_perms=False: Do not consider global permissions for objects. |
| 116 | +
|
| 117 | + Args: |
| 118 | + user (User): The user for whom to retrieve objects. |
| 119 | + perms (list[str]): List of permission codenames to check. |
| 120 | + klass (Model, optional): The model class to filter objects. |
| 121 | +
|
| 122 | + Returns: |
| 123 | + QuerySet: A queryset of objects the user has the specified permissions for. |
| 124 | +
|
| 125 | + """ |
| 126 | + return get_objects_for_user( |
| 127 | + user, |
| 128 | + perms, |
| 129 | + any_perm=True, |
| 130 | + use_groups=True, |
| 131 | + accept_global_perms=False, |
| 132 | + klass=klass, |
| 133 | + ) |
| 134 | + |
| 135 | + |
| 136 | +def get_accessible_services_for_user(user: User): |
| 137 | + return get_objects_for_user_with_perms( |
| 138 | + user, ["service_admin", "service_editor", "service_viewer"], klass=models.Service |
| 139 | + ) |
| 140 | + |
| 141 | + |
| 142 | +def get_accessible_projects_for_user(user: User): |
| 143 | + services = get_accessible_services_for_user(user) |
| 144 | + projects = get_objects_for_user_with_perms( |
| 145 | + user, ["project_admin", "project_editor", "project_viewer"], klass=models.Project |
| 146 | + ) |
| 147 | + return models.Project.objects.filter(Q(pk__in=projects) | Q(service__in=services)) |
| 148 | + |
| 149 | + |
| 150 | +def get_accessible_groups_for_user(user: User): |
| 151 | + return get_objects_for_user_with_perms( |
| 152 | + user, ["group_admin", "group_member"], klass=models.Group |
| 153 | + ) |
| 154 | + |
| 155 | + |
| 156 | +def get_editable_services_for_user(user: User): |
| 157 | + return get_objects_for_user_with_perms( |
| 158 | + user, ["service_admin", "service_editor"], klass=models.Service |
| 159 | + ) |
| 160 | + |
| 161 | + |
| 162 | +def get_editable_projects_for_user(user: User): |
| 163 | + services = get_editable_services_for_user(user) |
| 164 | + projects = get_objects_for_user_with_perms( |
| 165 | + user, ["project_admin", "project_editor"], klass=models.Project |
| 166 | + ) |
| 167 | + return models.Project.objects.filter(Q(pk__in=projects) | Q(service__in=services)) |
| 168 | + |
| 169 | + |
| 170 | +def get_highest_role(user: User, obj): |
| 171 | + """ |
| 172 | + Determine the highest role a user has for a given object. |
| 173 | +
|
| 174 | + Roles are determined based on the following hierarchy: |
| 175 | + - ADMIN: service_admin, project_admin, group_admin |
| 176 | + - EDIT: service_editor, project_editor |
| 177 | + - VIEW: service_viewer, project_viewer, group_member |
| 178 | + """ |
| 179 | + |
| 180 | + # Superusers always have ADMIN permission |
| 181 | + if user.is_active and user.is_superuser: |
| 182 | + return "ADMIN" |
| 183 | + |
| 184 | + check_permission_objects = get_check_permission_objects(obj) |
| 185 | + if not check_permission_objects: |
| 186 | + return None |
| 187 | + |
| 188 | + role_perms = [ |
| 189 | + ("ADMIN", ["service_admin", "project_admin", "group_admin"]), |
| 190 | + ("EDIT", ["service_editor", "project_editor"]), |
| 191 | + ("VIEW", ["service_viewer", "project_viewer", "group_member"]), |
| 192 | + ] |
| 193 | + |
| 194 | + for role, perms in role_perms: |
| 195 | + for check_obj in check_permission_objects: |
| 196 | + if any(user.has_perm(perm, check_obj) for perm in perms): |
| 197 | + return role |
| 198 | + |
| 199 | + return None |
0 commit comments