owncloud / ocis

:atom_symbol: ownCloud Infinite Scale Stack
https://doc.owncloud.com/ocis/next/
Apache License 2.0
1.38k stars 182 forks source link

Sharing NG #6993

Open exalate-issue-sync[bot] opened 1 year ago

exalate-issue-sync[bot] commented 1 year ago

Goal

⚠️ Note:

*needs deprecation notice 6 month prior for CERN

Images

expected

problem elevate permissions via sharing

Stories

Sharing

Links

Users

fschrempf commented 12 months 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. ;)

micbar commented 11 months ago

Lifecycle of a share

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:

List shares on a resource (includes sharing options)

OCS: web calls two endpoints:

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:

Search for recipient (not part of this PR)

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.*

Create a share with user or group

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 ",
        }
      }
    }
  ]
}

Shared by me

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)

Shared with others (not part of this PR)

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.

Shared with me

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:

  1. Shared driveItems that have not been mounted are not wrapped by another drive item. The ms graph api always wraps the shared driveItem. We do not want to do that on the fly as the driveItem representing the mountpoint would not have a parent id as it is unmounted. We will only wrap the shared driveItem with a mount point if the share has been created.
  2. libre graph for now does not use the 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.
  3. libre graph uses a @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.

Accept/Reject a share / an invite

OCS:

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.

Create a public link

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"
        }
    }
}

Update shares / links

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)

Delete shares / links

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}

Further reading

It follows the MS Graph API, documented here:

Listing shares incoming / outgoing shares:

interesting sidenote

micbar commented 11 months ago

@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 😄

fschade commented 10 months ago

@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?

felix-schwarz commented 10 months ago
micbar commented 10 months ago
  • 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 to false 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 types 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.

felix-schwarz commented 10 months ago
  • 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 to false 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 types 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

micbar commented 10 months ago

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.

Biggest differences:

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.

butonic commented 8 months ago

To clarify for the short term:

Long Term changes on the CS3 API: