OpenCloud deprecated

The OpenCloud integration accesses the files of an OpenCloud instance through WebDAV and its shares, spaces and permissions through the libre graph API. It authenticates either with basic authentication, i.e. a username and an app token, or with OAuth. The admin needs to configure the capability for the users the storage is available for:

com.openexchange.capability.filestorage_opencloud=true

Basic authentication

A client can trigger the account creation on behalf of the user by calling the new action in the fileaccount module (fileaccount?action=new). The configuration object within the request body must contain the URL the spaces of the instance are served under, along with the user's credentials.

{
  "filestorageService": "opencloud",
  "displayName": "My OpenCloud Account",
  "configuration": {
    "login": "username",
    "password": "app token",
    "url": "https://opencloud.example.com/dav/spaces"
  }
}

OAuth

OAuth requires the open-xchange-oauth package in addition to open-xchange-file-storage-webdav, see 3rd Party Integrations. OAuth itself is disabled by default and needs to be set up for the instance the deployment integrates with. Whether it is enabled is evaluated through the config cascade whenever the OAuth capabilities of a user are checked, so enabling or disabling it takes effect with a configuration reload, without a restart. As long as it is not enabled, no user is offered OAuth for OpenCloud, and the storage stays available with basic authentication.

com.openexchange.oauth.opencloud.enabled=true
com.openexchange.oauth.opencloud.hostname=opencloud.example.com
com.openexchange.oauth.opencloud.apiKey=<client id>
com.openexchange.oauth.opencloud.apiSecret=<client secret>
com.openexchange.oauth.opencloud.redirectUrl=https://appsuite.example.com/ajax/defer
com.openexchange.oauth.opencloud.productName=<name shown to the user>

The deployment authenticates as a confidential client towards the identity provider, so the client it is registered as needs a secret. A public client that authenticates with PKCE is not supported. An instance that is fronted by an identity provider of its own, e.g. Keycloak, states its endpoints, which default to the ones of the identity provider built into OpenCloud otherwise.

com.openexchange.oauth.opencloud.authorizationUrl=https://keycloak.example.com/realms/opencloud/protocol/openid-connect/auth
com.openexchange.oauth.opencloud.tokenUrl=https://keycloak.example.com/realms/opencloud/protocol/openid-connect/token
com.openexchange.oauth.opencloud.userInfoUrl=https://keycloak.example.com/realms/opencloud/protocol/openid-connect/userinfo

The token the middleware obtains needs to be accepted by the OpenCloud instance, which an instance with an identity provider of its own does not necessarily do for a client of its own registration. Such an instance associates a token with one of its users by the claims it states, e.g. the roles or the groups of that user, so the client the deployment authorizes with needs to state the same claims as the client of the instance itself. Whether a further scope is needed for the identity provider to state them is stated by

com.openexchange.oauth.opencloud.scope=openid profile email offline_access

A request the instance refuses is answered with the identifier of the request it logged the reason under, and the issuer, the party and the audience of the token that was used are stated within the debug log of the com.openexchange.file.storage.owncloud.opencloud package, so that a token the instance does not associate with a user of its own can be told apart from one it refuses altogether.

An account that is linked to an OAuth account states nothing but that account, as the URL is derived from the configured hostname. A URL that is stated for such an account is rejected, so that the access token is only ever sent to the instance that issued it.

{
  "filestorageService": "opencloud",
  "displayName": "My OpenCloud Account",
  "configuration": {
    "account": "17"
  }
}

Users and groups

The users and groups of an OpenCloud instance are matched with the ones of the server, so that a share can state who it is granted to, and so that a client can share an item with them. By default a user is matched by its mail address, falling back to the name it logs in with, and a group by its name:

com.openexchange.file.storage.opencloud.entityResolver.attribute=mail

Matching by mail address requires the OpenCloud instance to state the mail addresses of its users towards the users it serves. By default, OpenCloud omits the mail attribute from the users it lists or looks up on behalf of a user that is not an administrator, so that the users a share is granted to cannot be matched, and the shares of an item are stated without them. The graph service of the instance needs to be configured to include the addresses:

