Open exalate-issue-sync[bot] opened 1 year ago
Hi, I'm currently trying out oCIS and in the long run I would like to migrate from Nextcloud + NC Groupfolders. The changes described above look like an equivalent for NC Groupfolder ACL permissions and this is what I'm currently missing for oCIS.
It would be great to see this implemented and if I can help in some way I would like to try and do so.
IMHO the question about the "conflict" between share permissions and space permissions described in the second image above is important. In NC this issue causes a lot of confusion as users can see the same content of a directory once as share and once inside the groupfolder (=space).
Would it be possible to prevent the conflict in the first place and do a check for overlapping shares and space permission for each affected user whenever shares/permissions are changed?
Another thing to consider is the use-case where we have a space with ("many") subdirectories and each subdirectory should be visible only to certain stakeholders. Here is an example:
Groups:
Space:
Space "Meeting 2023" # should be visible to all groups
├─ Subdirectory "Shared" # should be writeable to all groups
├─ Subdirectory "Organisation" # should only be visible for group "Meeting Organisation Team"
├─ Subdirectory "Welcome" # should only be visible for groups "Meeting Welcome Team" and
│ # "Meeting Organisation Team"
└─ Subdirectory "Finance" # should only be visible for groups "Meeting Finance Team" and
# "Meeting Organisation Team"
Given the conditions outlined in the first image above, I think the permissions would need to look like this:
Space "Meeting 2023"
│ * view-only permissions for "Meeting Team"
├─ Subdirectory "Shared"
│ * write permissions for "Meeting Team"
├─ Subdirectory "Organisation"
│ * deny all permissions for "Meeting Team"
│ * write permissions for "Meeting Organisation Team"
├─ Subdirectory "Welcome"
│ * deny all permissions for "Meeting Team"
│ * write permissions for "Meeting Organisation Team"
│ * write permissions for "Meeting Welcome Team"
└─ Subdirectory "Finance"
* deny all permissions for "Meeting Team"
* write permissions for "Meeting Organisation Team"
* write permissions for "Meeting Finance Team"
For hiding subdirectories you need to use the parent group "Meeting Team" to first deny all permissions and then increase permissions for certain subgroups.
When scaling up to n
subdirectories (each accessible only to one group) with m
groups, we would get n * 2
permission rules.
If there is no group hierarchy available it looks different. Given groups like this:
We would get:
Space "Meeting 2023"
│ * view-only permissions for "Welcome Team"
│ * write permissions for "Organisation Team"
│ * view-only permissions for "Finance Team"
├─ Subdirectory "Shared"
│ * write permissions for "Welcome Team"
│ * write permissions for "Finance Team"
│ * (inherited) write permissions for "Organisation Team"
├─ Subdirectory "Organisation"
│ * deny all permissions for "Welcome Team"
│ * deny all permissions for "Finance Team"
│ * (inherited) write permissions for "Organisation Team"
├─ Subdirectory "Welcome"
│ * deny all permissions for "Finance Team"
│ * write permissions for "Welcome Team"
│ * (inherited) write permissions for "Organisation Team"
└─ Subdirectory "Finance"
* deny all permissions for "Welcome Team"
* write permissions for "Finance Team"
* (inherited) write permissions for "Organisation Team"
Note that "deny all" permissions are needed for each subdirectory and each group that has access to the space but not to the specific subdirectory. When scaling up to n
subdirectories (each accessible only to one group) with m
groups, we would get n * (m-1)
permission rules.
To simplify this and make the rules more intuitive we could use something like an implicit "view-tree-only" permission. This means that someone that has access to a subdirectory of a space would automatically get the permission to see the directory tree above this subdirectory up until the root of the space (but none of the siblings on any level).
Space "Meeting 2023"
│ * write permissions for "Organisation Team"
│ * (implicit) view-tree-only permission for "Welcome Team"
│ * (implicit) view-tree-only permission for "Finance Team"
├─ Subdirectory "Shared"
│ * write permissions for "Welcome Team"
│ * write permissions for "Finance Team"
├─ Subdirectory "Organisation"
│ * (inherited) write permissions for "Organisation Team"
├─ Subdirectory "Welcome"
│ * write permissions for "Welcome Team"
│ * (inherited) write permissions for "Organisation Team"
└─ Subdirectory "Finance"
* write permissions for "Finance Team"
* (inherited) write permissions for "Organisation Team"
I think this could greatly reduce the amount of explicit permission rules that are required and therefore make the whole thing much more simple to handle.
Sorry for the lengthy post and I hope this makes some sense at all to someone. ;)
libregraph API spec for the sharing ADR in https://github.com/owncloud/ocis/pull/6995
When following a share lifecycle, the following OCS requests can be replaced with these libregraph counterparts:
OCS: web calls two endpoints:
/ocs/v1.php/apps/files_sharing/api/v1/shares?path={space-relative-path}&space_ref={space-id}&reshares=true
/ocs/v1.php/apps/files_sharing/api/v1/shares?path={space-relative-path}&space_ref={space-id}&shared_with_me=true
ms graph: /drives/{drive-id}/items/{drive-item-id}/permissions
{
"value": [
{
"id": "collaborative-share-id",
"roles": [
"write"
],
"grantedToV2": {
"user": {
"displayName": "Albert Einstein",
"id": "4c510ada-c86b-4815-8820-42cdf82c3d51"
}
},
},
{
"id": "public-link-share-id",
"roles": [
"read"
],
"link": {
"webUrl": "https://cloud.example.com/s/CMXOrzoFODpHKsS"
}
}
]
}
libre graph: for now we stick to two kinds of permissions:
grantedToV2
for internal user or group shareslink
for public link shares
jfd: roles are currently hardcoded to read
, write
and owner
. We can make them customizable but I'll show that at the end of this description
{
"value": [
{
"id": "collaborative-share-id",
"roles": [
"write"
],
"grantedToV2": {
"user": {
"displayName": "Albert Einstein",
"id": "4c510ada-c86b-4815-8820-42cdf82c3d51"
}
},
},
{
"id": "public-link-share-id",
"roles": [
"read"
],
"link": {
"webUrl": "https://cloud.example.com/s/CMXOrzoFODpHKsS"
}
}
]
}
OCS: /ocs/v2.php/apps/files_sharing/api/v1/sharees?search=einst&itemType=(folder|file)&page=1&perPage=200&format=json
ms graph: /me/people?$search="einst"
is used to interact with users that are relevant or in a working-with relationship. The returned list of person entities has a personType
property to differentiate types of groups and users.
libre graph: /me/people?$search="einst"
is the only endpoint we need, I think. For now, a $search="einst"
parameter can be used instead of the OCS sharee call.
Note: There is a difference between $search="foo bar"
and $search=foo bar
. To mimic OCS the request has to quote the typed in string.*
OCS:
POST ocs/v1.php/apps/files_sharing/api/v1/shares
shareType=0
shareWith=marie
path=/Neuer Ordner
space_ref=storage-users-1$some-admin-user-id-0000-000000000000!71beebf5-0057-4104-a814-bb49712eaab9
permissions=31
role=editor
ms graph: sharing wit a user is done by posting an invite
POST /drives/{drive-id}/items/{drive-item-id}/invite
{
"requireSignIn": true,
"recipients": [
{
"email": "einstein@example.org"
}
],
"roles": [
"read"
]
}
response:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(permission)",
"value": [
{
"@odata.type": "#microsoft.graph.permission",
"id": "r_mJa3kBqBtkotIl8tWt9nVA2L1",
"roles": [
"read"
],
"shareId": "s!BFB3CkuhRCbBgmcZvWieFTBrBGQ",
"expirationDateTime": "0001-01-01T00:00:00Z",
"hasPassword": false,
"grantedToV2": {
"user": {
"id": "einstein@example.org"
}
},
"invitation": {
"email": "einstein@example.org",
"signInRequired": true
},
"link": {
"webUrl": "https://1drv.ms/f/s!BFB3CkuhRCbBgmcZvWieFTBrBGQ"
}
}
]
}
It seems the response does not use grantedToV2
. And when requireSignIn
is left out or false
it will happily create a link webUrl
that allows browsing the file.
libre graph: sharing wit a user is done by posting an invite
. While ms graph uses email
to identify a recipient libre graph assumes internal shares are created using the objectId
(the users id
)
POST /drives/{drive-id}/items/{drive-item-id}/invite
{
"requireSignIn": true,
"recipients": [
{
"objectId": "4c510ada-c86b-4815-8820-42cdf82c3d51"
}
],
"roles": [
"read"
]
}
response:
{
"value": [
{
"id": "8c5ed185-1fcb-4c5a-8569-b9ed04293204",
"roles": [
"read"
],
"grantedToV2": {
"user": {
"id": "4c510ada-c86b-4815-8820-42cdf82c3d51"
}
},
}
]
}
jfd: multiple recipients can be sent in the same request. Each will receive a dedicated permissions object with their own id. WChen an error occurs a 207 multistatus response will be returned, similar to https://learn.microsoft.com/en-us/graph/api/site-follow?view=graph-rest-1.0&tabs=http#response-1 which shows an examplo where one of the entries contains an error request:
POST /drives/{drive-id}/items/{drive-item-id}/invite
{
"recipients": [
{
"objectId": "4c510ada-c86b-4815-8820-42cdf82c3d51"
},
{
"objectId": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"
}
],
"roles": [
"read"
]
}
response:
{
"value": [
{
"id": "81d5bad3-3eff-410a-a2ea-eda2d14d4474",
"roles": [
"write"
],
"grantedToV2": [
{
"user": {
"id": "4c510ada-c86b-4815-8820-42cdf82c3d51",
"displayName": "Albert Einstein"
}
}
]
},
{
"id": "b470677e-a7f5-4304-8ef5-f5056a21fff1",
"error": {
"@odata.type": "#odata.error.main",
"code": "invalidRequest",
"message": "The user id that is provided in the request is incorrect",
"innerError": {
"code": "itemNotFound",
"errorType": "expected",
"message": "Unknown user id b470677e-a7f5-4304-8ef5-f5056a21fff1 ",
}
}
}
]
}
List shares created by the currently logged in user
OCS: is called Shared With Others, see below
ms graph: odata query not implemented
onedrive web: can list shares by me in the web ui. maybe something is coming to ms graph as well?
libre graph: /me/drive/sharedByMe
(only includes shares created by the current user)
Includes shares in project spaces that can be managed by the current user because he is a manager of the space. He does not need to be the creator of the share.
OCS: /ocs/v1.php/apps/files_sharing/api/v1/shares?reshares=true&include_tags=false&share_types=0,1,4,6
ms graph: odata query not implemented
libre graph: does not exist, but we could add a new /me/drive/sharedWithOthers
endpoint
Note: the OCS share_types
are 0 = User, 1 = Group, 3 = public link, 4 = guest, 6 = federated share, 7 = federated group / space member user (OH great we have a clash here), 8 = space member group.
Hint: There is a difference in the concept of shares. In OC10 shares are tied to a user. In OCIS they are tied to a space. The Owner/All managers of a space can collaboratively manage all shares in a personal/project space.
As the recipient you want to get a list of all driveItems that have been shared with you.
OCS: /ocs/v1.php/apps/files_sharing/api/v1/shares?include_tags=false&state=all&shared_with_me=true
ms graph: /me/drive/sharedWithMe
libre graph: /me/drive/sharedWithMe
The endpoint returns a collection(driveItem)
that contains both: mounted and unmounted driveItems. shared driveItems are wrapped in another driveItem representing the mountpoint as on the ms graph API. But there are two deviations from the ms graph API:
shared
property but expands the permissions
relation as it contains all necessary information. This might change if we think the shared
property is enough to show the necessary indicators. It would be faster than listing all details of the permission.@UI.hidden
annotation on driveItems to indicate
{
"value": [
{
"id": "u-u-id-of-mountpoint",
"name": "November-December Ad Proposals.pptx",
"size": 100984,
"parentReference": {
"driveType": "personal",
"driveId": "u-u-id-of-drive-containing-the-pointpoint",
"id": "parent-u-u-id"
},
"remoteItem": {
"id": "u-u-id-of-shared-driveItem",
"name": "November-December Ad Proposals.pptx",
"size": 100984,
"parentReference": {
"driveType": "personal",
"driveId": "u-u-id-of-drive-containing-the-item"
// no "id" because recipient is not allowed to see parent
},
"permissions": [
{
"@UI.hidden": false,
"id": "92f276ac-827a-40c1-9d9c-bb41a628ce71",
"roles": [
"write"
],
"grantedToV2": {
"user": {
"displayName": "Jörn Dreyer",
"id": "c12644a14b0a7750"
}
}
}
]
}
},
{
"id": "7fd82e03-09af-4b38-8d36-c7f7ec83cd99",
"name": "Marketing Term Successes International.xlsx",
"size": 17776,
"parentReference": {
"driveType": "personal",
"driveId": "u-u-id-of-drive-containing-the-item"
// no "id" because recipient is not allowed to see parent
},
"permissions": [
{
"@UI.hidden": false,
"id": "477731b4-56a6-4f58-9530-2e08bdf52df5",
"roles": [
"write"
],
"grantedToV2": {
"user": {
"displayName": "Jörn Dreyer",
"id": "c12644a14b0a7750"
}
}
}
]
},
{
"id": "a7d033f9-6093-43bb-91a7-bc82265e6a7f",
"name": "Irrelevant Marketing Term Successes International.xlsx",
"size": 176536587,
"parentReference": {
"driveType": "personal",
"driveId": "u-u-id-of-drive-containing-the-item"
// no "id" because recipient is not allowed to see parent
},
"permissions": [
{
"@UI.hidden": true,
"id": "5277be2e-db85-4d11-9a6c-28853142f279",
"roles": [
"write"
],
"grantedToV2": {
"user": {
"displayName": "Jörn Dreyer",
"id": "c12644a14b0a7750"
}
}
}
]
}
]
}
jfd: we cannot put the @UI.hidden
property on the drive item directly, because it would mean the driveItem was hidden. So we put it on the permissions property. That hidden flag can be toggled.
OCS:
POST ocs/v2.php/apps/files_sharing/api/v1/shares/pending/{share-id}
DELETE ocs/v2.php/apps/files_sharing/api/v1/shares/pending/{share-id}
ms graph: /sharedWithMe will list all shares, but"parentReference": {
"driveId": "c12644a14b0a7750",
"driveType": "personal"
},
libre graph: mount shares in the share jail by creating a driveItem using a POST /drives/{sharejailid}/items
request:
{
"name": "Einsteins project share",
"remoteItem": {
"id": "{drive-item-id}"
}
}
This request can check if the current user has access to the given drive item and will 'mount'/ accept it with the name "Einsteins project share". In theory we could add a remoteItem anywhere in a drive like in OC10, but it is a product decision to collect them in the dedicated virtual share jail drive.
The mountpoint can be deleted/rejected by sending a DELETE /drives/{drive-item-id}
where {drive-item-id}
is the id of the drive item representing the mount point, not the remote item.
ms graph
POST /drives/{drive-id}/items/{drive-item-id}/createLink
{
"type": "view",
"scope": "anonymous"
}
response:
{
"id": "{permission-id}",
"link": {
"type": "view",
"webUrl": "https://1drv.ms/f/s!AlB3CkuhRCbBgmde472f8-qxYdg",
"application": {
"id": "4c1ad100"
}
}
}
ms graph: jfd: same as libregraph but I tried changing the role ... we'll just not go there for now The role does affect the ui. It now shows upload elements even for non logged in users. The link type is still view. When trying to upload a file the web ui will try to log you in before making the request. The link type cannot be changed: in the UI there is a hint "This setting can't be changed. Create a new link if you need different permissions.". I guess this is to prevent changing permissions on existing links?
libre graph:
PATCH /drives/{drive-id}/items/{drive-item-id}/permissions/{permission-id}
{
"link": {
"type": "edit"
}
}
response
{
"id": "{permission-id}",
"link": {
"type": "edit",
"webUrl": "https://1drv.ms/f/s!AlB3CkuhRCbBgmde472f8-qxYdg",
"application": {
"id": "4c1ad100"
}
}
}
For links we ignore the role and instead set the link type. ms graph has a lot predefined: https://learn.microsoft.com/en-us/graph/api/listitem-createlink?view=graph-rest-beta&tabs=http#link-types
Type value | Description -- | -- internal | Only people who are invited. view | People can view and download. edit | People can view, download, upload, edit, move, add and delete blocksDownload | Creates a read-only link that blocks download to the item. Could be SecureView in the future. Not implemented yet. createOnly | People can only upload, existing content is not revealed (folders only) upload | People can upload, download and view (folders only)OCS DELETE ocs/v1.php/apps/files_sharing/api/v1/shares/{share-id}
libre graph DELETE /drives/{drive-id}/items/{drive-item-id}/permissions/{permission-id}
It follows the MS Graph API, documented here:
shares in ms graph are called permissions: https://learn.microsoft.com/en-us/graph/api/resources/permission?view=graph-rest-1.0
invite is used to add permissions and optionally send a message https://learn.microsoft.com/en-us/graph/api/driveitem-invite?view=graph-rest-1.0&tabs=http
list permissions on a driveItem: https://learn.microsoft.com/en-us/graph/api/driveitem-list-permissions?view=graph-rest-1.0&tabs=http
get permission: https://learn.microsoft.com/en-us/graph/api/permission-get?view=graph-rest-1.0&tabs=http
update permission: https://learn.microsoft.com/en-us/graph/api/permission-update?view=graph-rest-1.0&tabs=http
delete permission: https://learn.microsoft.com/en-us/graph/api/permission-delete?view=graph-rest-1.0&tabs=http
sharing roles are only read, view, owner: https://learn.microsoft.com/en-us/graph/api/resources/permission?view=graph-rest-1.0#roles-property-values
Listing shares incoming / outgoing shares:
https://graph.microsoft.com/v1.0/me/drive/items?$filter=shared ne null
. It works by matching all driveItems that have no sharing facet. However, it is not implemented in ms graph. Maybe because it is not clear that /v1.0/me/drive/items
works on ALL drives. Semantically, it should only list the shared drive items on the users personal drive. To match drive items on all drives the user has access to https://graph.microsoft.com/v1.0/drives?$expand=items&$filter=items/any(property:property/shared+ne+null)
could be used to get a list of all drives that have shared drive items and expand the items. This is not allowed on the ms graph api.
What does work is https://graph.microsoft.com/v1.0/me/drive/root/search(q='*')?filter=shared+ne+null
but it is slow as it relies on the search() function. I'd go with the semantically correct version.interesting sidenote
/shares/{share-id}
endpoint that can be used to directly access a shared drive item ... similar to /drives/{drive-id}
... 🤔 @TheOneRing @felix-schwarz @jesmrec @michaelstingl I posted a typical share lifecycle.
The details of the new API are getting clearer day by day now. I am really looking forward. We will get there 😄
@micbar
can you explain what the endpoint
Add POST drive/{drive-id}/items and DELETE drive/{drive-id}/items/{item-ID}
should do in the sharing ng context?
Shared by me
/ Shared with others
: what does the response look like?requireSignIn
set to false
and creating a public link?type
s instead?roles are currently hardcoded to read, write and owner. We can make them customizable but I'll show that at the end of this description
, but I can't seem to find that part.
- Regarding
Shared by me
/Shared with others
: what does the response look like?
You will get a list of drive items with an array of permissions https://owncloud.dev/libre-graph-api/#/me.drive/ListSharedByMe
- Can you explain the difference between creating a share with
requireSignIn
set tofalse
and creating a public link?
Public links are not created via the invite endpoint. Please check the swagger Ui https://owncloud.dev/libre-graph-api/#/drives.permissions/CreateLink
- Can you explain the reasoning behind not using roles for public links, but use a pre-defined set of
type
s instead?
Because they are the same like on OneDrive and we currently see no need to make that dynamic. The current efforts focus on shifting all responsibility to the server and make hatdcoded client side role mappings obsolete.
- What about APIs for roles? You mentioned that
roles are currently hardcoded to read, write and owner. We can make them customizable but I'll show that at the end of this description
, but I can't seem to find that part.
There are APIs to list roles and role permissions. The swagger ui provides a lot of explanations and examples. https://owncloud.dev/libre-graph-api/#/roleManagement/ListPermissionRoleDefinitions
They will not be configurable in the first iteration. But that has no impact on the API, because they should not be configurable at runtime.
- Regarding
Shared by me
/Shared with others
: what does the response look like?You will get a list of drive items with an array of permissions https://owncloud.dev/libre-graph-api/#/me.drive/ListSharedByMe
Thanks! Looking at the provided example below, is value.id
the ID of the share/permission? Or the File ID of the shared item? If it's the former: from where can a client get the File ID of the shared item?
{
"value": [
{
"id": "78363031-03ef-4eda-84a2-243a691a13cd",
"createdDateTime": "2020-02-19T14:23:25.52Z",
"eTag": "aQzEyNjQ0QTE0QjBBNzc1MCExMzc5LjQ",
"lastModifiedDateTime": "2021-09-03T14:09:25.503Z",
"name": "March Proposal.docx",
"parentReference": {
"driveId": "1991210caf",
"driveType": "personal"
}
- Can you explain the difference between creating a share with
requireSignIn
set tofalse
and creating a public link?Public links are not created via the invite endpoint. Please check the swagger Ui https://owncloud.dev/libre-graph-api/#/drives.permissions/CreateLink
I understand that public links should be created via createLink
, but am wondering what invite
's requireSignIn
is there for then. Since, if I'm able to create a link, via which someone else can view content without having to sign in, that sounds like the description of a public link to me.
- Can you explain the reasoning behind not using roles for public links, but use a pre-defined set of
type
s instead?Because they are the same like on OneDrive and we currently see no need to make that dynamic. The current efforts focus on shifting all responsibility to the server and make hatdcoded client side role mappings obsolete.
Right now, the iOS app can internally use roles for public links and user/group shares - and use the same UI code for public links as for user/group shares because the current/OC10 API uses the same permission model and endpoint as user/group shares.
The way I understand the proposed new API now (and please correct me if I'm wrong), public links and user/group shares no longer share a common permission model or semantic, but there's now distinct concepts for the two:
Advantages I'd have seen in adopting roles for public links as well would have been that
Advantages I'd have seen in adopting roles for public links as well would have been that
code could continue to be shared for both public links and user/group shares that the server would have full control over what types of public links users can create by including or excluding roles for public link types in the server-provided list of possible roles.
I see. From the server POV shares and public links are different. So i would not advise to treat them like they are the same.
Thanks! Looking at the provided example below, is value.id the ID of the share/permission? Or the File ID of the shared item? If it's the former: from where can a client get the File ID of the shared item?
The main entity is a driveItem, which is a file or a folder. The id of the driveItem is always the fileID. The permissions are a sub entity of the driveItem, and the permission id is equivalent to the shareID.
To clarify for the short term:
We will change the resource id of mountpoints from the current {sharesstorageproviderid}${sharejailid}!{shareid}
to {sharesstorageproviderid}${sharejailid}!{resourceid of shared item}
. Currently, the shareid
is {providerid}:{spaceid}:{shareid}
. We want to change it to be {providerid}:{spaceid}:{node/opaque id}
. The Encoding / Decoding with :
is only used by the jsoncs3 share manager. Unfortunately, we cannot change the implementation of the jsoncs3 share manager directly, because we would no longer be able to persist multiple shares per resource.
The plan is to replace the shareid with the resourceid on a higher level, until we can change the CS3 API to reflect this change as well. AFAICT we will need the graph API as well as webdav (or rather tha sharesstorageprovider) to rewrite ids.
The @ui.hidden
as well as @client.synchronize
flags will become part of the driveItem
in the /me/sharedWithMe
response. It will neither be part of the remoteItem
, nor a permission
.
@ui.hidden
should be @UI.Hidden
as per https://sap.github.io/odata-vocabularies/vocabularies/UI.html#:~:text=Hidden
Long Term changes on the CS3 API:
Shares should better reflect the graph API concept of a remoteItem? It should contain multiple permissions. Then hidden, synced and mountpoint would become properties of a share, rather than the current hack where we set these porperties on all shares of a remoteItem. The current shares would become permissions or maybe grants which is the term already used on the CS3 API when forwarding a share to the storageprovider.
Maybe the share manager should become the list of spaces and shares a user has access to. It should answer the question what are the spaces I have access to efficiently per user. Currently, space access is managed by bypassing the gateway and the share manager. The share manager should be made aware of spaces as well.
Goal
⚠️ Note:
*needs deprecation notice 6 month prior for CERN
Images
Stories
Sharing
POST drive/{drive-id}/items
andDELETE drive/{drive-id}/items/{item-ID}
@fschade, https://learn.microsoft.com/en-us/graph/api/driveitem-delete?view=graph-rest-1.0&tabs=http, https://learn.microsoft.com/en-us/graph/api/driveitem-post-children?view=graph-rest-1.0&tabs=httpgrantedtoV2
implementation with the unified roles instead of "editor, viewer, manager" https://github.com/owncloud/ocis/pull/7861 @fschadeLinks
quicklink
andinternal link
to spec https://github.com/owncloud/libre-graph-api/pull/131 @micbarUsers