Skip to content

Commit b5d71b4

Browse files
committed
Add delete_permissions functionality
- Refactor existing integration tests in `test_permissions.py` to improve clarity and structure. - Introduce a new class `TestDeletePermissions` to cover various scenarios for deleting permissions across entities. - Implement unit tests for asynchronous permission deletion in `unit_test_permissions_async.py`, ensuring proper handling of errors and entity types. - Create a new unit test file `unit_test_permissions.py` to test synchronous permission deletion methods. - Ensure comprehensive coverage for edge cases, including invalid entity types and recursive deletion behavior.
1 parent 70e53e6 commit b5d71b4

File tree

9 files changed

+1895
-42
lines changed

9 files changed

+1895
-42
lines changed

synapseclient/api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from .entity_services import (
2626
create_access_requirements_if_none,
2727
delete_entity,
28+
delete_entity_acl,
2829
delete_entity_generated_by,
2930
get_entities_by_md5,
3031
get_entity,
@@ -81,6 +82,7 @@
8182
"put_entity",
8283
"post_entity",
8384
"delete_entity",
85+
"delete_entity_acl",
8486
"get_upload_destination",
8587
"get_upload_destination_location",
8688
"create_access_requirements_if_none",

synapseclient/api/entity_services.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,60 @@ async def main():
291291
)
292292

293293