OC_SHOW_USER_EMAIL_IN_RESULTS=true

Note that this exposes the mail addresses of the users within the OpenCloud instance itself as well, e.g. within the search for a user to share an item with. A deployment that does not want that can match the users by the name they log in with instead, or map them statically as described below.

A deployment whose users and groups do not agree on those attributes can map them statically instead, which takes precedence:

com.openexchange.file.storage.opencloud.entityResolver.mappingFile=/opt/open-xchange/etc/opencloud-entities.list

The file states one entry per line, ':'-separated, just like the one of the ownCloud storage:

contextId:openCloudUserId:oxUserId               - a user of the given context
group:contextId:openCloudGroupId:oxGroupId       - a group of the given context

Reducing requests

The spaces of an account, the shares of an item and the users of the instance are looked up through the libre graph API, in addition to the WebDAV requests the files are accessed through. The spaces are looked up once per request and the role definitions and matched users are held in the cache of the deployment, while the following lookups are optional, as each of them is a request per item:

com.openexchange.file.storage.opencloud.entityResolver.folderPermissions=false
com.openexchange.file.storage.opencloud.entityResolver.objectPermissions=false
com.openexchange.file.storage.opencloud.entityResolver.createdModifiedBy=true
  • folderPermissions expresses the shares of a folder as its permissions, and applies the permissions stated for a folder as its shares. Disabled by default, as a listing does not tell whether a folder is shared, so it requires a request per listed folder.
  • objectPermissions expresses the shares of a file as its object permissions. A listing tells whether a file is shared, and the shares are only looked up if a client asks for the object permissions, but still with a request per shared file. Disabled by default, in which case a shared file states that it is shared, but not with whom. A client can share a file either way.
  • createdModifiedBy looks up the users that created and modified the items of a folder, which is a single request per listing, and only if a client asks for those users. Enabled by default.

Notable differences

  • The account's root holds the personal space, along with the virtual folders Spaces, Shared and Trash. Shared holds Shared with me, Shared with others and Shared via link. Each of them states its kind within the meta of the folder, so that a client can tell them apart.
  • Each file and folder the instance states a private link for carries the link that opens it within the web interface of the instance as backwardLink within the opencloud entry of its meta, so that a client needs not ask for it by a request of its own. A space states the URL of its web interface the same way. The virtual folders and the items of a trash bin state none.
  • OpenCloud limits the storage per space, so the quota of a folder is the one of the space it belongs to, and the quota of the account's root is the one of the personal space. A space that is not limited states no quota rather than a limit of zero.
  • An item another user shares is listed only once the user has accepted it within OpenCloud, as an item that is not synchronized there is served under no path.
  • The permissions of a folder always apply to the items it holds, so a request that asks for them not to be cascaded is answered with a warning.
  • A search is performed by the search service of the instance, which is asked through a REPORT request, OpenCloud implementing no SEARCH method. It matches the name, the size, the media type and the modification date of an item, and combines those criteria with "and", "or" and "not". A term that states another criterion, e.g. the content of an item, is rejected.
  • The items a search yields are taken from an index rather than from the folders themselves, so an item that was stored a moment ago may not be found yet. No more than 1000 items are considered for a single search, which is stated as a warning towards the client if a search yields more. Folders are searched by name the same way, through the search service of the instance.
  • A share states the role that matches the requested permissions best. If no role grants exactly those permissions, the least permissive role that exceeds them is granted, as OpenCloud knows no role that allows writing a file without allowing to delete it. So a file that is shared for writing is shared with a role that allows deleting it, too. A share that is meant to be read-only is never granted a role that allows to change the item, though.
  • The folders of an account that have a counterpart in the instance are not held in the folder cache of the middleware, as their names and the permissions their shares yield are changed within the OpenCloud instance just as well, which the middleware would not notice. Only the virtual folders, whose properties never change, are cached.
  • Restoring a previous version of a file copies that version onto the file, so the contents that were current before are kept as a further version.