Skip to content
Open
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
18 changes: 17 additions & 1 deletion api/links/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ class CreateLinkTypeRequestModel(OPModel):
]


def _can_delete_foreign_link(user_permissions, link_type: str) -> bool:
link_permissions = user_permissions.links

if not link_permissions.delete_others:
return False

if not link_permissions.enabled:
return True

return link_type in link_permissions.link_types


@router.get("/projects/{project_name}/links/types")
async def list_link_types(
project_name: ProjectName,
Expand Down Expand Up @@ -332,7 +344,11 @@ async def delete_entity_link(
link_type = res["link_type"]
link_type_name, input_type, output_type = link_type.split("|")

if res["author"] != user.name and not user.is_manager:
if (
res["author"] != user.name
and not user.is_manager
and not _can_delete_foreign_link(user.permissions(project_name), link_type)
):
raise ForbiddenException("You do not have permission to delete this link.")

query = "DELETE FROM links WHERE id = $1"
Expand Down
10 changes: 10 additions & 0 deletions ayon_server/access/access_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ def combine(
| set(value.endpoints)
)

elif perm_name == "links":
result[perm_name]["link_types"] = list(
set(result[perm_name].get("link_types", []))
| set(value.link_types)
)
result[perm_name]["delete_others"] = bool(
result[perm_name].get("delete_others", False)
or value.delete_others
)

if not result:
return Permissions()
return Permissions(**result)
9 changes: 9 additions & 0 deletions ayon_server/access/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ class EntityLinksAccessList(BasePermissionsModel):
default_factory=list,
enum_resolver=_link_types_enum,
)
delete_others: bool = SettingsField(
False,
title="Delete links created by others",
description=(
"Allow users to delete entity links created by other users. "
"When link restrictions are enabled, this only applies to the "
"allowed link types."
),
)


# Model for studio management permissions
Expand Down
74 changes: 74 additions & 0 deletions tests/link_permissions_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), ".."))

from api.links.links import _can_delete_foreign_link
from ayon_server.access.access_groups import AccessGroups
from ayon_server.access.permissions import Permissions


class TestLinkPermissions:
def setup_method(self):
AccessGroups.access_groups = {}

def test_combine_merges_link_delete_permission_and_types(self):
AccessGroups.add_access_group(
"artists",
"_",
Permissions(
links={
"enabled": True,
"link_types": ["reference|folder|version"],
}
),
)
AccessGroups.add_access_group(
"reviewers",
"_",
Permissions(
links={
"enabled": True,
"delete_others": True,
"link_types": ["breakdown|folder|folder"],
}
),
)

permissions = AccessGroups.combine(["artists", "reviewers"])

assert permissions.links.delete_others is True
assert set(permissions.links.link_types) == {
"reference|folder|version",
"breakdown|folder|folder",
}

def test_delete_foreign_link_requires_explicit_permission(self):
permissions = Permissions()

assert (
_can_delete_foreign_link(permissions, "reference|folder|version") is False
)

def test_delete_foreign_link_respects_link_restrictions(self):
permissions = Permissions(
links={
"enabled": True,
"delete_others": True,
"link_types": ["reference|folder|version"],
}
)

assert (
_can_delete_foreign_link(permissions, "reference|folder|version") is True
)
assert _can_delete_foreign_link(permissions, "breakdown|folder|folder") is False

def test_delete_foreign_link_can_apply_to_all_link_types(self):
permissions = Permissions(
links={
"delete_others": True,
}
)

assert _can_delete_foreign_link(permissions, "reference|folder|version") is True