Repository: owncloud/ocis
Pull request #8796 (2024-04-08 08:55:24)

[tests-only][full-ci]added test for enable disable share shared by project space to group
## Description
Adding test feature file that covers test for enabling and disabling sync of share shared from Project space to group

## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
- Part of https://github.com/owncloud/ocis/issues/8486

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- test environment:
- tlocally
- CI
## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [ ] Code changes
- [ ] Unit tests added
- [x] Acceptance tests added
- [ ] Documentation ticket raised: <link> 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ac7f5eea9c..a7f2d558b8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
 # Table of Contents
 
 * [Changelog for unreleased](#changelog-for-unreleased-unreleased)
+* [Changelog for 5.0.1](#changelog-for-501-2024-04-10)
+* [Changelog for 4.0.7](#changelog-for-407-2024-03-27)
 * [Changelog for 5.0.0](#changelog-for-500-2024-03-18)
 * [Changelog for 4.0.6](#changelog-for-406-2024-02-07)
 * [Changelog for 4.0.5](#changelog-for-405-2023-12-21)
@@ -38,7 +40,7 @@
 
 The following sections list the changes for unreleased.
 
-[unreleased]: https://github.com/owncloud/ocis/compare/v5.0.0...master
+[unreleased]: https://github.com/owncloud/ocis/compare/v5.0.1...master
 
 ## Summary
 
@@ -58,6 +60,7 @@ The following sections list the changes for unreleased.
 * Bugfix - Fix restarting of postprocessing: [#8782](https://github.com/owncloud/ocis/pull/8782)
 * Bugfix - Fix the create personal space cache: [#8799](https://github.com/owncloud/ocis/pull/8799)
 * Bugfix - Fix removing groups from space: [#8803](https://github.com/owncloud/ocis/pull/8803)
+* Bugfix - Validate conditions for sharing roles by resource type: [#8815](https://github.com/owncloud/ocis/pull/8815)
 * Bugfix - Fix creating the drive item: [#8817](https://github.com/owncloud/ocis/pull/8817)
 * Change - Change the default store for presigned keys to nats-js-kv: [#8419](https://github.com/owncloud/ocis/pull/8419)
 * Change - Disable resharing by default for deprecation: [#8653](https://github.com/owncloud/ocis/pull/8653)
@@ -207,6 +210,16 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/issues/8768
    https://github.com/owncloud/ocis/pull/8803
 
+* Bugfix - Validate conditions for sharing roles by resource type: [#8815](https://github.com/owncloud/ocis/pull/8815)
+
+   We improved the validation of the allowed sharing roles for specific resource
+   type for various sharing related graph API endpoints. This allows e.g. the web
+   client to restrict the sharing roles presented to the user based on the type of
+   the resource that is being shared.
+
+   https://github.com/owncloud/ocis/issues/8331
+   https://github.com/owncloud/ocis/pull/8815
+
 * Bugfix - Fix creating the drive item: [#8817](https://github.com/owncloud/ocis/pull/8817)
 
    We fixed the issue when creating a drive item with random item id was allowed
@@ -455,6 +468,103 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/pull/8812
    https://github.com/owncloud/web/releases/tag/v9.0.0-alpha.4
 
+# Changelog for [5.0.1] (2024-04-10)
+
+The following sections list the changes for 5.0.1.
+
+[5.0.1]: https://github.com/owncloud/ocis/compare/v4.0.7...v5.0.1
+
+## Summary
+
+* Bugfix - Make IDP cookies same site strict: [#8716](https://github.com/owncloud/ocis/pull/8716)
+* Bugfix - Update reva to v2.19.4: [#8781](https://github.com/owncloud/ocis/pull/8781)
+* Bugfix - Fix restarting of postprocessing: [#8782](https://github.com/owncloud/ocis/pull/8782)
+* Bugfix - Fix the create personal space cache: [#8799](https://github.com/owncloud/ocis/pull/8799)
+
+## Details
+
+* Bugfix - Make IDP cookies same site strict: [#8716](https://github.com/owncloud/ocis/pull/8716)
+
+   To enhance the security of our application and prevent Cross-Site Request
+   Forgery (CSRF) attacks, we have updated the SameSite attribute of the build in
+   Identity Provider (IDP) cookies to Strict.
+
+   This change restricts the browser from sending these cookies with any cross-site
+   requests, thereby limiting the exposure of the user's session to potential
+   threats.
+
+   This update does not impact the existing functionality of the application but
+   provides an additional layer of security where needed.
+
+   This only affects cookies set by the built-in IDP. Production systems should not
+   be affected.
+
+   https://github.com/owncloud/ocis/pull/8716
+
+* Bugfix - Update reva to v2.19.4: [#8781](https://github.com/owncloud/ocis/pull/8781)
+
+   We updated reva to v2.19.4
+
+  *   Bugfix [cs3org/reva#4612](https://github.com/cs3org/reva/pull/4612): Use gateway selector in jsoncs3 to scale the service
+
+   Https://github.com/owncloud/ocis/pull/8787
+
+   We updated reva to v2.19.3
+
+  *   Bugfix[cs3org/reva#4607](https://github.com/cs3org/reva/pull/4607): Mask user email in output
+
+   https://github.com/owncloud/ocis/pull/8781
+
+* Bugfix - Fix restarting of postprocessing: [#8782](https://github.com/owncloud/ocis/pull/8782)
+
+   When an upload is not found, the logic to restart postprocessing was bunked.
+   Additionally we extended the upload sessions command to be able to restart the
+   uploads without using a second command.
+
+   NOTE: This also includes a breaking fix for the deprecated `ocis storage-users
+   uploads list` command
+
+   https://github.com/owncloud/ocis/pull/8782
+
+* Bugfix - Fix the create personal space cache: [#8799](https://github.com/owncloud/ocis/pull/8799)
+
+   We fixed a problem with the config for the create personal space cache which
+   resulted in the cache never being used.
+
+   https://github.com/owncloud/ocis/pull/8799
+
+# Changelog for [4.0.7] (2024-03-27)
+
+The following sections list the changes for 4.0.7.
+
+[4.0.7]: https://github.com/owncloud/ocis/compare/v5.0.0...v4.0.7
+
+## Summary
+
+* Bugfix - Update reva to include bugfixes and improvements: [#8718](https://github.com/owncloud/ocis/pull/8718)
+* Enhancement - Update to go 1.22: [#8597](https://github.com/owncloud/ocis/pull/8597)
+
+## Details
+
+* Bugfix - Update reva to include bugfixes and improvements: [#8718](https://github.com/owncloud/ocis/pull/8718)
+
+   ## Changelog for reva 2.13.4
+
+  *   Bugfix [cs3org/reva#4398](https://github.com/cs3org/reva/pull/4398): Fix ceph build
+  *   Bugfix [cs3org/reva#4396](https://github.com/cs3org/reva/pull/4396): Allow an empty credentials chain in the auth middleware
+  *   Bugfix [cs3org/reva#4423](https://github.com/cs3org/reva/pull/4423): Fix disconnected traces
+  *   Bugfix [cs3org/reva#4590](https://github.com/cs3org/reva/pull/4590): Fix uploading via a public link
+  *   Bugfix [cs3org/reva#4470](https://github.com/cs3org/reva/pull/4470): Keep failed processing status
+  *   Enhancement [cs3org/reva#4397](https://github.com/cs3org/reva/pull/4397): Introduce UploadSessionLister interface
+
+   https://github.com/owncloud/ocis/pull/8718
+
+* Enhancement - Update to go 1.22: [#8597](https://github.com/owncloud/ocis/pull/8597)
+
+   We have updated go to version 1.22.
+
+   https://github.com/owncloud/ocis/pull/8597
+
 # Changelog for [5.0.0] (2024-03-18)
 
 The following sections list the changes for 5.0.0.
@@ -3388,6 +3498,7 @@ The following sections list the changes for 3.0.0.
 * Bugfix - Fix the wrong status code when appRoleAssignments is forbidden: [#6037](https://github.com/owncloud/ocis/issues/6037)
 * Bugfix - Fix Search reindexing performance regression: [#6085](https://github.com/owncloud/ocis/pull/6085)
 * Bugfix - Fix userlog panic: [#6114](https://github.com/owncloud/ocis/pull/6114)
+* Bugfix - Fix wrong compile date: [#6132](https://github.com/owncloud/ocis/pull/6132)
 * Bugfix - Fix Logout Url config name: [#6227](https://github.com/owncloud/ocis/pull/6227)
 * Bugfix - Allow selected updates on graph users: [#6233](https://github.com/owncloud/ocis/pull/6233)
 * Bugfix - Add missing response to blocked requests: [#6277](https://github.com/owncloud/ocis/pull/6277)
@@ -3613,6 +3724,13 @@ The following sections list the changes for 3.0.0.
 
    https://github.com/owncloud/ocis/pull/6114
 
+* Bugfix - Fix wrong compile date: [#6132](https://github.com/owncloud/ocis/pull/6132)
+
+   We fixed that current date is always printed.
+
+   https://github.com/owncloud/ocis/issues/6124
+   https://github.com/owncloud/ocis/pull/6132
+
 * Bugfix - Fix Logout Url config name: [#6227](https://github.com/owncloud/ocis/pull/6227)
 
    We fixed the yaml and json name of the logout url option.
diff --git a/changelog/3.0.0_2023-06-06/fix-compile-date.md b/changelog/3.0.0_2023-06-06/fix-compile-date.md
new file mode 100644
index 00000000000..78f44d154a9
--- /dev/null
+++ b/changelog/3.0.0_2023-06-06/fix-compile-date.md
@@ -0,0 +1,6 @@
+Bugfix: fix wrong compile date
+
+We fixed that current date is always printed.
+
+https://github.com/owncloud/ocis/pull/6132
+https://github.com/owncloud/ocis/issues/6124
\ No newline at end of file
diff --git a/changelog/4.0.7_2024-03-27/bump-go-122.md b/changelog/4.0.7_2024-03-27/bump-go-122.md
new file mode 100644
index 00000000000..67227d9a877
--- /dev/null
+++ b/changelog/4.0.7_2024-03-27/bump-go-122.md
@@ -0,0 +1,5 @@
+Enhancement: Update to go 1.22
+
+We have updated go to version 1.22.
+
+https://github.com/owncloud/ocis/pull/8597
diff --git a/changelog/4.0.7_2024-03-27/update-reva.md b/changelog/4.0.7_2024-03-27/update-reva.md
new file mode 100644
index 00000000000..ec1728f5d05
--- /dev/null
+++ b/changelog/4.0.7_2024-03-27/update-reva.md
@@ -0,0 +1,12 @@
+Bugfix: Update reva to include bugfixes and improvements
+
+## Changelog for reva 2.13.4
+
+*   Bugfix [cs3org/reva#4398](https://github.com/cs3org/reva/pull/4398): Fix ceph build
+*   Bugfix [cs3org/reva#4396](https://github.com/cs3org/reva/pull/4396): Allow an empty credentials chain in the auth middleware
+*   Bugfix [cs3org/reva#4423](https://github.com/cs3org/reva/pull/4423): Fix disconnected traces
+*   Bugfix [cs3org/reva#4590](https://github.com/cs3org/reva/pull/4590): Fix uploading via a public link
+*   Bugfix [cs3org/reva#4470](https://github.com/cs3org/reva/pull/4470): Keep failed processing status
+*   Enhancement [cs3org/reva#4397](https://github.com/cs3org/reva/pull/4397): Introduce UploadSessionLister interface
+
+https://github.com/owncloud/ocis/pull/8718
diff --git a/changelog/5.0.1_2024-04-10/bump-reva.md b/changelog/5.0.1_2024-04-10/bump-reva.md
new file mode 100644
index 00000000000..57c4a4c2eb1
--- /dev/null
+++ b/changelog/5.0.1_2024-04-10/bump-reva.md
@@ -0,0 +1,13 @@
+Bugfix: Update reva to v2.19.4
+
+We updated reva to v2.19.4
+
+*   Bugfix [cs3org/reva#4612](https://github.com/cs3org/reva/pull/4612): Use gateway selector in jsoncs3 to scale the service
+
+https://github.com/owncloud/ocis/pull/8787
+
+We updated reva to v2.19.3
+
+*   Bugfix[cs3org/reva#4607](https://github.com/cs3org/reva/pull/4607): Mask user email in output
+
+https://github.com/owncloud/ocis/pull/8781
diff --git a/changelog/5.0.1_2024-04-10/enhancement-strict-same-site-cookie.md b/changelog/5.0.1_2024-04-10/enhancement-strict-same-site-cookie.md
new file mode 100644
index 00000000000..c3a4e1a1f37
--- /dev/null
+++ b/changelog/5.0.1_2024-04-10/enhancement-strict-same-site-cookie.md
@@ -0,0 +1,14 @@
+Bugfix: Make IDP cookies same site strict
+
+To enhance the security of our application and prevent Cross-Site Request Forgery (CSRF) attacks, we have updated the
+SameSite attribute of the build in Identity Provider (IDP) cookies to Strict.
+
+This change restricts the browser from sending these cookies with any cross-site requests,
+thereby limiting the exposure of the user's session to potential threats.
+
+This update does not impact the existing functionality of the application but provides an additional layer of security
+where needed.
+
+This only affects cookies set by the built-in IDP. Production systems should not be affected.
+
+https://github.com/owncloud/ocis/pull/8716
diff --git a/changelog/5.0.1_2024-04-10/fix-create-personal-space-cache.md b/changelog/5.0.1_2024-04-10/fix-create-personal-space-cache.md
new file mode 100644
index 00000000000..cd7831bce19
--- /dev/null
+++ b/changelog/5.0.1_2024-04-10/fix-create-personal-space-cache.md
@@ -0,0 +1,5 @@
+Bugfix: Fix the create personal space cache
+
+We fixed a problem with the config for the create personal space cache which resulted in the cache never being used.
+
+https://github.com/owncloud/ocis/pull/8799
diff --git a/changelog/5.0.1_2024-04-10/fix-postprocessing-restart.md b/changelog/5.0.1_2024-04-10/fix-postprocessing-restart.md
new file mode 100644
index 00000000000..6183701f168
--- /dev/null
+++ b/changelog/5.0.1_2024-04-10/fix-postprocessing-restart.md
@@ -0,0 +1,8 @@
+Bugfix: Fix restarting of postprocessing
+
+When an upload is not found, the logic to restart postprocessing was bunked. Additionally we extended the upload sessions
+command to be able to restart the uploads without using a second command.
+
+NOTE: This also includes a breaking fix for the deprecated `ocis storage-users uploads list` command
+
+https://github.com/owncloud/ocis/pull/8782
diff --git a/changelog/unreleased/fix-graph-allowed-roles.md b/changelog/unreleased/fix-graph-allowed-roles.md
new file mode 100644
index 00000000000..bfd334e41cb
--- /dev/null
+++ b/changelog/unreleased/fix-graph-allowed-roles.md
@@ -0,0 +1,9 @@
+Bugfix: Validate conditions for sharing roles by resource type
+
+We improved the validation of the allowed sharing roles for specific resource type
+for various sharing related graph API endpoints. This allows e.g. the web client to
+restrict the sharing roles presented to the user based on the type of the resource
+that is being shared.
+
+https://github.com/owncloud/ocis/pull/8815
+https://github.com/owncloud/ocis/issues/8331
diff --git a/deployments/continuous-deployment-config/ocis_keycloak/released.yml b/deployments/continuous-deployment-config/ocis_keycloak/released.yml
index cf91c92d609..68187812118 100644
--- a/deployments/continuous-deployment-config/ocis_keycloak/released.yml
+++ b/deployments/continuous-deployment-config/ocis_keycloak/released.yml
@@ -32,7 +32,7 @@
         env:
           INSECURE: "false"
           TRAEFIK_ACME_MAIL: mbarz@owncloud.com
-          OCIS_DOCKER_TAG: 5.0.0
+          OCIS_DOCKER_TAG: 5.0.1
           OCIS_DOMAIN: ocis.ocis-keycloak.released.owncloud.works
           KEYCLOAK_DOMAIN: keycloak.ocis-keycloak.released.owncloud.works
           COMPOSE_FILE: docker-compose.yml:monitoring_tracing/docker-compose-additions.yml
diff --git a/deployments/continuous-deployment-config/ocis_ldap/released.yml b/deployments/continuous-deployment-config/ocis_ldap/released.yml
index 9beff9103ba..1936bf75885 100644
--- a/deployments/continuous-deployment-config/ocis_ldap/released.yml
+++ b/deployments/continuous-deployment-config/ocis_ldap/released.yml
@@ -32,7 +32,7 @@
         env:
           INSECURE: "false"
           TRAEFIK_ACME_MAIL: mbarz@owncloud.com
-          OCIS_DOCKER_TAG: 5.0.0
+          OCIS_DOCKER_TAG: 5.0.1
           OCIS_DOMAIN: ocis.ocis-ldap.released.owncloud.works
           LDAP_MANAGER_DOMAIN: ldap.ocis-ldap.released.owncloud.works
           COMPOSE_FILE: docker-compose.yml:monitoring_tracing/docker-compose-additions.yml
diff --git a/deployments/continuous-deployment-config/ocis_traefik/released.yml b/deployments/continuous-deployment-config/ocis_traefik/released.yml
index d0ff5fc7895..1345b85f12c 100644
--- a/deployments/continuous-deployment-config/ocis_traefik/released.yml
+++ b/deployments/continuous-deployment-config/ocis_traefik/released.yml
@@ -32,7 +32,7 @@
         env:
           INSECURE: "false"
           TRAEFIK_ACME_MAIL: mbarz@owncloud.com
-          OCIS_DOCKER_TAG: 5.0.0
+          OCIS_DOCKER_TAG: 5.0.1
           OCIS_DOMAIN: ocis.ocis-traefik.released.owncloud.works
           DEMO_USERS: "true"
           INBUCKET_DOMAIN: mail.ocis-traefik.released.owncloud.works
diff --git a/deployments/continuous-deployment-config/ocis_wopi/released.yml b/deployments/continuous-deployment-config/ocis_wopi/released.yml
index f570f3a52d3..4673fb4872d 100644
--- a/deployments/continuous-deployment-config/ocis_wopi/released.yml
+++ b/deployments/continuous-deployment-config/ocis_wopi/released.yml
@@ -32,7 +32,7 @@
         env:
           INSECURE: "false"
           TRAEFIK_ACME_MAIL: mbarz@owncloud.com
-          OCIS_DOCKER_TAG: 5.0.0
+          OCIS_DOCKER_TAG: 5.0.1
           OCIS_DOMAIN: ocis.ocis-wopi.released.owncloud.works
           COMPANION_DOMAIN: companion.ocis-wopi.released.owncloud.works
           COMPANION_IMAGE: owncloud/uppy-companion:3.12.13-owncloud
diff --git a/services/frontend/README.md b/services/frontend/README.md
index 83b395348b0..d66736f0b60 100644
--- a/services/frontend/README.md
+++ b/services/frontend/README.md
@@ -22,6 +22,10 @@ The datagateway endpoint, by default `/data`, forwards file up- and download req
 
 The ocs endpoint, by default `/ocs`, implements the ownCloud 10 Open Collaboration Services API by translating it into CS3 API requests. It can handle users, groups, capabilities and also implements the files sharing functionality on top of CS3. The `/ocs/v[12].php/cloud/user/signing-key` is currently handled by the dedicated [ocs](https://github.com/owncloud/ocis/tree/master/services/ocs) service.
 
+#### Event Handler
+
+The `frontend` service contains an eventhandler for handling `ocs` related events. As of now, it only listens to the `ShareCreated` event.
+
 ### Sharing
 
 Aggregating share information is one of the most time consuming operations in OCIS. The service fetches a list of either received or created shares and has to stat every resource individually. While stats are fast, the default behavior scales linearly with the number of shares.
@@ -63,15 +67,13 @@ Store specific notes:
   -   When using `nats-js-kv` it is recommended to set `OCIS_CACHE_STORE_NODES` to the same value as `OCIS_EVENTS_ENDPOINT`. That way the cache uses the same nats instance as the event bus.
   -   When using the `nats-js-kv` store, it is possible to set `OCIS_CACHE_DISABLE_PERSISTENCE` to instruct nats to not persist cache data on disc.
 
-## Event Handler
-
-The `frontend` service contains an eventhandler for handling `ocs` related events. As of now, it only listens to the `ShareCreated` event.
-
 ### Auto-Accept Shares
 
 When setting the `FRONTEND_AUTO_ACCEPT_SHARES` to `true`, all incoming shares will be accepted automatically. Users can overwrite this setting individually in their profile.
 
-## The password policy
+## Passwords
+
+### The Password Policy
 
 Note that the password policy currently impacts only **public link password validation**.
 
@@ -104,7 +106,7 @@ These variables are global ocis variables because they are used not only in the
 
 Note that a password can have a maximum length of **72 bytes**. Depending on the alphabet used, a character is encoded by 1 to 4 bytes, defining the maximum length of a password indirectly. While US-ASCII will only need one byte, Latin alphabets and also Greek or Cyrillic ones need two bytes. Three bytes are needed for characters in Chinese, Japanese and Korean etc.
 
-### The password policy capability
+### The Password Policy Capability
 
 The capabilities endpoint (e.g. https://ocis.test/ocs/v1.php/cloud/capabilities?format=json) gives you following capabilities which are relevant for the password policy:
 
@@ -126,3 +128,19 @@ The capabilities endpoint (e.g. https://ocis.test/ocs/v1.php/cloud/capabilities?
   }
 }
 ```
+
+### Password Enforcement for all Public Links
+
+For public accessible shares, independent if read only or writable, a password is enforced. To change this requirement, set the following environment variable to `false`:
+
+`OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD`
+
+### Password Enforcement for Writeable Public Links
+
+For public accessible writable shares, a password can be enforced. To change the current setting, set the following environment variable to `true`:
+
+`OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD`
+
+Note that changing this environment variable only makes sense if\
+`OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD`\
+is set to `false`.
diff --git a/services/frontend/pkg/config/config.go b/services/frontend/pkg/config/config.go
index 6404c33ccb6..1408cd75954 100644
--- a/services/frontend/pkg/config/config.go
+++ b/services/frontend/pkg/config/config.go
@@ -142,8 +142,9 @@ type OCS struct {
 	EnableDenials                        bool               `yaml:"enable_denials" env:"FRONTEND_OCS_ENABLE_DENIALS" desc:"EXPERIMENTAL: enable the feature to deny access on folders." introductionVersion:"pre5.0"`
 	ListOCMShares                        bool               `yaml:"list_ocm_shares" env:"FRONTEND_OCS_LIST_OCM_SHARES" desc:"Include OCM shares when listing shares. See the OCM service documentation for more details." introductionVersion:"5.0"`
 	PublicShareMustHavePassword          bool               `yaml:"public_sharing_share_must_have_password" env:"OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on all public shares." introductionVersion:"5.0"`
-	WriteablePublicShareMustHavePassword bool               `yaml:"public_sharing_writeableshare_must_have_password" env:"OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on Uploader, Editor or Contributor shares." introductionVersion:"5.0"`
+	WriteablePublicShareMustHavePassword bool               `yaml:"public_sharing_writeableshare_must_have_password" env:"OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords for writable shares. Only effective if the setting for 'passwords on all public shares' is set to false." introductionVersion:"5.0"`
 	IncludeOCMSharees                    bool               `yaml:"include_ocm_sharees" env:"FRONTEND_OCS_INCLUDE_OCM_SHAREES" desc:"Include OCM sharees when listing sharees." introductionVersion:"5.0"`
+	ShowUserEmailInResults               bool               `yaml:"show_email_in_results" env:"OCIS_SHOW_USER_EMAIL_IN_RESULTS" desc:"Mask user email addresses in responses." introductionVersion:"5.1"`
 }
 
 type CacheWarmupDrivers struct {
diff --git a/services/frontend/pkg/revaconfig/config.go b/services/frontend/pkg/revaconfig/config.go
index 769a6f6d1a0..1a94ae323eb 100644
--- a/services/frontend/pkg/revaconfig/config.go
+++ b/services/frontend/pkg/revaconfig/config.go
@@ -338,7 +338,8 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
 							"productversion": version.GetString(),
 						},
 					},
-					"include_ocm_sharees": cfg.OCS.IncludeOCMSharees,
+					"include_ocm_sharees":   cfg.OCS.IncludeOCMSharees,
+					"show_email_in_results": cfg.OCS.ShowUserEmailInResults,
 				},
 			},
 		},
diff --git a/services/graph/pkg/errorcode/cs3.go b/services/graph/pkg/errorcode/cs3.go
index 12bd7b5c8b3..6b3f71be317 100644
--- a/services/graph/pkg/errorcode/cs3.go
+++ b/services/graph/pkg/errorcode/cs3.go
@@ -17,12 +17,12 @@ import (
 //
 // This function is particularly useful when dealing with CS3 responses,
 // and a unified error handling within the application is necessary.
-func FromCS3Status(status *cs3rpc.Status, inerr error, ignore ...cs3rpc.Code) *Error {
+func FromCS3Status(status *cs3rpc.Status, inerr error, ignore ...cs3rpc.Code) error {
 	if inerr != nil {
-		return &Error{msg: inerr.Error(), errorCode: GeneralException}
+		return Error{msg: inerr.Error(), errorCode: GeneralException}
 	}
 
-	err := &Error{errorCode: GeneralException, msg: "unspecified error has occurred"}
+	err := Error{errorCode: GeneralException, msg: "unspecified error has occurred"}
 
 	if status != nil {
 		err.msg = status.GetMessage()
@@ -33,7 +33,7 @@ func FromCS3Status(status *cs3rpc.Status, inerr error, ignore ...cs3rpc.Code) *E
 	case slices.Contains(ignore, status.GetCode()):
 		fallthrough
 	case code == cs3rpc.Code_CODE_OK:
-		err = nil
+		return nil
 	case code == cs3rpc.Code_CODE_NOT_FOUND:
 		err.errorCode = ItemNotFound
 	case code == cs3rpc.Code_CODE_PERMISSION_DENIED:
@@ -61,11 +61,11 @@ func FromCS3Status(status *cs3rpc.Status, inerr error, ignore ...cs3rpc.Code) *E
 	return err
 }
 
-// FromStat transforms a *provider.StatResponse object and an error into an *Error.
+// FromStat transforms a *provider.StatResponse object and an error into an Error.
 //
 // It takes a stat of type *provider.StatResponse, an error, and a variadic parameter of type cs3rpc.Code.
 // It invokes the FromCS3Status function with the StatResponse Status and the ignore codes.
-func FromStat(stat *provider.StatResponse, err error, ignore ...cs3rpc.Code) *Error {
+func FromStat(stat *provider.StatResponse, err error, ignore ...cs3rpc.Code) error {
 	// TODO: look into ResourceInfo to get the postprocessing state and map that to 425 status?
 	return FromCS3Status(stat.GetStatus(), err, ignore...)
 }
diff --git a/services/graph/pkg/errorcode/errorcode.go b/services/graph/pkg/errorcode/errorcode.go
index 88bb421f24b..f4a9bda4102 100644
--- a/services/graph/pkg/errorcode/errorcode.go
+++ b/services/graph/pkg/errorcode/errorcode.go
@@ -166,7 +166,8 @@ func RenderError(w http.ResponseWriter, r *http.Request, err error) {
 	var errcode Error
 	if errors.As(err, &errcode) {
 		errcode.Render(w, r)
-	} else {
-		GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
 	}
+
+	GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
 }
diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go
index 1fd6caa56b1..df98081d9a5 100644
--- a/services/graph/pkg/service/v0/api_driveitem_permissions.go
+++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go
@@ -83,15 +83,14 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor
 	}
 
 	statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &resourceId}})
-	if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
-		s.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
-		return libregraph.Permission{}, *errCode
+	if err := errorcode.FromStat(statResponse, err); err != nil {
+		s.logger.Warn().Err(err).Interface("stat.res", statResponse).Msg("stat failed")
+		return libregraph.Permission{}, err
 	}
 
-	resourceInfo := statResponse.GetInfo()
-	condition := unifiedrole.UnifiedRoleConditionGrantee
-	if IsSpaceRoot(resourceInfo.GetId()) {
-		condition = unifiedrole.UnifiedRoleConditionOwner
+	var condition string
+	if condition, err = roleConditionForResourceType(statResponse.GetInfo()); err != nil {
+		return libregraph.Permission{}, err
 	}
 
 	unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: invite.LibreGraphPermissionsActions}}
@@ -116,7 +115,7 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor
 	cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions(unifiedRolePermissions)
 
 	createShareRequest := &collaboration.CreateShareRequest{
-		ResourceInfo: resourceInfo,
+		ResourceInfo: statResponse.GetInfo(),
 		Grant: &collaboration.ShareGrant{
 			Permissions: &collaboration.SharePermissions{
 				Permissions: cs3ResourcePermissions,
@@ -178,14 +177,14 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor
 	}
 
 	createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
-	if errCode := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); errCode != nil {
+	if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
 		s.logger.Debug().Err(err).Msg("share creation failed")
-		return libregraph.Permission{}, *errCode
+		return libregraph.Permission{}, err
 	}
 
 	if id := createShareResponse.GetShare().GetId().GetOpaqueId(); id != "" {
 		permission.Id = conversions.ToPointer(id)
-	} else if IsSpaceRoot(resourceInfo.GetId()) {
+	} else if IsSpaceRoot(statResponse.GetInfo().GetId()) {
 		// permissions on a space root are not handled by a share manager so
 		// they don't get a share-id
 		permission.SetId(identitySetToSpacePermissionID(permission.GetGrantedToV2()))
@@ -227,14 +226,14 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID
 	}
 
 	statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
-	if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
-		s.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
+	if err := errorcode.FromStat(statResponse, err); err != nil {
+		s.logger.Warn().Err(err).Interface("stat.res", statResponse).Msg("stat failed")
 		return collectionOfPermissions, err
 	}
 
-	condition := unifiedrole.UnifiedRoleConditionGrantee
-	if IsSpaceRoot(statResponse.GetInfo().GetId()) {
-		condition = unifiedrole.UnifiedRoleConditionOwner
+	var condition string
+	if condition, err = roleConditionForResourceType(statResponse.GetInfo()); err != nil {
+		return collectionOfPermissions, err
 	}
 
 	permissionSet := *statResponse.GetInfo().GetPermissionSet()
diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions_links.go b/services/graph/pkg/service/v0/api_driveitem_permissions_links.go
index c1410d880c8..3de834a331d 100644
--- a/services/graph/pkg/service/v0/api_driveitem_permissions_links.go
+++ b/services/graph/pkg/service/v0/api_driveitem_permissions_links.go
@@ -290,8 +290,8 @@ func (s DriveItemPermissionsService) updatePublicLinkPermission(ctx context.Cont
 			},
 		})
 
-	if errCode := errorcode.FromCS3Status(statResp.GetStatus(), err); errCode != nil {
-		return nil, *errCode
+	if err := errorcode.FromCS3Status(statResp.GetStatus(), err); err != nil {
+		return nil, err
 	}
 
 	if newPermission.HasExpirationDateTime() {
@@ -369,8 +369,8 @@ func (s DriveItemPermissionsService) updatePublicLinkPassword(ctx context.Contex
 			},
 		},
 	})
-	if errCode := errorcode.FromCS3Status(changeLinkRes.GetStatus(), err); errCode != nil {
-		return nil, *errCode
+	if err := errorcode.FromCS3Status(changeLinkRes.GetStatus(), err); err != nil {
+		return nil, err
 	}
 	permission, err := s.libreGraphPermissionFromCS3PublicShare(changeLinkRes.GetShare())
 	if err != nil {
@@ -396,8 +396,8 @@ func (s DriveItemPermissionsService) updatePublicLink(ctx context.Context, permi
 		},
 	})
 
-	if errCode := errorcode.FromCS3Status(changeLinkRes.GetStatus(), err); errCode != nil {
-		return nil, *errCode
+	if err := errorcode.FromCS3Status(changeLinkRes.GetStatus(), err); err != nil {
+		return nil, err
 	}
 
 	permission, err := s.libreGraphPermissionFromCS3PublicShare(changeLinkRes.GetShare())
diff --git a/services/graph/pkg/service/v0/api_drives_drive_item.go b/services/graph/pkg/service/v0/api_drives_drive_item.go
index 2b874d5eb31..882132fa0d1 100644
--- a/services/graph/pkg/service/v0/api_drives_drive_item.go
+++ b/services/graph/pkg/service/v0/api_drives_drive_item.go
@@ -71,11 +71,11 @@ func (s DrivesDriveItemService) UnmountShare(ctx context.Context, resourceID sto
 			},
 		},
 	)
-	if errCode := errorcode.FromCS3Status(getReceivedShareResponse.GetStatus(), err); errCode != nil {
-		s.logger.Debug().Err(errCode).
+	if err := errorcode.FromCS3Status(getReceivedShareResponse.GetStatus(), err); err != nil {
+		s.logger.Debug().Err(err).
 			Str("shareid", shareId).
 			Msg("failed to read share")
-		return errCode
+		return err
 	}
 
 	// Find all accepted shares for this resource
diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go
index e1bec910931..ace9091de3d 100644
--- a/services/graph/pkg/service/v0/base.go
+++ b/services/graph/pkg/service/v0/base.go
@@ -21,13 +21,14 @@ import (
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/cs3org/reva/v2/pkg/utils"
 	libregraph "github.com/owncloud/libre-graph-api-go"
+	"google.golang.org/protobuf/types/known/fieldmaskpb"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/config"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
-	"google.golang.org/protobuf/types/known/fieldmaskpb"
 )
 
 // BaseGraphService implements a couple of helper functions that are
@@ -165,7 +166,7 @@ func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, s
 			p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos())))
 		}
 
-		if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*perm, unifiedrole.UnifiedRoleConditionOwner); role != nil {
+		if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*perm, unifiedrole.UnifiedRoleConditionDrive); role != nil {
 			switch apiVersion {
 			case APIVersion_1:
 				if r := unifiedrole.GetLegacyName(*role); r != "" {
@@ -287,7 +288,17 @@ func (g BaseGraphService) cs3UserSharesToDriveItems(ctx context.Context, shares
 			}
 			item = *itemptr
 		}
-		perm, err := g.cs3UserShareToPermission(ctx, s, false)
+
+		var condition string
+		switch {
+		case item.Root != nil:
+			condition = unifiedrole.UnifiedRoleConditionDrive
+		case item.Folder != nil:
+			condition = unifiedrole.UnifiedRoleConditionFolder
+		case item.File != nil:
+			condition = unifiedrole.UnifiedRoleConditionFile
+		}
+		perm, err := g.cs3UserShareToPermission(ctx, s, condition)
 
 		var errcode errorcode.Error
 		switch {
@@ -303,10 +314,10 @@ func (g BaseGraphService) cs3UserSharesToDriveItems(ctx context.Context, shares
 	return driveItems, nil
 }
 
-func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share, isSpacePermission bool) (*libregraph.Permission, error) {
+func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share, roleCondition string) (*libregraph.Permission, error) {
 	perm := libregraph.Permission{}
 	perm.SetRoles([]string{})
-	if !isSpacePermission {
+	if roleCondition != unifiedrole.UnifiedRoleConditionDrive {
 		perm.SetId(share.GetId().GetOpaqueId())
 	}
 	grantedTo := libregraph.SharePointIdentitySet{}
@@ -322,7 +333,7 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c
 			return nil, errorcode.New(errorcode.GeneralException, err.Error())
 		default:
 			grantedTo.SetUser(user)
-			if isSpacePermission {
+			if roleCondition == unifiedrole.UnifiedRoleConditionDrive {
 				perm.SetId("u:" + user.GetId())
 			}
 		}
@@ -337,7 +348,7 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c
 			return nil, errorcode.New(errorcode.GeneralException, err.Error())
 		default:
 			grantedTo.SetGroup(group)
-			if isSpacePermission {
+			if roleCondition == unifiedrole.UnifiedRoleConditionDrive {
 				perm.SetId("g:" + group.GetId())
 			}
 		}
@@ -347,13 +358,9 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c
 	if share.GetExpiration() != nil {
 		perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration()))
 	}
-	condition := unifiedrole.UnifiedRoleConditionGrantee
-	if isSpacePermission {
-		condition = unifiedrole.UnifiedRoleConditionOwner
-	}
 	role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(
 		*share.GetPermissions().GetPermissions(),
-		condition,
+		roleCondition,
 	)
 	if role != nil {
 		perm.SetRoles([]string{role.GetId()})
@@ -418,8 +425,8 @@ func (g BaseGraphService) getCS3PublicShareByID(ctx context.Context, permissionI
 			},
 		},
 	)
-	if errCode := errorcode.FromCS3Status(getPublicShareResp.GetStatus(), err); errCode != nil {
-		return nil, *errCode
+	if err := errorcode.FromCS3Status(getPublicShareResp.GetStatus(), err); err != nil {
+		return nil, err
 	}
 	return getPublicShareResp.GetShare(), nil
 }
@@ -441,8 +448,8 @@ func (g BaseGraphService) removePublicShare(ctx context.Context, permissionID st
 				},
 			},
 		})
-	if errcode := errorcode.FromCS3Status(removePublicShareResp.GetStatus(), err); errcode != nil {
-		return *errcode
+	if err := errorcode.FromCS3Status(removePublicShareResp.GetStatus(), err); err != nil {
+		return err
 	}
 	// We need to return an untyped nil here otherwise the error==nil check won't work
 	return nil
@@ -466,8 +473,8 @@ func (g BaseGraphService) removeUserShare(ctx context.Context, permissionID stri
 			},
 		})
 
-	if errCode := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); errCode != nil {
-		return *errCode
+	if err := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); err != nil {
+		return err
 	}
 	// We need to return an untyped nil here otherwise the error==nil check won't work
 	return nil
@@ -495,8 +502,8 @@ func (g BaseGraphService) removeSpacePermission(ctx context.Context, permissionI
 		},
 	})
 
-	if errCode := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); errCode != nil {
-		return *errCode
+	if err := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); err != nil {
+		return err
 	}
 	// We need to return an untyped nil here otherwise the error==nil check won't work
 	return nil
@@ -527,15 +534,20 @@ func (g BaseGraphService) getCS3UserShareByID(ctx context.Context, permissionID
 				},
 			},
 		})
-	if errCode := errorcode.FromCS3Status(getShareResp.GetStatus(), err); errCode != nil {
-		return nil, *errCode
+	if err := errorcode.FromCS3Status(getShareResp.GetStatus(), err); err != nil {
+		return nil, err
 	}
 	return getShareResp.GetShare(), nil
 }
 
 func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId) (*libregraph.Permission, *storageprovider.ResourceId, error) {
-	publicShare, err := g.getCS3PublicShareByID(ctx, permissionID)
 	var errcode errorcode.Error
+	gatewayClient, err := g.gatewaySelector.Next()
+	if err != nil {
+		g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
+		return nil, nil, err
+	}
+	publicShare, err := g.getCS3PublicShareByID(ctx, permissionID)
 	switch {
 	case err == nil:
 		// the id is referencing a public share
@@ -546,13 +558,7 @@ func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID st
 		return permission, publicShare.GetResourceId(), nil
 	case IsSpaceRoot(itemID):
 		// itemID is referencing a spaceroot this is a space permission. Handle
-		// that next
-		gatewayClient, err := g.gatewaySelector.Next()
-		if err != nil {
-			g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
-			return nil, nil, err
-		}
-		// get space id
+		// that here and get space id
 		resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
 		if err != nil {
 			return nil, nil, err
@@ -573,7 +579,15 @@ func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID st
 		if err != nil {
 			return nil, nil, err
 		}
-		permission, err := g.cs3UserShareToPermission(ctx, share, false)
+		resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
+		if err != nil {
+			return nil, nil, err
+		}
+		condition, err := roleConditionForResourceType(resourceInfo)
+		if err != nil {
+			return nil, nil, err
+		}
+		permission, err := g.cs3UserShareToPermission(ctx, share, condition)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -590,6 +604,14 @@ func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID stri
 		return nil, err
 	}
 
+	resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
+	if err != nil {
+		return nil, err
+	}
+	condition, err := roleConditionForResourceType(resourceInfo)
+	if err != nil {
+		return nil, err
+	}
 	var cs3UpdateShareReq collaboration.UpdateShareRequest
 	// When updating a space root we need to reference the share by resourceId and grantee
 	if IsSpaceRoot(itemID) {
@@ -632,11 +654,6 @@ func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID stri
 				return nil, err
 			}
 
-			condition := unifiedrole.UnifiedRoleConditionGrantee
-			if IsSpaceRoot(itemID) {
-				condition = unifiedrole.UnifiedRoleConditionOwner
-			}
-			// FIXME: When setting permissions on a space, we need to use UnifiedRoleConditionOwner here
 			allowedResourceActions = unifiedrole.GetAllowedResourceActions(role, condition)
 			if len(allowedResourceActions) == 0 {
 				return nil, errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource")
@@ -667,11 +684,11 @@ func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID stri
 	}
 
 	updateUserShareResp, err := gatewayClient.UpdateShare(ctx, &cs3UpdateShareReq)
-	if errCode := errorcode.FromCS3Status(updateUserShareResp.GetStatus(), err); errCode != nil {
-		return nil, *errCode
+	if err := errorcode.FromCS3Status(updateUserShareResp.GetStatus(), err); err != nil {
+		return nil, err
 	}
 
-	permission, err := g.cs3UserShareToPermission(ctx, updateUserShareResp.GetShare(), IsSpaceRoot(itemID))
+	permission, err := g.cs3UserShareToPermission(ctx, updateUserShareResp.GetShare(), condition)
 	if err != nil {
 		return nil, err
 	}
diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go
index b8416210605..60527674520 100644
--- a/services/graph/pkg/service/v0/drives.go
+++ b/services/graph/pkg/service/v0/drives.go
@@ -27,7 +27,7 @@ import (
 	revactx "github.com/cs3org/reva/v2/pkg/ctx"
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/cs3org/reva/v2/pkg/utils"
-	"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/l10n"
 	"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
 	v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
@@ -78,7 +78,7 @@ func (g Graph) GetDrives(version APIVersion) http.HandlerFunc {
 func (g Graph) GetDrivesV1(w http.ResponseWriter, r *http.Request) {
 	spaces, errCode := g.getDrives(r, false, APIVersion_1)
 	if errCode != nil {
-		errCode.Render(w, r)
+		errorcode.RenderError(w, r, errCode)
 		return
 	}
 
@@ -99,7 +99,7 @@ func (g Graph) GetDrivesV1(w http.ResponseWriter, r *http.Request) {
 func (g Graph) GetDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
 	spaces, errCode := g.getDrives(r, false, APIVersion_1_Beta_1)
 	if errCode != nil {
-		errCode.Render(w, r)
+		errorcode.RenderError(w, r, errCode)
 		return
 	}
 
@@ -133,7 +133,7 @@ func (g Graph) GetAllDrives(version APIVersion) http.HandlerFunc {
 func (g Graph) GetAllDrivesV1(w http.ResponseWriter, r *http.Request) {
 	spaces, errCode := g.getDrives(r, true, APIVersion_1)
 	if errCode != nil {
-		errCode.Render(w, r)
+		errorcode.RenderError(w, r, errCode)
 		return
 	}
 
@@ -153,7 +153,7 @@ func (g Graph) GetAllDrivesV1(w http.ResponseWriter, r *http.Request) {
 func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
 	drives, errCode := g.getDrives(r, true, APIVersion_1_Beta_1)
 	if errCode != nil {
-		errCode.Render(w, r)
+		errorcode.RenderError(w, r, errCode)
 		return
 	}
 
@@ -168,7 +168,7 @@ func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
 }
 
 // getDrives implements the Service interface.
-func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, *errorcode.Error) {
+func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, error) {
 	logger := g.logger.SubloggerWithRequestID(r.Context())
 	logger.Info().
 		Interface("query", r.URL.Query()).
@@ -179,20 +179,20 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi
 	odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
 	if err != nil {
 		logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: query error")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, err.Error()))
+		return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
 	}
 	ctx := r.Context()
 
 	filters, err := generateCs3Filters(odataReq)
 	if err != nil {
 		logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: error parsing filters")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.NotSupported, err.Error()))
+		return nil, errorcode.New(errorcode.NotSupported, err.Error())
 	}
 	if !unrestricted {
 		user, ok := revactx.ContextGetUser(r.Context())
 		if !ok {
 			logger.Debug().Msg("could not create drive: invalid user")
-			return nil, conversions.ToPointer(errorcode.New(errorcode.AccessDenied, "invalid user"))
+			return nil, errorcode.New(errorcode.AccessDenied, "invalid user")
 		}
 		filters = append(filters, &storageprovider.ListStorageSpacesRequest_Filter{
 			Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_USER,
@@ -210,32 +210,32 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi
 	switch {
 	case err != nil:
 		logger.Error().Err(err).Msg("could not get drives: transport error")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
+		return nil, errorcode.New(errorcode.GeneralException, err.Error())
 	case res.Status.Code != cs3rpc.Code_CODE_OK:
 		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
 			// ok, empty return
 			return nil, nil
 		}
 		logger.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, res.Status.Message))
+		return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
 	}
 
 	webDavBaseURL, err := g.getWebDavBaseURL()
 	if err != nil {
 		logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drives: error parsing url")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
+		return nil, errorcode.New(errorcode.GeneralException, err.Error())
 	}
 
 	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion)
 	if err != nil {
 		logger.Debug().Err(err).Msg("could not get drives: error parsing grpc response")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
+		return nil, errorcode.New(errorcode.GeneralException, err.Error())
 	}
 
 	spaces, err = sortSpaces(odataReq, spaces)
 	if err != nil {
 		logger.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query")
-		return nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, err.Error()))
+		return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
 	}
 
 	return spaces, nil
diff --git a/services/graph/pkg/service/v0/l10n/locale/bg/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/bg/LC_MESSAGES/graph.po
index 1f9090f501d..b3255c4a901 100644
--- a/services/graph/pkg/service/v0/l10n/locale/bg/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/bg/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Martin <github@diemattels.at>, 2024\n"
 "Language-Team: Bulgarian (https://app.transifex.com/owncloud-org/teams/6149/bg/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: bg\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Тук можете да добавите описание на това пространство."
diff --git a/services/graph/pkg/service/v0/l10n/locale/de/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/de/LC_MESSAGES/graph.po
index 131b9e2506c..1f2ade6724f 100644
--- a/services/graph/pkg/service/v0/l10n/locale/de/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/de/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Martin <github@diemattels.at>, 2024\n"
 "Language-Team: German (https://app.transifex.com/owncloud-org/teams/6149/de/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: de\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Hier können Sie eine Beschreibung für diesen Space einfügen."
diff --git a/services/graph/pkg/service/v0/l10n/locale/en_GB/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/en_GB/LC_MESSAGES/graph.po
index fbf642e9c4e..9b547c240db 100644
--- a/services/graph/pkg/service/v0/l10n/locale/en_GB/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/en_GB/LC_MESSAGES/graph.po
@@ -2,21 +2,26 @@
 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 # This file is distributed under the same license as the PACKAGE package.
 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
+# 
+# Translators:
+# Andi Chandler <andi@gowling.com>, 2024
+# 
 #, fuzzy
-msgid   ""
-msgstr  "Project-Id-Version: \n"
-        "Report-Msgid-Bugs-To: EMAIL\n"
-        "POT-Creation-Date: 2024-03-26 15:11+0100\n"
-        "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-        "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-        "Language-Team: LANGUAGE <LL@li.org>\n"
-        "Language: \n"
-        "MIME-Version: 1.0\n"
-        "Content-Type: text/plain; charset=CHARSET\n"
-        "Content-Transfer-Encoding: 8bit\n"
-
-#: pkg/service/v0/spacetemplates.go:29
-msgid   "Here you can add a description for this Space."
-msgstr  ""
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: EMAIL\n"
+"POT-Creation-Date: 2024-04-13 01:20+0000\n"
+"PO-Revision-Date: 2024-04-04 00:17+0000\n"
+"Last-Translator: Andi Chandler <andi@gowling.com>, 2024\n"
+"Language-Team: English (United Kingdom) (https://app.transifex.com/owncloud-org/teams/6149/en_GB/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en_GB\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
+#. default description for new spaces
+#: pkg/service/v0/spacetemplates.go:37
+msgid "Here you can add a description for this Space."
+msgstr "Here you can add a description for this Space."
diff --git a/services/graph/pkg/service/v0/l10n/locale/es/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/es/LC_MESSAGES/graph.po
index 2b494c474d5..8a0d990ee8d 100644
--- a/services/graph/pkg/service/v0/l10n/locale/es/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/es/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Martin <github@diemattels.at>, 2024\n"
 "Language-Team: Spanish (https://app.transifex.com/owncloud-org/teams/6149/es/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: es\n"
 "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Aquí puede añadir una descripción para este espacio."
diff --git a/services/graph/pkg/service/v0/l10n/locale/fr/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/fr/LC_MESSAGES/graph.po
index ecc3ecc4944..8b27d281dfa 100644
--- a/services/graph/pkg/service/v0/l10n/locale/fr/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/fr/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Ldm Public <ldmpub@gmail.com>, 2024\n"
 "Language-Team: French (https://app.transifex.com/owncloud-org/teams/6149/fr/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: fr\n"
 "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Vous pouvez ajouter une description pour cet Espace ici."
diff --git a/services/graph/pkg/service/v0/l10n/locale/fr_FR/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/fr_FR/LC_MESSAGES/graph.po
index a6ebeaed13b..c5575214c5f 100644
--- a/services/graph/pkg/service/v0/l10n/locale/fr_FR/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/fr_FR/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-08 00:04+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: pr topi, 2024\n"
 "Language-Team: French (France) (https://app.transifex.com/owncloud-org/teams/6149/fr_FR/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: fr_FR\n"
 "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Vous pouvez ajouter ici une description pour cet Espace."
diff --git a/services/graph/pkg/service/v0/l10n/locale/ja_JP/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/ja_JP/LC_MESSAGES/graph.po
index 7e64f6fbaa0..3bc7323d7b4 100644
--- a/services/graph/pkg/service/v0/l10n/locale/ja_JP/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/ja_JP/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: hoshi1800 <hoshi1800@gmail.com>, 2024\n"
 "Language-Team: Japanese (Japan) (https://app.transifex.com/owncloud-org/teams/6149/ja_JP/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: ja_JP\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "ここでこのスペースの説明を追加できます。"
diff --git a/services/graph/pkg/service/v0/l10n/locale/ko/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/ko/LC_MESSAGES/graph.po
index 12d18d08621..130c50ecf77 100644
--- a/services/graph/pkg/service/v0/l10n/locale/ko/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/ko/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Martin <github@diemattels.at>, 2024\n"
 "Language-Team: Korean (https://app.transifex.com/owncloud-org/teams/6149/ko/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: ko\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "여기에 이 스페이스 대한 설명을 추가할 수 있습니다."
diff --git a/services/graph/pkg/service/v0/l10n/locale/nl/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/nl/LC_MESSAGES/graph.po
index a987b2076a4..6b517dce5c0 100644
--- a/services/graph/pkg/service/v0/l10n/locale/nl/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/nl/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: 2a344c4626711d8707658672303caf6a_6d7f053, 2024\n"
 "Language-Team: Dutch (https://app.transifex.com/owncloud-org/teams/6149/nl/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: nl\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Hier kan u een beschrijving toevoegen voor deze Space."
diff --git a/services/graph/pkg/service/v0/l10n/locale/pt_BR/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/pt_BR/LC_MESSAGES/graph.po
index a47e178d8e8..5c4679a8057 100644
--- a/services/graph/pkg/service/v0/l10n/locale/pt_BR/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/pt_BR/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-10 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Flávio Veras <flaviove@gmail.com>, 2024\n"
 "Language-Team: Portuguese (Brazil) (https://app.transifex.com/owncloud-org/teams/6149/pt_BR/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: pt_BR\n"
 "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Aqui você pode adicionar uma descrição para este Espaço."
diff --git a/services/graph/pkg/service/v0/l10n/locale/ru/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/ru/LC_MESSAGES/graph.po
index 9359465f5fd..3a44509dce1 100644
--- a/services/graph/pkg/service/v0/l10n/locale/ru/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/ru/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Alex <hostspepc@gmail.com>, 2024\n"
 "Language-Team: Russian (https://app.transifex.com/owncloud-org/teams/6149/ru/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: ru\n"
 "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Здесь вы можете добавить описание этого пространства."
diff --git a/services/graph/pkg/service/v0/l10n/locale/sq/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/sq/LC_MESSAGES/graph.po
index 9298a2a7390..6f4d32dc334 100644
--- a/services/graph/pkg/service/v0/l10n/locale/sq/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/sq/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Besnik Bleta <besnik@programeshqip.org>, 2024\n"
 "Language-Team: Albanian (https://app.transifex.com/owncloud-org/teams/6149/sq/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: sq\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Këtu mund të shtoni një përshkrim për këtë Hapësirë."
diff --git a/services/graph/pkg/service/v0/l10n/locale/tr/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/tr/LC_MESSAGES/graph.po
index 004d6e72d39..83b23e74517 100644
--- a/services/graph/pkg/service/v0/l10n/locale/tr/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/tr/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Martin <github@diemattels.at>, 2024\n"
 "Language-Team: Turkish (https://app.transifex.com/owncloud-org/teams/6149/tr/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: tr\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Buraya bu Alan için bir açıklama ekleyebilirsiniz."
diff --git a/services/graph/pkg/service/v0/l10n/locale/vi/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/vi/LC_MESSAGES/graph.po
index 9605aa37be5..b18d9a0a3e1 100644
--- a/services/graph/pkg/service/v0/l10n/locale/vi/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/vi/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Nguyễn Tiến Trình <tunglam062000@gmail.com>, 2024\n"
 "Language-Team: Vietnamese (https://app.transifex.com/owncloud-org/teams/6149/vi/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: vi\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "Ở đây bạn có thể thêm mô tả cho không gian này."
diff --git a/services/graph/pkg/service/v0/l10n/locale/zh/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/zh/LC_MESSAGES/graph.po
index 12c2c65dde1..4da8f6136ac 100644
--- a/services/graph/pkg/service/v0/l10n/locale/zh/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/zh/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Martin <github@diemattels.at>, 2024\n"
 "Language-Team: Chinese (https://app.transifex.com/owncloud-org/teams/6149/zh/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: zh\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "在此处添加此空间的描述。"
diff --git a/services/graph/pkg/service/v0/l10n/locale/zh_HK/LC_MESSAGES/graph.po b/services/graph/pkg/service/v0/l10n/locale/zh_HK/LC_MESSAGES/graph.po
index 6dff64597a4..b4c465e5c84 100644
--- a/services/graph/pkg/service/v0/l10n/locale/zh_HK/LC_MESSAGES/graph.po
+++ b/services/graph/pkg/service/v0/l10n/locale/zh_HK/LC_MESSAGES/graph.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-04-05 00:41+0000\n"
+"POT-Creation-Date: 2024-04-12 01:22+0000\n"
 "PO-Revision-Date: 2024-04-04 00:17+0000\n"
 "Last-Translator: Ann Li <dn.annli@gmail.com>, 2024\n"
 "Language-Team: Chinese (Hong Kong) (https://app.transifex.com/owncloud-org/teams/6149/zh_HK/)\n"
@@ -21,7 +21,7 @@ msgstr ""
 "Language: zh_HK\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
-#. text for the readme.md file
+#. default description for new spaces
 #: pkg/service/v0/spacetemplates.go:37
 msgid "Here you can add a description for this Space."
 msgstr "你可以在此添加這空間的描述"
diff --git a/services/graph/pkg/service/v0/sharedwithme.go b/services/graph/pkg/service/v0/sharedwithme.go
index 42d02d76c55..8d88206d272 100644
--- a/services/graph/pkg/service/v0/sharedwithme.go
+++ b/services/graph/pkg/service/v0/sharedwithme.go
@@ -34,9 +34,9 @@ func (g Graph) listSharedWithMe(ctx context.Context) ([]libregraph.DriveItem, er
 	}
 
 	listReceivedSharesResponse, err := gatewayClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
-	if errCode := errorcode.FromCS3Status(listReceivedSharesResponse.GetStatus(), err); errCode != nil {
+	if err := errorcode.FromCS3Status(listReceivedSharesResponse.GetStatus(), err); err != nil {
 		g.logger.Error().Err(err).Msg("listing shares failed")
-		return nil, *errCode
+		return nil, err
 	}
 
 	return cs3ReceivedSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, listReceivedSharesResponse.GetShares())
diff --git a/services/graph/pkg/service/v0/utils.go b/services/graph/pkg/service/v0/utils.go
index 55311185b67..525c6cdafc2 100644
--- a/services/graph/pkg/service/v0/utils.go
+++ b/services/graph/pkg/service/v0/utils.go
@@ -3,6 +3,7 @@ package svc
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"io"
 	"net/http"
 	"reflect"
@@ -16,6 +17,7 @@ import (
 	"golang.org/x/sync/errgroup"
 
 	libregraph "github.com/owncloud/libre-graph-api-go"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
@@ -166,18 +168,21 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 				},
 			})
 
-			switch errCode := errorcode.FromCS3Status(shareStat.GetStatus(), err); {
-			case errCode == nil:
-				break
+			var errCode errorcode.Error
+			errors.As(errorcode.FromCS3Status(shareStat.GetStatus(), err), &errCode)
+
+			switch {
 			// skip ItemNotFound shares, they might have been deleted in the meantime or orphans.
 			case errCode.GetCode() == errorcode.ItemNotFound:
 				return nil
+			case err == nil:
+				break
 			default:
 				logger.Error().Err(errCode).Msg("could not stat")
 				return errCode
 			}
 
-			driveItem, err := fillDriveItemPropertiesFromReceivedShare(ctx, logger, identityCache, receivedShares)
+			driveItem, err := fillDriveItemPropertiesFromReceivedShare(ctx, logger, identityCache, receivedShares, shareStat.GetInfo())
 			if err != nil {
 				return err
 			}
@@ -313,7 +318,8 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 }
 
 func fillDriveItemPropertiesFromReceivedShare(ctx context.Context, logger *log.Logger,
-	identityCache identity.IdentityCache, receivedShares []*collaboration.ReceivedShare) (*libregraph.DriveItem, error) {
+	identityCache identity.IdentityCache, receivedShares []*collaboration.ReceivedShare,
+	resourceInfo *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
 
 	driveItem := libregraph.NewDriveItem()
 	permissions := make([]libregraph.Permission, 0, len(receivedShares))
@@ -327,7 +333,7 @@ func fillDriveItemPropertiesFromReceivedShare(ctx context.Context, logger *log.L
 			oldestReceivedShare = receivedShare
 		}
 
-		permission, err := cs3ReceivedShareToLibreGraphPermissions(ctx, logger, identityCache, receivedShare)
+		permission, err := cs3ReceivedShareToLibreGraphPermissions(ctx, logger, identityCache, receivedShare, resourceInfo)
 		if err != nil {
 			return driveItem, err
 		}
@@ -387,7 +393,8 @@ func fillDriveItemPropertiesFromReceivedShare(ctx context.Context, logger *log.L
 }
 
 func cs3ReceivedShareToLibreGraphPermissions(ctx context.Context, logger *log.Logger,
-	identityCache identity.IdentityCache, receivedShare *collaboration.ReceivedShare) (*libregraph.Permission, error) {
+	identityCache identity.IdentityCache, receivedShare *collaboration.ReceivedShare,
+	resourceInfo *storageprovider.ResourceInfo) (*libregraph.Permission, error) {
 	permission := libregraph.NewPermission()
 	if id := receivedShare.GetShare().GetId().GetOpaqueId(); id != "" {
 		permission.SetId(id)
@@ -398,10 +405,11 @@ func cs3ReceivedShareToLibreGraphPermissions(ctx context.Context, logger *log.Lo
 	}
 
 	if permissionSet := receivedShare.GetShare().GetPermissions().GetPermissions(); permissionSet != nil {
-		role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(
-			*permissionSet,
-			unifiedrole.UnifiedRoleConditionGrantee,
-		)
+		condition, err := roleConditionForResourceType(resourceInfo)
+		if err != nil {
+			return nil, err
+		}
+		role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*permissionSet, condition)
 
 		if role != nil {
 			permission.SetRoles([]string{role.GetId()})
@@ -433,3 +441,16 @@ func cs3ReceivedShareToLibreGraphPermissions(ctx context.Context, logger *log.Lo
 
 	return permission, nil
 }
+
+func roleConditionForResourceType(ri *storageprovider.ResourceInfo) (string, error) {
+	switch {
+	case utils.ResourceIDEqual(ri.GetSpace().GetRoot(), ri.GetId()):
+		return unifiedrole.UnifiedRoleConditionDrive, nil
+	case ri.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER:
+		return unifiedrole.UnifiedRoleConditionFolder, nil
+	case ri.Type == storageprovider.ResourceType_RESOURCE_TYPE_FILE:
+		return unifiedrole.UnifiedRoleConditionFile, nil
+	default:
+		return "", errorcode.New(errorcode.InvalidRequest, "unsupported resource type")
+	}
+}
diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go
index 91c3dfa3634..c06d3d4bb15 100644
--- a/services/graph/pkg/unifiedrole/unifiedrole.go
+++ b/services/graph/pkg/unifiedrole/unifiedrole.go
@@ -28,12 +28,12 @@ const (
 	// UnifiedRoleManagerID Unified role manager id.
 	UnifiedRoleManagerID = "312c0871-5ef7-4b3a-85b6-0e4074c64049"
 
-	// UnifiedRoleConditionSelf defines constraint where the principal matches the target resource
-	UnifiedRoleConditionSelf = "@Subject.objectId == @Resource.objectId"
-	// UnifiedRoleConditionOwner defines constraints when the principal is the owner of the target resource
-	UnifiedRoleConditionOwner = "@Subject.objectId Any_of @Resource.owners"
-	// UnifiedRoleConditionGrantee does not exist in MS Graph, but we use it to express permissions on shared resources
-	UnifiedRoleConditionGrantee = "@Subject.objectId Any_of @Resource.grantee"
+	// UnifiedRoleConditionDrive defines constraint that matches a Driveroot/Spaceroot
+	UnifiedRoleConditionDrive = "exists @Resource.Root"
+	// UnifiedRoleConditionFolder defines constraints that matches a DriveItem representing a Folder
+	UnifiedRoleConditionFolder = "exists @Resource.Folder"
+	// UnifiedRoleConditionFile defines a constraint that matches a DriveItem representing a File
+	UnifiedRoleConditionFile = "exists @Resource.File"
 
 	DriveItemPermissionsCreate = "libre.graph/driveItem/permissions/create"
 	DriveItemChildrenCreate    = "libre.graph/driveItem/children/create"
@@ -68,7 +68,7 @@ var legacyNames map[string]string = map[string]string{
 	UnifiedRoleManagerID:     conversions.RoleManager,
 }
 
-// NewViewerUnifiedRole creates a viewer role. `sharing` indicates if sharing permission should be added
+// NewViewerUnifiedRole creates a viewer role.
 func NewViewerUnifiedRole() *libregraph.UnifiedRoleDefinition {
 	r := conversions.NewViewerRole()
 	return &libregraph.UnifiedRoleDefinition{
@@ -78,7 +78,11 @@ func NewViewerUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionGrantee),
+				Condition:              proto.String(UnifiedRoleConditionFile),
+			},
+			{
+				AllowedResourceActions: convert(r),
+				Condition:              proto.String(UnifiedRoleConditionFolder),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -95,14 +99,14 @@ func NewSpaceViewerUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionOwner),
+				Condition:              proto.String(UnifiedRoleConditionDrive),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
 	}
 }
 
-// NewEditorUnifiedRole creates an editor role. `sharing` indicates if sharing permission should be added
+// NewEditorUnifiedRole creates an editor role.
 func NewEditorUnifiedRole() *libregraph.UnifiedRoleDefinition {
 	r := conversions.NewEditorRole()
 	return &libregraph.UnifiedRoleDefinition{
@@ -112,7 +116,7 @@ func NewEditorUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionGrantee),
+				Condition:              proto.String(UnifiedRoleConditionFolder),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -129,7 +133,7 @@ func NewSpaceEditorUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionOwner),
+				Condition:              proto.String(UnifiedRoleConditionDrive),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -146,7 +150,7 @@ func NewFileEditorUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionGrantee),
+				Condition:              proto.String(UnifiedRoleConditionFile),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -163,7 +167,7 @@ func NewUploaderUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionGrantee),
+				Condition:              proto.String(UnifiedRoleConditionFolder),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -180,7 +184,7 @@ func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionOwner),
+				Condition:              proto.String(UnifiedRoleConditionDrive),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -219,19 +223,20 @@ func GetApplicableRoleDefinitionsForActions(actions []string, constraints string
 	definitions := make([]*libregraph.UnifiedRoleDefinition, 0, len(builtin))
 
 	for _, definition := range builtin {
-		definitionMatch := true
+		var definitionMatch bool
 
 		for _, permission := range definition.GetRolePermissions() {
 			if permission.GetCondition() != constraints {
-				definitionMatch = false
 				continue
 			}
 
-			for _, action := range permission.GetAllowedResourceActions() {
+			for i, action := range permission.GetAllowedResourceActions() {
 				if !slices.Contains(actions, action) {
-					definitionMatch = false
 					break
 				}
+				if i == len(permission.GetAllowedResourceActions())-1 {
+					definitionMatch = true
+				}
 			}
 
 			if definitionMatch {
@@ -239,30 +244,33 @@ func GetApplicableRoleDefinitionsForActions(actions []string, constraints string
 			}
 		}
 
-		if !definitionMatch {
-			continue
+		if definitionMatch {
+			definitions = append(definitions, definition)
 		}
 
-		definitions = append(definitions, definition)
 	}
 
-	return WeightRoleDefinitions(definitions, descending)
+	return WeightRoleDefinitions(definitions, constraints, descending)
 }
 
 // WeightRoleDefinitions sorts the provided role definitions by the number of permissions[n].actions they grant,
 // the implementation is optimistic and assumes that the weight relies on the number of available actions.
 // descending - false - sorts the roles from least to most permissions
 // descending - true - sorts the roles from most to least permissions
-func WeightRoleDefinitions(roleDefinitions []*libregraph.UnifiedRoleDefinition, descending bool) []*libregraph.UnifiedRoleDefinition {
+func WeightRoleDefinitions(roleDefinitions []*libregraph.UnifiedRoleDefinition, constraints string, descending bool) []*libregraph.UnifiedRoleDefinition {
 	slices.SortFunc(roleDefinitions, func(i, j *libregraph.UnifiedRoleDefinition) int {
 		var ia []string
 		for _, rp := range i.GetRolePermissions() {
-			ia = append(ia, rp.GetAllowedResourceActions()...)
+			if rp.GetCondition() == constraints {
+				ia = append(ia, rp.GetAllowedResourceActions()...)
+			}
 		}
 
 		var ja []string
 		for _, rp := range j.GetRolePermissions() {
-			ja = append(ja, rp.GetAllowedResourceActions()...)
+			if rp.GetCondition() == constraints {
+				ja = append(ja, rp.GetAllowedResourceActions()...)
+			}
 		}
 
 		switch descending {
diff --git a/services/ocs/pkg/config/config.go b/services/ocs/pkg/config/config.go
index 1c64050ac6e..4097ff4ef68 100644
--- a/services/ocs/pkg/config/config.go
+++ b/services/ocs/pkg/config/config.go
@@ -19,7 +19,6 @@ type Config struct {
 	Debug   Debug    `yaml:"debug"`
 
 	HTTP HTTP `yaml:"http"`
-	API  API  `yaml:"api"`
 
 	GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
 	GrpcClient    client.Client         `yaml:"-"`
@@ -39,7 +38,3 @@ type SigningKeys struct {
 	AuthUsername string        `yaml:"username" env:"OCIS_CACHE_AUTH_USERNAME;OCS_PRESIGNEDURL_SIGNING_KEYS_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"5.0"`
 	AuthPassword string        `yaml:"password" env:"OCIS_CACHE_AUTH_PASSWORD;OCS_PRESIGNEDURL_SIGNING_KEYS_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"5.0"`
 }
-
-type API struct {
-	ShowUserEmailInResults bool `yaml:"show_email_in_results" env:"OCIS_SHOW_USER_EMAIL_IN_RESULTS" desc:"Mask user email addresses in responses." introductionVersion:"5.1"`
-}


Test patch:
diff --git a/services/graph/pkg/errorcode/cs3_test.go b/services/graph/pkg/errorcode/cs3_test.go
index f4a1f60a198..5b160e7ad78 100644
--- a/services/graph/pkg/errorcode/cs3_test.go
+++ b/services/graph/pkg/errorcode/cs3_test.go
@@ -8,45 +8,44 @@ import (
 	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
 	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 
-	"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
 )
 
 func TestFromCS3Status(t *testing.T) {
 	var tests = []struct {
-		status *cs3rpc.Status
-		err    error
-		ignore []cs3rpc.Code
-		result *errorcode.Error
+		status   *cs3rpc.Status
+		err      error
+		ignore   []cs3rpc.Code
+		expected error
 	}{
-		{nil, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "unspecified error has occurred"))},
-		{nil, errors.New("test error"), nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "test error"))},
+		{nil, nil, nil, errorcode.New(errorcode.GeneralException, "unspecified error has occurred")},
+		{nil, errors.New("test error"), nil, errorcode.New(errorcode.GeneralException, "test error")},
 		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_OK}, nil, nil, nil},
 		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_NOT_FOUND}, nil, []cs3rpc.Code{cs3rpc.Code_CODE_NOT_FOUND}, nil},
 		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_PERMISSION_DENIED}, nil, []cs3rpc.Code{cs3rpc.Code_CODE_NOT_FOUND, cs3rpc.Code_CODE_PERMISSION_DENIED}, nil},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_NOT_FOUND, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.ItemNotFound, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_PERMISSION_DENIED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.AccessDenied, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNAUTHENTICATED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.Unauthenticated, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INVALID_ARGUMENT, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_ALREADY_EXISTS, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.NameAlreadyExists, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_FAILED_PRECONDITION, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNIMPLEMENTED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.NotSupported, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INVALID, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_CANCELLED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNKNOWN, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_RESOURCE_EXHAUSTED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_ABORTED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_OUT_OF_RANGE, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRange, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INTERNAL, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNAVAILABLE, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.ServiceNotAvailable, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_REDIRECTION, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INSUFFICIENT_STORAGE, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.QuotaLimitReached, "msg"))},
-		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_LOCKED, Message: "msg"}, nil, nil, conversions.ToPointer(errorcode.New(errorcode.ItemIsLocked, "msg"))},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_NOT_FOUND, Message: "msg"}, nil, nil, errorcode.New(errorcode.ItemNotFound, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_PERMISSION_DENIED, Message: "msg"}, nil, nil, errorcode.New(errorcode.AccessDenied, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNAUTHENTICATED, Message: "msg"}, nil, nil, errorcode.New(errorcode.Unauthenticated, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INVALID_ARGUMENT, Message: "msg"}, nil, nil, errorcode.New(errorcode.InvalidRequest, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_ALREADY_EXISTS, Message: "msg"}, nil, nil, errorcode.New(errorcode.NameAlreadyExists, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_FAILED_PRECONDITION, Message: "msg"}, nil, nil, errorcode.New(errorcode.InvalidRequest, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNIMPLEMENTED, Message: "msg"}, nil, nil, errorcode.New(errorcode.NotSupported, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INVALID, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_CANCELLED, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNKNOWN, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_RESOURCE_EXHAUSTED, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_ABORTED, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_OUT_OF_RANGE, Message: "msg"}, nil, nil, errorcode.New(errorcode.InvalidRange, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INTERNAL, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_UNAVAILABLE, Message: "msg"}, nil, nil, errorcode.New(errorcode.ServiceNotAvailable, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_REDIRECTION, Message: "msg"}, nil, nil, errorcode.New(errorcode.GeneralException, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_INSUFFICIENT_STORAGE, Message: "msg"}, nil, nil, errorcode.New(errorcode.QuotaLimitReached, "msg")},
+		{&cs3rpc.Status{Code: cs3rpc.Code_CODE_LOCKED, Message: "msg"}, nil, nil, errorcode.New(errorcode.ItemIsLocked, "msg")},
 	}
 
 	for _, test := range tests {
-		if output := errorcode.FromCS3Status(test.status, test.err, test.ignore...); !reflect.DeepEqual(output, test.result) {
-			t.Error("Test Failed: {} expected, received: {}", test.result, output)
+		if got := errorcode.FromCS3Status(test.status, test.err, test.ignore...); !reflect.DeepEqual(got, test.expected) {
+			t.Error("Test Failed: {} expected, received: {}", test.expected, got)
 		}
 	}
 }
@@ -55,9 +54,9 @@ func TestFromStat(t *testing.T) {
 	var tests = []struct {
 		stat   *provider.StatResponse
 		err    error
-		result *errorcode.Error
+		result error
 	}{
-		{nil, errors.New("some error"), conversions.ToPointer(errorcode.New(errorcode.GeneralException, "some error"))},
+		{nil, errors.New("some error"), errorcode.New(errorcode.GeneralException, "some error")},
 		{&provider.StatResponse{Status: &cs3rpc.Status{Code: cs3rpc.Code_CODE_OK}}, nil, nil},
 	}
 
diff --git a/services/graph/pkg/errorcode/errorcode_test.go b/services/graph/pkg/errorcode/errorcode_test.go
new file mode 100644
index 00000000000..003bdfc8f96
--- /dev/null
+++ b/services/graph/pkg/errorcode/errorcode_test.go
@@ -0,0 +1,45 @@
+package errorcode_test
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
+)
+
+type customErr struct{}
+
+func (customErr) Error() string {
+	return "some error"
+}
+
+func TestRenderError(t *testing.T) {
+	t.Parallel()
+
+	t.Run("errorcode.Error value error", func(t *testing.T) {
+		r := httptest.NewRequest("GET", "/", nil)
+		w := httptest.NewRecorder()
+		err := errorcode.New(errorcode.ItemNotFound, "test error")
+		errorcode.RenderError(w, r, err)
+		require.Equal(t, http.StatusNotFound, w.Code)
+	})
+
+	t.Run("errorcode.Error zero value error", func(t *testing.T) {
+		r := httptest.NewRequest("GET", "/", nil)
+		w := httptest.NewRecorder()
+		var err errorcode.Error
+		errorcode.RenderError(w, r, err)
+		require.Equal(t, http.StatusForbidden, w.Code)
+	})
+
+	t.Run("custom error", func(t *testing.T) {
+		r := httptest.NewRequest("GET", "/", nil)
+		w := httptest.NewRecorder()
+		var err customErr
+		errorcode.RenderError(w, r, err)
+		require.Equal(t, http.StatusInternalServerError, w.Code)
+	})
+}
diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go
index 3c2d25bb829..20ee36a42ad 100644
--- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go
+++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go
@@ -27,6 +27,10 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	libregraph "github.com/owncloud/libre-graph-api-go"
+	"github.com/stretchr/testify/mock"
+	"github.com/tidwall/gjson"
+	"google.golang.org/grpc"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/services/graph/mocks"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
@@ -35,9 +39,6 @@ import (
 	"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
 	svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
-	"github.com/stretchr/testify/mock"
-	"github.com/tidwall/gjson"
-	"google.golang.org/grpc"
 )
 
 var _ = Describe("DriveItemPermissionsService", func() {
@@ -103,6 +104,10 @@ var _ = Describe("DriveItemPermissionsService", func() {
 
 		BeforeEach(func() {
 			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
+			statResponse.Info = &provider.ResourceInfo{
+				Id:   &driveItemId,
+				Type: provider.ResourceType_RESOURCE_TYPE_FILE,
+			}
 
 			getGroupResponse = &grouppb.GetGroupResponse{
 				Status: status.NewOK(ctx),
@@ -158,7 +163,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(permission.GrantedToV2.Group.GetId()).To(Equal("2"))
 		})
 
-		It("with roles (happy path)", func() {
+		It("succeeds with file roles (happy path)", func() {
 			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
 			gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
 			driveItemInvite.Recipients = []libregraph.DriveRecipient{
@@ -173,7 +178,23 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.NewViewerUnifiedRole().GetId()))
 		})
 
-		It("fails with wrong role", func() {
+		It("succeeds with folder roles (happy path)", func() {
+			statResponse.Info.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER
+			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
+			gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
+			driveItemInvite.Recipients = []libregraph.DriveRecipient{
+				{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
+			}
+			driveItemInvite.Roles = []string{unifiedrole.NewEditorUnifiedRole().GetId()}
+
+			permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
+			Expect(err).ToNot(HaveOccurred())
+
+			Expect(permission.GetRoles()).To(HaveLen(1))
+			Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.NewEditorUnifiedRole().GetId()))
+		})
+
+		It("fails with when trying to set a space role on a file", func() {
 			driveItemInvite.Recipients = []libregraph.DriveRecipient{
 				{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
 			}
@@ -184,6 +205,29 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(permission).To(BeZero())
 		})
 
+		It("fails with when trying to set a folder role on a file", func() {
+			driveItemInvite.Recipients = []libregraph.DriveRecipient{
+				{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
+			}
+			driveItemInvite.Roles = []string{unifiedrole.NewEditorUnifiedRole().GetId()}
+			permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
+
+			Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource")))
+			Expect(permission).To(BeZero())
+		})
+
+		It("fails with when trying to set a file role on a folder", func() {
+			statResponse.Info.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER
+			driveItemInvite.Recipients = []libregraph.DriveRecipient{
+				{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
+			}
+			driveItemInvite.Roles = []string{unifiedrole.NewFileEditorUnifiedRole().GetId()}
+			permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
+
+			Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource")))
+			Expect(permission).To(BeZero())
+		})
+
 		It("with actions (happy path)", func() {
 			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
 			gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
@@ -245,6 +289,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			createShareResponse = &collaboration.CreateShareResponse{
 				Status: status.NewOK(ctx),
 			}
+			statResponse.Info = &provider.ResourceInfo{}
 		})
 
 		It("adds a user to a space as expected (happy path)", func() {
@@ -254,6 +299,10 @@ var _ = Describe("DriveItemPermissionsService", func() {
 				SpaceId:   "2",
 				OpaqueId:  "3",
 			}
+			statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root
+			statResponse.Info.Space = &provider.StorageSpace{
+				Root: listSpacesResponse.StorageSpaces[0].Root,
+			}
 
 			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil)
 			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
@@ -307,12 +356,13 @@ var _ = Describe("DriveItemPermissionsService", func() {
 				Status: status.NewOK(ctx),
 				Shares: []*collaboration.Share{},
 			}
-		})
-		It("populates allowedValues for files that are not shared", func() {
 			statResponse.Info = &provider.ResourceInfo{
 				Id:            &itemID,
+				Type:          provider.ResourceType_RESOURCE_TYPE_FILE,
 				PermissionSet: roleconversions.NewViewerRole().CS3ResourcePermissions(),
 			}
+		})
+		It("populates allowedValues for files that are not shared", func() {
 			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
 			gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
@@ -322,10 +372,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
 		})
 		It("returns one permission per share", func() {
-			statResponse.Info = &provider.ResourceInfo{
-				Id:            &itemID,
-				PermissionSet: roleconversions.NewEditorRole().CS3ResourcePermissions(),
-			}
+			statResponse.Info.PermissionSet = roleconversions.NewEditorRole().CS3ResourcePermissions()
 			listSharesResponse.Shares = []*collaboration.Share{
 				{
 					Id: &collaboration.ShareId{OpaqueId: "1"},
@@ -394,6 +441,10 @@ var _ = Describe("DriveItemPermissionsService", func() {
 					},
 				},
 			}
+			statResponse.Info = &provider.ResourceInfo{
+				Type:          provider.ResourceType_RESOURCE_TYPE_FILE,
+				PermissionSet: roleconversions.NewViewerRole().CS3ResourcePermissions(),
+			}
 		})
 
 		It("adds a user to a space as expected (happy path)", func() {
@@ -406,10 +457,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 
 			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil)
 			gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
-			statResponse.Info = &provider.ResourceInfo{
-				Id:            listSpacesResponse.StorageSpaces[0].Root,
-				PermissionSet: roleconversions.NewViewerRole().CS3ResourcePermissions(),
-			}
+			statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root
 			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			permissions, err := driveItemPermissionsService.ListSpaceRootPermissions(context.Background(), driveId)
 			Expect(err).ToNot(HaveOccurred())
@@ -649,7 +697,6 @@ var _ = Describe("DriveItemPermissionsService", func() {
 					},
 				},
 			}
-
 		})
 		It("fails when no share is found", func() {
 			getShareMockResponse.Share = nil
@@ -714,6 +761,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(res.Link.GetLibreGraphDisplayName() == TestLinkName)
 		})
 		It("updates the expiration date", func() {
+			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
 			getPublicShareMockResponse.Share = nil
 			getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found")
@@ -743,6 +791,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(res.GetExpirationDateTime().Equal(expiration)).To(BeTrue())
 		})
 		It("deletes the expiration date", func() {
+			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
 			getPublicShareMockResponse.Share = nil
 			getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found")
@@ -770,6 +819,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(ok).To(BeFalse())
 		})
 		It("fails to update the share permissions for a file share when setting a space specific role", func() {
+			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			getPublicShareMockResponse.Share = nil
 			getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found")
 			gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool {
@@ -788,6 +838,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(res).To(BeZero())
 		})
 		It("fails to update the space permissions for a space share when setting a file specific role", func() {
+			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			getPublicShareMockResponse.Share = nil
 			getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found")
 			gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil)
@@ -809,6 +860,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
 			Expect(res).To(BeZero())
 		})
 		It("updates the share permissions when changing the resource permission actions", func() {
+			gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
 			getPublicShareMockResponse.Share = nil
 			getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found")
 			gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil)
diff --git a/services/graph/pkg/service/v0/api_drives_drive_item_test.go b/services/graph/pkg/service/v0/api_drives_drive_item_test.go
index 960d58d7259..6c7f4684bea 100644
--- a/services/graph/pkg/service/v0/api_drives_drive_item_test.go
+++ b/services/graph/pkg/service/v0/api_drives_drive_item_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/services/graph/mocks"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
@@ -377,7 +378,7 @@ var _ = Describe("DrivesDriveItemService", func() {
 					Return(&collaborationv1beta1.GetReceivedShareResponse{}, errors.New("listing shares failed"))
 
 				err := drivesDriveItemService.UnmountShare(context.Background(), storageprovider.ResourceId{})
-				Expect(err).To(MatchError(&expectedError))
+				Expect(err).To(MatchError(expectedError))
 			})
 
 			It("uses the correct filters to get the shares", func() {
diff --git a/services/graph/pkg/service/v0/sharedbyme_test.go b/services/graph/pkg/service/v0/sharedbyme_test.go
index 6d4840686a7..e87ef7094b7 100644
--- a/services/graph/pkg/service/v0/sharedbyme_test.go
+++ b/services/graph/pkg/service/v0/sharedbyme_test.go
@@ -176,7 +176,8 @@ var _ = Describe("sharedbyme", func() {
 			Return(&provider.StatResponse{
 				Status: status.NewOK(ctx),
 				Info: &provider.ResourceInfo{
-					Id: userShare.ResourceId,
+					Id:   userShare.ResourceId,
+					Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER,
 				},
 			}, nil)
 
diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go
index 3c87114ab25..5b0e21ba916 100644
--- a/services/graph/pkg/unifiedrole/unifiedrole_test.go
+++ b/services/graph/pkg/unifiedrole/unifiedrole_test.go
@@ -24,12 +24,13 @@ var _ = Describe("unifiedroles", func() {
 			Expect(r.GetId()).To(Equal(unifiedRole.GetId()))
 
 		},
-		Entry(rConversions.RoleViewer, rConversions.NewViewerRole(), unifiedrole.NewViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
-		Entry(rConversions.RoleEditor, rConversions.NewEditorRole(), unifiedrole.NewEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
-		Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(), unifiedrole.NewFileEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
-		Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
-		Entry(rConversions.RoleSpaceViewer, rConversions.NewSpaceViewerRole(), unifiedrole.NewSpaceViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
-		Entry(rConversions.RoleSpaceEditor, rConversions.NewSpaceEditorRole(), unifiedrole.NewSpaceEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
+		Entry(rConversions.RoleViewer, rConversions.NewViewerRole(), unifiedrole.NewViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionFile),
+		Entry(rConversions.RoleViewer, rConversions.NewViewerRole(), unifiedrole.NewViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionFolder),
+		Entry(rConversions.RoleEditor, rConversions.NewEditorRole(), unifiedrole.NewEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionFolder),
+		Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(), unifiedrole.NewFileEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionFile),
+		Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionDrive),
+		Entry(rConversions.RoleSpaceViewer, rConversions.NewSpaceViewerRole(), unifiedrole.NewSpaceViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionDrive),
+		Entry(rConversions.RoleSpaceEditor, rConversions.NewSpaceEditorRole(), unifiedrole.NewSpaceEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionDrive),
 	)
 
 	DescribeTable("UnifiedRolePermissionsToCS3ResourcePermissions",
@@ -57,9 +58,9 @@ var _ = Describe("unifiedroles", func() {
 	)
 
 	DescribeTable("WeightRoleDefinitions",
-		func(roleDefinitions []*libregraph.UnifiedRoleDefinition, descending bool, expectedDefinitions []*libregraph.UnifiedRoleDefinition) {
+		func(roleDefinitions []*libregraph.UnifiedRoleDefinition, constraint string, descending bool, expectedDefinitions []*libregraph.UnifiedRoleDefinition) {
 
-			for i, generatedDefinition := range unifiedrole.WeightRoleDefinitions(roleDefinitions, descending) {
+			for i, generatedDefinition := range unifiedrole.WeightRoleDefinitions(roleDefinitions, constraint, descending) {
 				Expect(generatedDefinition.Id).To(Equal(expectedDefinitions[i].Id))
 			}
 		},
@@ -69,6 +70,7 @@ var _ = Describe("unifiedroles", func() {
 				unifiedrole.NewViewerUnifiedRole(),
 				unifiedrole.NewFileEditorUnifiedRole(),
 			},
+			unifiedrole.UnifiedRoleConditionFile,
 			false,
 			[]*libregraph.UnifiedRoleDefinition{
 				unifiedrole.NewViewerUnifiedRole(),
@@ -81,6 +83,7 @@ var _ = Describe("unifiedroles", func() {
 				unifiedrole.NewViewerUnifiedRole(),
 				unifiedrole.NewFileEditorUnifiedRole(),
 			},
+			unifiedrole.UnifiedRoleConditionFile,
 			true,
 			[]*libregraph.UnifiedRoleDefinition{
 				unifiedrole.NewFileEditorUnifiedRole(),
@@ -130,7 +133,7 @@ var _ = Describe("unifiedroles", func() {
 			Entry(
 				"ViewerUnifiedRole",
 				rolesToAction(unifiedrole.NewViewerUnifiedRole()),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFolder,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewViewerUnifiedRole(),
 				},
@@ -139,7 +142,7 @@ var _ = Describe("unifiedroles", func() {
 			Entry(
 				"ViewerUnifiedRole | share",
 				rolesToAction(unifiedrole.NewViewerUnifiedRole()),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFile,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewViewerUnifiedRole(),
 				},
@@ -148,17 +151,7 @@ var _ = Describe("unifiedroles", func() {
 			Entry(
 				"NewFileEditorUnifiedRole",
 				rolesToAction(unifiedrole.NewFileEditorUnifiedRole()),
-				unifiedrole.UnifiedRoleConditionGrantee,
-				[]*libregraph.UnifiedRoleDefinition{
-					unifiedrole.NewViewerUnifiedRole(),
-					unifiedrole.NewFileEditorUnifiedRole(),
-				},
-			),
-
-			Entry(
-				"NewFileEditorUnifiedRole - share",
-				rolesToAction(unifiedrole.NewFileEditorUnifiedRole()),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFile,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewViewerUnifiedRole(),
 					unifiedrole.NewFileEditorUnifiedRole(),
@@ -168,35 +161,31 @@ var _ = Describe("unifiedroles", func() {
 			Entry(
 				"NewEditorUnifiedRole",
 				rolesToAction(unifiedrole.NewEditorUnifiedRole()),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFolder,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewUploaderUnifiedRole(),
 					unifiedrole.NewViewerUnifiedRole(),
-					unifiedrole.NewFileEditorUnifiedRole(),
 					unifiedrole.NewEditorUnifiedRole(),
 				},
 			),
 
 			Entry(
-				"NewEditorUnifiedRole - share",
-				rolesToAction(unifiedrole.NewEditorUnifiedRole()),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				"GetBuiltinRoleDefinitionList",
+				rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...),
+				unifiedrole.UnifiedRoleConditionFile,
 				[]*libregraph.UnifiedRoleDefinition{
-					unifiedrole.NewUploaderUnifiedRole(),
 					unifiedrole.NewViewerUnifiedRole(),
 					unifiedrole.NewFileEditorUnifiedRole(),
-					unifiedrole.NewEditorUnifiedRole(),
 				},
 			),
 
 			Entry(
 				"GetBuiltinRoleDefinitionList",
 				rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFolder,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewUploaderUnifiedRole(),
 					unifiedrole.NewViewerUnifiedRole(),
-					unifiedrole.NewFileEditorUnifiedRole(),
 					unifiedrole.NewEditorUnifiedRole(),
 				},
 			),
@@ -204,7 +193,7 @@ var _ = Describe("unifiedroles", func() {
 			Entry(
 				"GetBuiltinRoleDefinitionList",
 				rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...),
-				unifiedrole.UnifiedRoleConditionOwner,
+				unifiedrole.UnifiedRoleConditionDrive,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewSpaceViewerUnifiedRole(),
 					unifiedrole.NewSpaceEditorUnifiedRole(),
@@ -212,29 +201,17 @@ var _ = Describe("unifiedroles", func() {
 				},
 			),
 
-			Entry(
-				"GetBuiltinRoleDefinitionList - share",
-				rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList()...),
-				unifiedrole.UnifiedRoleConditionGrantee,
-				[]*libregraph.UnifiedRoleDefinition{
-					unifiedrole.NewUploaderUnifiedRole(),
-					unifiedrole.NewViewerUnifiedRole(),
-					unifiedrole.NewFileEditorUnifiedRole(),
-					unifiedrole.NewEditorUnifiedRole(),
-				},
-			),
-
 			Entry(
 				"single",
 				[]string{unifiedrole.DriveItemQuotaRead},
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFile,
 				[]*libregraph.UnifiedRoleDefinition{},
 			),
 
 			Entry(
 				"mixed",
 				append(rolesToAction(unifiedrole.NewUploaderUnifiedRole()), unifiedrole.DriveItemQuotaRead),
-				unifiedrole.UnifiedRoleConditionGrantee,
+				unifiedrole.UnifiedRoleConditionFolder,
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewUploaderUnifiedRole(),
 				},
diff --git a/tests/acceptance/features/apiSharingNg/enableDisableShareSync.feature b/tests/acceptance/features/apiSharingNg/enableDisableShareSync.feature
index af20d52823f..fe2badcc1aa 100644
--- a/tests/acceptance/features/apiSharingNg/enableDisableShareSync.feature
+++ b/tests/acceptance/features/apiSharingNg/enableDisableShareSync.feature
@@ -229,3 +229,69 @@ Feature:  enable or disable sync of incoming shares
       | resource      |
       | textfile0.txt |
       | FolderToShare |
+
+
+  Scenario Outline: enable a group share sync shared from Project Space by only one user in a group
+    Given user "Carol" has been created with default attributes and without skeleton files
+    And the administrator has assigned the role "Space Admin" to user "Carol" using the Graph API
+    And group "grp1" has been created
+    And user "Alice" has been added to group "grp1"
+    And user "Brian" has been added to group "grp1"
+    And user "Alice" has disabled the auto-sync share
+    And user "Brian" has disabled the auto-sync share
+    And user "Carol" has created a space "NewSpace" with the default quota using the Graph API
+    And user "Carol" has created a folder "FolderToShare" in space "NewSpace"
+    And user "Carol" has uploaded a file inside space "NewSpace" with content "hello world" to "/textfile0.txt"
+    And user "Carol" has sent the following share invitation:
+      | resource        | <resource> |
+      | space           | NewSpace   |
+      | sharee          | grp1       |
+      | shareType       | group      |
+      | permissionsRole | Viewer     |
+    When user "Alice" enables sync of share "<resource>" offered by "Carol" from "NewSpace" space using the Graph API
+    Then the HTTP status code should be "201"
+    And the JSON data of the response should match
+      """
+      {
+        "type": "object",
+        "required": [
+          "@client.synchronize"
+        ],
+        "properties": {
+          "@client.synchronize": {
+            "const": true
+          }
+        }
+      }
+      """
+    And user "Alice" should have sync enabled for share "<resource>"
+    And user "Brian" should have sync disabled for share "<resource>"
+    Examples:
+      | resource      |
+      | textfile0.txt |
+      | FolderToShare |
+
+
+  Scenario Outline: disable group share sync shared from Project space by only one user in a group
+    Given user "Carol" has been created with default attributes and without skeleton files
+    And the administrator has assigned the role "Space Admin" to user "Carol" using the Graph API
+    And group "grp1" has been created
+    And user "Alice" has been added to group "grp1"
+    And user "Brian" has been added to group "grp1"
+    And user "Carol" has created a space "NewSpace" with the default quota using the Graph API
+    And user "Carol" has created a folder "FolderToShare" in space "NewSpace"
+    And user "Carol" has uploaded a file inside space "NewSpace" with content "hello world" to "/textfile0.txt"
+    And user "Carol" has sent the following share invitation:
+      | resource        | <resource> |
+      | space           | NewSpace   |
+      | sharee          | grp1       |
+      | shareType       | group      |
+      | permissionsRole | Viewer     |
+    When user "Alice" disables sync of share "<resource>" using the Graph API
+    Then the HTTP status code should be "200"
+    And user "Alice" should have sync disabled for share "<resource>"
+    And user "Brian" should have sync enabled for share "<resource>"
+    Examples:
+      | resource      |
+      | textfile0.txt |
+      | FolderToShare |
diff --git a/tests/acceptance/features/apiSharingNg/listPermissions.feature b/tests/acceptance/features/apiSharingNg/listPermissions.feature
index a3877df6fe8..5efcbb718c7 100644
--- a/tests/acceptance/features/apiSharingNg/listPermissions.feature
+++ b/tests/acceptance/features/apiSharingNg/listPermissions.feature
@@ -48,8 +48,8 @@ Feature: List a sharing permissions
           },
           "@libre.graph.permissions.roles.allowedValues": {
             "type": "array",
-            "minItems": 4,
-            "maxItems": 4,
+            "minItems": 3,
+            "maxItems": 3,
             "uniqueItems": true,
             "items": {
               "oneOf": [
@@ -138,41 +138,6 @@ Feature: List a sharing permissions
                         3
                       ]
                     },
-                    "description": {
-                      "type": "string",
-                      "enum": [
-                        "View, download and edit."
-                      ]
-                    },
-                    "displayName": {
-                      "type": "string",
-                      "enum": [
-                        "Can edit"
-                      ]
-                    },
-                    "id": {
-                      "type": "string",
-                      "enum": [
-                        "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a"
-                      ]
-                    }
-                  }
-                },
-                {
-                  "type": "object",
-                  "required": [
-                    "@libre.graph.weight",
-                    "description",
-                    "displayName",
-                    "id"
-                  ],
-                  "properties": {
-                    "@libre.graph.weight": {
-                      "type": "integer",
-                      "enum": [
-                        4
-                      ]
-                    },
                     "description": {
                       "type": "string",
                       "enum": [
@@ -594,4 +559,4 @@ Feature: List a sharing permissions
           }
         }
       }
-      """
\ No newline at end of file
+      """
diff --git a/tests/acceptance/features/apiSharingNg/shareInvitations.feature b/tests/acceptance/features/apiSharingNg/shareInvitations.feature
index b6307f87b78..187a62122ed 100644
--- a/tests/acceptance/features/apiSharingNg/shareInvitations.feature
+++ b/tests/acceptance/features/apiSharingNg/shareInvitations.feature
@@ -1570,7 +1570,7 @@ Feature: Send a sharing invitations
               "message": {
                 "type": "string",
                 "enum": [
-                  "cannot set the requested permissions on that type of resource"
+                  "role not applicable to this resource"
                 ]
               }
             }
@@ -2356,9 +2356,9 @@ Feature: Send a sharing invitations
       """
     Examples:
       | permissions-role | error-message                                                                                      |
-      | Space Viewer     | space type is not eligible for sharing                                                             |
-      | Space Editor     | space type is not eligible for sharing                                                             |
-      | Manager          | space type is not eligible for sharing                                                             |
+      | Space Viewer     | role not applicable to this resource								      |
+      | Space Editor     | role not applicable to this resource                                                               |
+      | Manager          | role not applicable to this resource                                                               |
       | Co Owner         | Key: 'DriveItemInvite.Roles' Error:Field validation for 'Roles' failed on the 'available_role' tag |