-
Notifications
You must be signed in to change notification settings - Fork 80
Restricted access #756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Restricted access #756
Changes from 4 commits
77c6124
54a07fc
e5dc7db
99ccd58
214cabb
e1d8846
7d55533
82f3b2c
ef01070
29cefa3
4697db3
c42b3d3
f76987d
f54f395
d7c2f16
144ff71
6091744
faf0580
6c267c2
c9f7b2c
955de79
2c57264
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,9 +42,9 @@ | |
| from pydantic import BaseModel as PydanticBaseModel | ||
| from timezone_field import TimeZoneField | ||
|
|
||
| from core.permissions import get_permissions_backend | ||
| from core.storage.cache import invalidate_storage_used_cache | ||
| from core.utils.item_title import manage_unique_title as manage_unique_title_utils | ||
| from wopi.conversion.policy import target_extension_for | ||
|
|
||
| logger = getLogger(__name__) | ||
|
|
||
|
|
@@ -1197,9 +1197,7 @@ def nb_accesses(self): | |
| nb_accesses = cache.get(cache_key) | ||
|
|
||
| if nb_accesses is None: | ||
| nb_accesses = ItemAccess.objects.filter( | ||
| item__path__ancestors=self.path, | ||
| ).count() | ||
| nb_accesses = get_permissions_backend().effective_accesses(self).count() | ||
| cache.set(cache_key, nb_accesses) | ||
|
|
||
| return nb_accesses | ||
|
|
@@ -1239,10 +1237,7 @@ def get_role(self, user): | |
| try: | ||
| roles = self.user_roles or [] | ||
| except AttributeError: | ||
| roles = ItemAccess.objects.filter( | ||
| models.Q(user=user) | models.Q(team__in=user.teams), | ||
| item__path__ancestors=self.path, | ||
| ).values_list("role", flat=True) | ||
| roles = get_permissions_backend().roles_for(user, self) | ||
|
|
||
| return RoleChoices.max(*roles) | ||
|
Comment on lines
1272
to
1277
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes but it will not take the advantage of the cache present in |
||
|
|
||
|
|
@@ -1322,93 +1317,8 @@ def computed_link_role(self): | |
| return self.computed_link_definition["link_role"] | ||
|
|
||
| def get_abilities(self, user): | ||
| """ | ||
| Compute and return abilities for a given user on the item. | ||
| """ | ||
| # First get the role based on specific access | ||
| role = self.get_role(user) | ||
| # Characteristics that are based only on specific access | ||
| is_owner = role == RoleChoices.OWNER | ||
| is_deleted = self.ancestors_deleted_at | ||
| is_owner_or_admin = is_owner or role == RoleChoices.ADMIN | ||
|
|
||
| # Compute access roles before adding link roles because we don't | ||
| # want anonymous users to access versions (we wouldn't know from | ||
| # which date to allow them anyway) | ||
| # Anonymous users should also not see item accesses | ||
| has_access_role = bool(role) and not is_deleted | ||
| link_select_options = ( | ||
| LinkReachChoices.get_select_options(**self.ancestors_link_definition) | ||
| if has_access_role | ||
| else {} | ||
| ) | ||
|
|
||
| link_definition = self.computed_link_definition | ||
|
|
||
| link_reach = link_definition["link_reach"] | ||
| if link_reach == LinkReachChoices.PUBLIC or ( | ||
| link_reach == LinkReachChoices.AUTHENTICATED and user.is_authenticated | ||
| ): | ||
| # Set the user role to the highest role between the item role and the link role | ||
| # Needed for a user with an access lower than link_role | ||
| # Needed for a user without access to determine the role he has. | ||
| role = RoleChoices.max(role, link_definition["link_role"]) | ||
| can_get = bool(role) and not is_deleted | ||
| retrieve = can_get or is_owner | ||
| can_manage = is_owner_or_admin and not is_deleted | ||
| can_update = (is_owner_or_admin or role == RoleChoices.EDITOR) and not is_deleted | ||
| can_create_children = can_update and user.is_authenticated | ||
| can_hard_delete = ( | ||
| is_owner | ||
| if self.is_root | ||
| else (is_owner_or_admin or (user.is_authenticated and self.creator == user)) | ||
| ) | ||
| can_destroy = can_hard_delete and not is_deleted | ||
| can_duplicate = ( | ||
| can_get | ||
| and user.is_authenticated | ||
| and self.type == ItemTypeChoices.FILE | ||
| and self.upload_state == ItemUploadStateChoices.READY | ||
| ) | ||
| can_export = can_get and self.type == ItemTypeChoices.FOLDER | ||
| can_convert = ( | ||
| can_update | ||
| and self.type == ItemTypeChoices.FILE | ||
| and self.upload_state | ||
| in ( | ||
| ItemUploadStateChoices.READY, | ||
| ItemUploadStateChoices.ANALYZING, | ||
| ) | ||
| and bool(target_extension_for(self.extension)) | ||
| and bool(settings.WOPI_ONLYOFFICE_CONVERT_JWT_SECRET) | ||
| ) | ||
|
|
||
| return { | ||
| "accesses_manage": can_manage, | ||
| "accesses_view": has_access_role, | ||
| "breadcrumb": can_get, | ||
| "children_list": can_get, | ||
| "children_create": can_create_children, | ||
| "destroy": can_destroy, | ||
| "download": can_get, | ||
| "duplicate": can_duplicate, | ||
| "export": can_export, | ||
| "hard_delete": can_hard_delete, | ||
| "favorite": can_get and user.is_authenticated, | ||
| "link_configuration": can_manage, | ||
| "invite_owner": is_owner and not is_deleted, | ||
| "link_select_options": link_select_options, | ||
| "move": can_manage, | ||
| "restore": is_owner, | ||
| "retrieve": retrieve, | ||
| "tree": can_get, | ||
| "media_auth": can_get, | ||
| "partial_update": can_update, | ||
| "update": can_update, | ||
| "upload_ended": can_update and user.is_authenticated, | ||
| "wopi": can_get, | ||
| "convert": can_convert, | ||
| } | ||
| """Compute and return abilities for a given user on the item.""" | ||
| return get_permissions_backend().abilities(user, self) | ||
|
kernicPanel marked this conversation as resolved.
|
||
|
|
||
| def send_email(self, subject, emails, context=None, language=None): | ||
| """Generate and send email from a template.""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| """Item permissions backend utilities.""" | ||
|
|
||
| from core.permissions.factory import get_permissions_backend | ||
|
|
||
| __all__ = ["get_permissions_backend"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Item permissions backends.""" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| """Permissions Backend base class.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from django.contrib.auth.models import AnonymousUser | ||
| from django.db.models import QuerySet | ||
|
|
||
| from lasuite.drf.models.choices import RoleChoices | ||
|
|
||
| if TYPE_CHECKING: | ||
| from core import models | ||
|
|
||
|
|
||
| class PermissionsBackend(ABC): | ||
| """Abstract base class for item permissions backends.""" | ||
|
|
||
| @abstractmethod | ||
| def effective_accesses(self, item: models.Item) -> QuerySet[models.ItemAccess]: | ||
| """Return the accesses applying to the item, direct or inherited.""" | ||
|
|
||
| @abstractmethod | ||
| def roles_at(self, user: models.User | AnonymousUser, path: str) -> QuerySet[str]: | ||
| """Return the roles the user holds at the given path, direct or inherited.""" | ||
|
|
||
| @abstractmethod | ||
| def abilities(self, user: models.User | AnonymousUser, item: models.Item) -> dict: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would have been nice to move it in the first commit creating the backend since the get_abilities method was already existing. |
||
| """Compute and return abilities for a given user on the item.""" | ||
|
|
||
|
Comment on lines
+28
to
+31
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would have been nice to move it in the first commit creating the backend since they were already existing. |
||
| def roles_for(self, user: models.User | AnonymousUser, item: models.Item) -> QuerySet[str]: | ||
| """Return the roles the user holds on the item, direct or inherited.""" | ||
| return self.roles_at(user, item.path) | ||
|
|
||
| def role_at(self, user: models.User | AnonymousUser, path: str) -> str | None: | ||
| """Return the highest role the user holds at the given path.""" | ||
| return RoleChoices.max(*self.roles_at(user, path)) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
paige?