294+
async def delete_entity_acl(
295+
entity_id: str,
296+
*,
297+
synapse_client: Optional["Synapse"] = None,
298+
) -> None:
299+
"""
300+
Delete the Access Control List (ACL) for a given Entity.
301+
302+
By default, Entities such as FileEntity and Folder inherit their permission from
303+
their containing Project. For such Entities the Project is the Entity's 'benefactor'.
304+
This permission inheritance can be overridden by creating an ACL for the Entity.
305+
When this occurs the Entity becomes its own benefactor and all permission are
306+
determined by its own ACL.
307+
308+
If the ACL of an Entity is deleted, then its benefactor will automatically be set
309+
to its parent's benefactor. The ACL for a Project cannot be deleted.
310+
311+
Note: The caller must be granted ACCESS_TYPE.CHANGE_PERMISSIONS on the Entity to
312+
call this method.
313+
314+
Arguments:
315+
entity_id: The ID of the entity that should have its ACL deleted.
316+
synapse_client: If not passed in and caching was not disabled by
317+
`Synapse.allow_client_caching(False)` this will use the last created
318+
instance from the Synapse class constructor.
319+
320+
Example: Delete the ACL for entity `syn123`:
321+
This will delete the ACL for the entity, making it inherit permissions from
322+
its parent.
323+
324+
```python
325+
import asyncio
326+
from synapseclient import Synapse
327+
from synapseclient.api import delete_entity_acl
328+
329+
syn = Synapse()
330+
syn.login()
331+
332+
async def main():
333+
await delete_entity_acl(entity_id="syn123")
334+
335+
asyncio.run(main())
336+
```
337+
338+
Returns: None
339+
"""
340+
from synapseclient import Synapse
341+
342+
client = Synapse.get_client(synapse_client=synapse_client)
343+
return await client.rest_delete_async(
344+
uri=f"/entity/{entity_id}/acl",
345+
)
346+
347+
294348
async def get_entity_path(
295349
entity_id: str,
296350
*,

synapseclient/client.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2725,7 +2725,7 @@ def getChildren(
27252725
27262726
Arguments:
27272727
parent: An id or an object of a Synapse container or None to retrieve all projects
2728-
includeTypes: Must be a list of entity types (ie. ["folder","file"]) which can be found [here](http://docs.synapse.org/rest/org/sagebionetworks/repo/model/EntityType.html)
2728+
includeTypes: Must be a list of entity types (ie. ["folder","file"]) which can be found [here](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/EntityType.html)
27292729
sortBy: How results should be sorted. Can be NAME, or CREATED_ON
27302730
sortDirection: The direction of the result sort. Can be ASC, or DESC
27312731
@@ -2795,24 +2795,48 @@ def _getBenefactor(
27952795

27962796
return entity
27972797

2798-
def _getACL(self, entity: Union[Entity, str]) -> Dict[str, Union[str, list]]:
2798+
def _getACL(
2799+
self, entity: Union[Entity, str], check_benefactor: bool = True
2800+
) -> Dict[str, Union[str, list]]:
27992801
"""
28002802
Get the effective Access Control Lists (ACL) for a Synapse Entity.
28012803
28022804
Arguments:
28032805
entity: A Synapse Entity or Synapse ID
2806+
check_benefactor: If True (default), check the benefactor for the entity
2807+
to get the ACL. If False, only check the entity itself.
2808+
This is useful for checking the ACL of an entity that has local sharing
2809+
settings, but you want to check the ACL of the entity itself and not
2810+
the benefactor it may inherit from.
28042811
28052812
Returns:
28062813
A dictionary of the Entity's ACL
28072814
"""
28082815
if hasattr(entity, "getACLURI"):
28092816
uri = entity.getACLURI()
28102817
else:
2811-
# Get the ACL from the benefactor (which may be the entity itself)
2812-
benefactor = self._getBenefactor(entity)
2813-
trace.get_current_span().set_attributes({"synapse.id": benefactor["id"]})
2814-
uri = "/entity/%s/acl" % (benefactor["id"])
2815-
return self.restGET(uri)
2818+
if check_benefactor:
2819+
# Get the ACL from the benefactor (which may be the entity itself)
2820+
benefactor = self._getBenefactor(entity)
2821+
trace.get_current_span().set_attributes(
2822+
{"synapse.id": benefactor["id"]}
2823+
)
2824+
uri = "/entity/%s/acl" % (benefactor["id"])
2825+
return self.restGET(uri)
2826+
else:
2827+
synid, _ = utils.get_synid_and_version(entity)
2828+
trace.get_current_span().set_attributes({"synapse.id": synid})
2829+
uri = "/entity/%s/acl" % (synid)
2830+
try:
2831+
return self.restGET(uri)
2832+
except SynapseHTTPError as e:
2833+
if (
2834+
"The requested ACL does not exist. This entity inherits its permissions from:"
2835+
in str(e)
2836+
):
2837+
# If the entity does not have an ACL, return an empty ACL
2838+
return {"resourceAccess": []}
2839+
raise e
28162840

28172841
def _storeACL(
28182842
self, entity: Union[Entity, str], acl: Dict[str, Union[str, list]]
@@ -2889,6 +2913,7 @@ def get_acl(
28892913
self,
28902914
entity: Union[Entity, Evaluation, str, collections.abc.Mapping],
28912915
principal_id: str = None,
2916+
check_benefactor: bool = True,
28922917
) -> typing.List[str]:
28932918
"""
28942919
Get the [ACL](https://rest-docs.synapse.org/rest/org/
@@ -2898,6 +2923,11 @@ def get_acl(
28982923
Arguments:
28992924
entity: An Entity or Synapse ID to lookup
29002925
principal_id: Identifier of a user or group (defaults to PUBLIC users)
2926+
check_benefactor: If True (default), check the benefactor for the entity
2927+
to get the ACL. If False, only check the entity itself.
2928+
This is useful for checking the ACL of an entity that has local sharing
2929+
settings, but you want to check the ACL of the entity itself and not
2930+
the benefactor it may inherit from.
29012931
29022932
Returns:
29032933
An array containing some combination of
@@ -2912,7 +2942,7 @@ def get_acl(
29122942
{"synapse.id": id_of(entity), "synapse.principal_id": principal_id}
29132943
)
29142944

2915-
acl = self._getACL(entity)
2945+
acl = self._getACL(entity=entity, check_benefactor=check_benefactor)
29162946

29172947
team_list = self._find_teams_for_principal(principal_id)
29182948
team_ids = [int(team.id) for team in team_list]

0 commit comments

Comments
 (0)