Repository: owncloud/ocis
Pull request #8642 (2024-03-13 18:39:02)

enhancement(sharing): Return space permissions when looking up space root
Make `GET https://localhost:9200/graph/v1beta1/drives/{{driveid}}/items/{{itemid}}/permissions` work for spaceroots

This is still missing link permission on the space root. 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 840659dd2a0..dd33411812c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -48,6 +48,7 @@ The following sections list the changes for unreleased.
 * Bugfix - Fix graph drive invite: [#8538](https://github.com/owncloud/ocis/pull/8538)
 * Bugfix - We now always select the next clients when autoaccepting shares: [#8570](https://github.com/owncloud/ocis/pull/8570)
 * Bugfix - Fix sharing invite on virtual drive: [#8609](https://github.com/owncloud/ocis/pull/8609)
+* Bugfix - Disable Multipart uploads: [#8666](https://github.com/owncloud/ocis/pull/8666)
 * Change - Change the default store for presigned keys to nats-js-kv: [#8419](https://github.com/owncloud/ocis/pull/8419)
 * Enhancement - Graphs endpoint for mounting and unmounting shares: [#7885](https://github.com/owncloud/ocis/pull/7885)
 * Enhancement - Add epub reader to web default apps: [#8410](https://github.com/owncloud/ocis/pull/8410)
@@ -105,6 +106,12 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/issues/8495
    https://github.com/owncloud/ocis/pull/8609
 
+* Bugfix - Disable Multipart uploads: [#8666](https://github.com/owncloud/ocis/pull/8666)
+
+   Disables multiparts uploads as they lead to high memory consumption
+
+   https://github.com/owncloud/ocis/pull/8666
+
 * Change - Change the default store for presigned keys to nats-js-kv: [#8419](https://github.com/owncloud/ocis/pull/8419)
 
    We wrapped the store service in a micro store implementation and changed the
diff --git a/changelog/unreleased/non-default-multipartuploads.md b/changelog/unreleased/non-default-multipartuploads.md
new file mode 100644
index 00000000000..91f5e331373
--- /dev/null
+++ b/changelog/unreleased/non-default-multipartuploads.md
@@ -0,0 +1,6 @@
+Bugfix: Disable Multipart uploads
+
+Disables multiparts uploads as they lead to high memory consumption
+
+https://github.com/owncloud/ocis/pull/8666
+
diff --git a/changelog/unreleased/sharing-ng-spacepermissions.md b/changelog/unreleased/sharing-ng-spacepermissions.md
new file mode 100644
index 00000000000..e703decea06
--- /dev/null
+++ b/changelog/unreleased/sharing-ng-spacepermissions.md
@@ -0,0 +1,7 @@
+Enhancement: The graph endpoints for listing permission works for spaces now
+
+We enhanced the 'graph/v1beta1/drives/{{driveid}}/items/{{itemid}}/permissions' endpoint
+to list permission of the space when the 'itemid' refers to a space root.
+
+https://github.com/owncloud/ocis/pull/8642
+https://github.com/owncloud/ocis/issues/8352
diff --git a/go.mod b/go.mod
index 505c2eb6191..859e7cb34fd 100644
--- a/go.mod
+++ b/go.mod
@@ -83,7 +83,7 @@ require (
 	github.com/spf13/afero v1.11.0
 	github.com/spf13/cobra v1.8.0
 	github.com/stretchr/testify v1.9.0
-	github.com/thejerf/suture/v4 v4.0.2
+	github.com/thejerf/suture/v4 v4.0.5
 	github.com/tidwall/gjson v1.17.1
 	github.com/tus/tusd v1.13.0
 	github.com/urfave/cli/v2 v2.27.1
@@ -91,7 +91,7 @@ require (
 	go-micro.dev/v4 v4.10.2
 	go.etcd.io/bbolt v1.3.9
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
-	go.opentelemetry.io/contrib/zpages v0.48.0
+	go.opentelemetry.io/contrib/zpages v0.49.0
 	go.opentelemetry.io/otel v1.24.0
 	go.opentelemetry.io/otel/exporters/jaeger v1.17.0
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
diff --git a/go.sum b/go.sum
index 7fa0bc2412a..d465ca97e72 100644
--- a/go.sum
+++ b/go.sum
@@ -2005,8 +2005,8 @@ github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE
 github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
 github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
 github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
-github.com/thejerf/suture/v4 v4.0.2 h1:VxIH/J8uYvqJY1+9fxi5GBfGRkRZ/jlSOP6x9HijFQc=
-github.com/thejerf/suture/v4 v4.0.2/go.mod h1:g0e8vwskm9tI0jRjxrnA6lSr0q6OfPdWJVX7G5bVWRs=
+github.com/thejerf/suture/v4 v4.0.5 h1:F1E/4FZwXWqvlWDKEUo6/ndLtxGAUzMmNqkrMknZbAA=
+github.com/thejerf/suture/v4 v4.0.5/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
 github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
 github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -2091,8 +2091,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
-go.opentelemetry.io/contrib/zpages v0.48.0 h1:7vnqarYRu2VYvUUwrLWXRk5HqPuCbtuZdgR4eaVeP3Y=
-go.opentelemetry.io/contrib/zpages v0.48.0/go.mod h1:fzewgBzh126TFUgnNhI9/1hjvnZes7wBKXAMQmyM01g=
+go.opentelemetry.io/contrib/zpages v0.49.0 h1:Wk217PkNBxcKWnIQpwtbZZE286K4ZY9uajnM5woSeLU=
+go.opentelemetry.io/contrib/zpages v0.49.0/go.mod h1:6alLi5mmkZWbAtZMRPd1ffIgkTcsU9OTHQF2NbSOhrQ=
 go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
 go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
 go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
diff --git a/services/frontend/pkg/config/config.go b/services/frontend/pkg/config/config.go
index f54cd693f3f..9cae9ab91a2 100644
--- a/services/frontend/pkg/config/config.go
+++ b/services/frontend/pkg/config/config.go
@@ -31,7 +31,7 @@ type Config struct {
 	UploadMaxChunkSize             int    `yaml:"upload_max_chunk_size" env:"FRONTEND_UPLOAD_MAX_CHUNK_SIZE" desc:"Sets the max chunk sizes in bytes for uploads via the clients." introductionVersion:"pre5.0"`
 	UploadHTTPMethodOverride       string `yaml:"upload_http_method_override" env:"FRONTEND_UPLOAD_HTTP_METHOD_OVERRIDE" desc:"Advise TUS to replace PATCH requests by POST requests." introductionVersion:"pre5.0"`
 	DefaultUploadProtocol          string `yaml:"default_upload_protocol" env:"FRONTEND_DEFAULT_UPLOAD_PROTOCOL" desc:"The default upload protocol to use in clients. Currently only 'tus' is avaliable. See the developer API documentation for more details about TUS." introductionVersion:"pre5.0"`
-	EnableResharing                bool   `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;FRONTEND_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing in the clients." introductionVersion:"pre5.0"`
+	EnableResharing                bool   `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;FRONTEND_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing in the clients." introductionVersion:"pre5.0" deprecationVersion:"5.0" deprecationInfo:"Resharing will be removed in the future."`
 	EnableFederatedSharingIncoming bool   `yaml:"enable_federated_sharing_incoming" env:"FRONTEND_ENABLE_FEDERATED_SHARING_INCOMING" desc:"Changing this value is NOT supported. Enables support for incoming federated sharing for clients. The backend behaviour is not changed." introductionVersion:"pre5.0"`
 	EnableFederatedSharingOutgoing bool   `yaml:"enable_federated_sharing_outgoing" env:"FRONTEND_ENABLE_FEDERATED_SHARING_OUTGOING" desc:"Changing this value is NOT supported. Enables support for outgoing federated sharing for clients. The backend behaviour is not changed." introductionVersion:"pre5.0"`
 	SearchMinLength                int    `yaml:"search_min_length" env:"FRONTEND_SEARCH_MIN_LENGTH" desc:"Minimum number of characters to enter before a client should start a search for Share receivers. This setting can be used to customize the user experience if e.g too many results are displayed." introductionVersion:"pre5.0"`
diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go
index a7bd8531d23..950760825ec 100644
--- a/services/graph/pkg/config/config.go
+++ b/services/graph/pkg/config/config.go
@@ -152,5 +152,5 @@ type ServiceAccount struct {
 
 // FilesSharing is the configuration for the files sharing
 type FilesSharing struct {
-	EnableResharing bool `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;GRAPH_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing." introductionVersion:"5.0"`
+	EnableResharing bool `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;GRAPH_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing." introductionVersion:"5.0" deprecationVersion:"5.0" deprecationInfo:"Resharing will be removed in the future."`
 }
diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go
index f6dd52b0a5b..45a5a32d961 100644
--- a/services/graph/pkg/service/v0/driveitems.go
+++ b/services/graph/pkg/service/v0/driveitems.go
@@ -408,14 +408,45 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
 	}
 
 	driveItems := make(driveItemsByResourceID)
-	driveItems, err = g.listUserShares(ctx, []*collaboration.Filter{
-		share.ResourceIDFilter(conversions.ToPointer(itemID)),
-	}, driveItems)
-	if err != nil {
-		errorcode.RenderError(w, r, err)
-		return
-	}
+	if IsSpaceRoot(statResponse.GetInfo().GetId()) {
+		// this is a space root, get permissions via storage space API
+		filters := []*storageprovider.ListStorageSpacesRequest_Filter{
+			listStorageSpacesIDFilter(statResponse.GetInfo().GetSpace().GetId().GetOpaqueId()),
+		}
+		res, err := g.ListStorageSpacesWithFilters(ctx, filters, true)
+		switch {
+		case err != nil:
+			g.logger.Error().Err(err).Msg("could not get drive: transport error")
+			errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+			return
+		case res.Status.Code != cs3rpc.Code_CODE_OK:
+			if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
+				// the client is doing a lookup for a specific space, therefore we need to return
+				// not found to the caller
+				g.logger.Debug().Msg("could not get drive: not found")
+				errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
+				return
+			}
+			g.logger.Debug().
+				Str("grpcmessage", res.GetStatus().GetMessage()).
+				Msg("could not get drive: grpc error")
+			errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
+			return
+		}
+		permissions := g.cs3PermissionsToLibreGraph(ctx, res.GetStorageSpaces()[0], APIVersion_1_Beta_1)
+		collectionOfPermissions.Value = permissions
+	} else {
+		// "normal" driveItem, populate user  permissions via share providers
+		driveItems, err = g.listUserShares(ctx, []*collaboration.Filter{
+			share.ResourceIDFilter(conversions.ToPointer(itemID)),
+		}, driveItems)
+		if err != nil {
+			errorcode.RenderError(w, r, err)
+			return
+		}
 
+	}
+	// finally get public shares, which are possible for spaceroots and "normal" resources
 	driveItems, err = g.listPublicShares(ctx, []*link.ListPublicSharesRequest_Filter{
 		publicshare.ResourceIDFilter(conversions.ToPointer(itemID)),
 	}, driveItems)
diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go
index fcf28430c69..e4f0df83987 100644
--- a/services/graph/pkg/service/v0/drives.go
+++ b/services/graph/pkg/service/v0/drives.go
@@ -25,7 +25,6 @@ import (
 	merrors "go-micro.dev/v4/errors"
 	"golang.org/x/sync/errgroup"
 
-	revaConversions "github.com/cs3org/reva/v2/pkg/conversions"
 	revactx "github.com/cs3org/reva/v2/pkg/ctx"
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/cs3org/reva/v2/pkg/utils"
@@ -78,7 +77,7 @@ func (g Graph) GetDrives(version APIVersion) http.HandlerFunc {
 // GetDrivesV1 attempts to retrieve the current users drives;
 // it lists all drives the current user has access to.
 func (g Graph) GetDrivesV1(w http.ResponseWriter, r *http.Request) {
-	spaces, errCode := g.getDrives(r, false)
+	spaces, errCode := g.getDrives(r, false, APIVersion_1)
 	if errCode != nil {
 		errCode.Render(w, r)
 		return
@@ -99,7 +98,7 @@ func (g Graph) GetDrivesV1(w http.ResponseWriter, r *http.Request) {
 // it includes the grantedtoV2 property
 // it uses unified roles instead of the cs3 representations
 func (g Graph) GetDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
-	spaces, errCode := g.getDrivesBeta(r, false)
+	spaces, errCode := g.getDrives(r, false, APIVersion_1_Beta_1)
 	if errCode != nil {
 		errCode.Render(w, r)
 		return
@@ -133,7 +132,7 @@ func (g Graph) GetAllDrives(version APIVersion) http.HandlerFunc {
 // GetAllDrivesV1 attempts to retrieve the current users drives;
 // it includes another user's drives, if the current user has the permission.
 func (g Graph) GetAllDrivesV1(w http.ResponseWriter, r *http.Request) {
-	spaces, errCode := g.getDrives(r, true)
+	spaces, errCode := g.getDrives(r, true, APIVersion_1)
 	if errCode != nil {
 		errCode.Render(w, r)
 		return
@@ -153,7 +152,7 @@ func (g Graph) GetAllDrivesV1(w http.ResponseWriter, r *http.Request) {
 // it includes the grantedtoV2 property
 // it uses unified roles instead of the cs3 representations
 func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
-	drives, errCode := g.getDrivesBeta(r, true)
+	drives, errCode := g.getDrives(r, true, APIVersion_1_Beta_1)
 	if errCode != nil {
 		errCode.Render(w, r)
 		return
@@ -169,66 +168,8 @@ func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-// getDrivesBeta retrieves the drives associated with the given request 'r'.
-// It updates the 'GrantedToIdentities' to 'GrantedToV2',
-// which represents the transition from legacy identity representation to a newer version.
-// It also maps the old role names to their new unified role identifiers.
-func (g Graph) getDrivesBeta(r *http.Request, unrestricted bool) ([]*libregraph.Drive, *errorcode.Error) {
-	drives, errCode := g.getDrives(r, unrestricted)
-	if errCode != nil {
-		return nil, errCode
-	}
-
-	for _, drive := range drives {
-		for i, permission := range drive.GetRoot().Permissions {
-			grantedToIdentities := permission.GetGrantedToIdentities()
-
-			if len(grantedToIdentities) < 1 {
-				continue
-			}
-
-			permission.GrantedToIdentities = nil
-			grantedToIdentity := grantedToIdentities[0]
-
-			permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
-				User:  grantedToIdentity.User,
-				Group: grantedToIdentity.Group,
-			}
-
-			for i, role := range permission.GetRoles() {
-
-				// v1 implementation for getDrives > ** > cs3PermissionsToLibreGraph
-				// does not use space related role names, we first need to resolve the correct descriptor.
-				switch role {
-				case revaConversions.RoleViewer:
-					role = revaConversions.RoleSpaceViewer
-				case revaConversions.RoleEditor:
-					role = revaConversions.RoleSpaceEditor
-				}
-
-				cs3Role := revaConversions.RoleFromName(role, g.config.FilesSharing.EnableResharing)
-				uniRole := unifiedrole.CS3ResourcePermissionsToUnifiedRole(
-					*cs3Role.CS3ResourcePermissions(),
-					unifiedrole.UnifiedRoleConditionOwner,
-					g.config.FilesSharing.EnableResharing,
-				)
-
-				if uniRole == nil {
-					continue
-				}
-
-				permission.Roles[i] = uniRole.GetId()
-			}
-
-			drive.Root.Permissions[i] = permission
-		}
-	}
-
-	return drives, nil
-}
-
 // getDrives implements the Service interface.
-func (g Graph) getDrives(r *http.Request, unrestricted bool) ([]*libregraph.Drive, *errorcode.Error) {
+func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, *errorcode.Error) {
 	logger := g.logger.SubloggerWithRequestID(r.Context())
 	logger.Info().
 		Interface("query", r.URL.Query()).
@@ -286,7 +227,7 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool) ([]*libregraph.Driv
 		return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
 	}
 
-	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces)
+	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()))
@@ -346,7 +287,7 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
 	}
-	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces)
+	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1)
 	if err != nil {
 		log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -493,7 +434,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
 		resp.StorageSpace.Opaque = utils.MergeOpaques(resp.GetStorageSpace().GetOpaque(), opaque)
 	}
 
-	newDrive, err := g.cs3StorageSpaceToDrive(r.Context(), webDavBaseURL, resp.GetStorageSpace())
+	newDrive, err := g.cs3StorageSpaceToDrive(r.Context(), webDavBaseURL, resp.GetStorageSpace(), APIVersion_1)
 	if err != nil {
 		logger.Debug().Err(err).Msg("could not create drive: error parsing drive")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -653,7 +594,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace})
+	spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1)
 	if err != nil {
 		logger.Debug().Err(err).Msg("could not update drive: error parsing grpc response")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -664,7 +605,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 	render.JSON(w, r, spaces[0])
 }
 
-func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace) ([]*libregraph.Drive, error) {
+func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion) ([]*libregraph.Drive, error) {
 	errg, ctx := errgroup.WithContext(ctx)
 	work := make(chan *storageprovider.StorageSpace, len(storageSpaces))
 	results := make(chan *libregraph.Drive, len(storageSpaces))
@@ -690,7 +631,7 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces
 	for i := 0; i < numWorkers; i++ {
 		errg.Go(func() error {
 			for storageSpace := range work {
-				res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace)
+				res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion)
 				if err != nil {
 					return err
 				}
@@ -781,7 +722,7 @@ func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*stor
 	return res, err
 }
 
-func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace) (*libregraph.Drive, error) {
+func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion) (*libregraph.Drive, error) {
 	logger := g.logger.SubloggerWithRequestID(ctx)
 	if space.Root == nil {
 		logger.Error().Msg("unable to parse space: space has no root")
@@ -793,7 +734,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
 	}
 	spaceID := storagespace.FormatResourceID(spaceRid)
 
-	permissions := g.cs3PermissionsToLibreGraph(ctx, space)
+	permissions := g.cs3PermissionsToLibreGraph(ctx, space, apiVersion)
 
 	drive := &libregraph.Drive{
 		Id:   libregraph.PtrString(spaceID),
@@ -889,7 +830,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
 	return drive, nil
 }
 
-func (g Graph) cs3PermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace) []libregraph.Permission {
+func (g Graph) cs3PermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission {
 	if space.Opaque == nil {
 		return nil
 	}
@@ -943,41 +884,57 @@ func (g Graph) cs3PermissionsToLibreGraph(ctx context.Context, space *storagepro
 		// libregraph.Identity and if we pass the pointer from the loop every identity
 		// will have the same id.
 		tmp := id
-		var identitySet libregraph.IdentitySet
+		isGroup := false
+		var identity libregraph.Identity
+		var err error
+		var p libregraph.Permission
 		if _, ok := groupsMap[id]; ok {
-			identity, err := groupIdToIdentity(ctx, g.identityCache, tmp)
+			identity, err = groupIdToIdentity(ctx, g.identityCache, tmp)
 			if err != nil {
 				g.logger.Warn().Str("groupid", tmp).Msg("Group not found by id")
 			}
-			identitySet = libregraph.IdentitySet{Group: &identity}
+			isGroup = true
 		} else {
-			identity, err := userIdToIdentity(ctx, g.identityCache, tmp)
+			identity, err = userIdToIdentity(ctx, g.identityCache, tmp)
 			if err != nil {
 				g.logger.Warn().Str("userid", tmp).Msg("User not found by id")
 			}
-			identitySet = libregraph.IdentitySet{User: &identity}
 		}
-
-		p := libregraph.Permission{
-			GrantedToIdentities: []libregraph.IdentitySet{identitySet},
+		switch apiVersion {
+		case APIVersion_1:
+			var identitySet libregraph.IdentitySet
+			if isGroup {
+				identitySet.SetGroup(identity)
+			} else {
+				identitySet.SetUser(identity)
+			}
+			p.SetGrantedToIdentities([]libregraph.IdentitySet{identitySet})
+		case APIVersion_1_Beta_1:
+			var identitySet libregraph.SharePointIdentitySet
+			if isGroup {
+				identitySet.SetGroup(identity)
+			} else {
+				identitySet.SetUser(identity)
+			}
+			p.SetId(identitySetToSpacePermissionID(identitySet))
+			p.SetGrantedToV2(identitySet)
 		}
 
 		if exp := permissionsExpirations[id]; exp != nil {
 			p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos())))
 		}
 
-		// we need to map the permissions to the roles
-		switch {
-		// having RemoveGrant qualifies you as a manager
-		case perm.RemoveGrant:
-			p.SetRoles([]string{"manager"})
-		// InitiateFileUpload means you are an editor
-		case perm.InitiateFileUpload:
-			p.SetRoles([]string{"editor"})
-		// Stat permission at least makes you a viewer
-		case perm.Stat:
-			p.SetRoles([]string{"viewer"})
+		if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*perm, unifiedrole.UnifiedRoleConditionOwner, false); role != nil {
+			switch apiVersion {
+			case APIVersion_1:
+				if r := unifiedrole.GetLegacyName(*role); r != "" {
+					p.SetRoles([]string{r})
+				}
+			case APIVersion_1_Beta_1:
+				p.SetRoles([]string{role.GetId()})
+			}
 		}
+
 		permissions = append(permissions, p)
 	}
 	return permissions
diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go
index bf268a49726..b7d6c4fcaa4 100644
--- a/services/graph/pkg/service/v0/users.go
+++ b/services/graph/pkg/service/v0/users.go
@@ -174,7 +174,7 @@ func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) {
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
 	}
-	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces)
+	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1)
 	if err != nil {
 		logger.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -513,7 +513,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
 			user.Drive = &libregraph.Drive{}
 		}
 		for _, sp := range lspr.GetStorageSpaces() {
-			d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp)
+			d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1)
 			if err != nil {
 				logger.Debug().Err(err).Interface("id", sp.Id).Msg("error converting space to drive")
 				continue
diff --git a/services/graph/pkg/service/v0/utils.go b/services/graph/pkg/service/v0/utils.go
index 270db47e342..ee5ccd03e98 100644
--- a/services/graph/pkg/service/v0/utils.go
+++ b/services/graph/pkg/service/v0/utils.go
@@ -131,6 +131,21 @@ func groupIdToIdentity(ctx context.Context, cache identity.IdentityCache, groupI
 	return identity, err
 }
 
+// identitySetToSpacePermissionID generates an Id for a permission from an identitySet. In libregraph
+// permissions need to have an id. For user share permission we just use the cs3 share id as the permission-id
+// As permissions on space to not map to a cs3 share we need something else of the ids. So we just
+// construct the id for the id of the user or group that the permission applies to and prefix that
+// with a "u:" for userids and "g:" for group ids.
+func identitySetToSpacePermissionID(identitySet libregraph.SharePointIdentitySet) (id string) {
+	switch {
+	case identitySet.HasUser():
+		id = "u:" + identitySet.User.GetId()
+	case identitySet.HasGroup():
+		id = "g:" + identitySet.Group.GetId()
+	}
+	return id
+}
+
 func cs3ReceivedSharesToDriveItems(ctx context.Context,
 	logger *log.Logger,
 	gatewayClient gateway.GatewayAPIClient,
@@ -138,25 +153,6 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 	resharing bool,
 	receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error) {
 
-	// doStat is a helper function that stat a resource.
-	doStat := func(resourceId *storageprovider.ResourceId) (*storageprovider.StatResponse, error) {
-		shareStat, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{
-			Ref: &storageprovider.Reference{ResourceId: resourceId},
-		})
-		switch errCode := errorcode.FromCS3Status(shareStat.GetStatus(), err); {
-		case errCode == nil:
-			break
-		// skip ItemNotFound shares, they might have been deleted in the meantime or orphans.
-		case errCode.GetCode() == errorcode.ItemNotFound:
-			return nil, nil
-		default:
-			logger.Error().Err(errCode).Msg("could not stat")
-			return nil, errCode
-		}
-
-		return shareStat, nil
-	}
-
 	ch := make(chan libregraph.DriveItem)
 	group := new(errgroup.Group)
 	// Set max concurrency
@@ -173,80 +169,29 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 
 		group.Go(func() error {
 			var err error // redeclare
-			shareStat, err := doStat(receivedShares[0].GetShare().GetResourceId())
-			if shareStat == nil || err != nil {
-				return err
+			shareStat, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{
+				Ref: &storageprovider.Reference{
+					ResourceId: receivedShares[0].GetShare().GetResourceId(),
+				},
+			})
+
+			switch errCode := errorcode.FromCS3Status(shareStat.GetStatus(), err); {
+			case errCode == nil:
+				break
+			// skip ItemNotFound shares, they might have been deleted in the meantime or orphans.
+			case errCode.GetCode() == errorcode.ItemNotFound:
+				return nil
+			default:
+				logger.Error().Err(errCode).Msg("could not stat")
+				return errCode
 			}
 
-			driveItem := libregraph.NewDriveItem()
-
-			permissions := make([]libregraph.Permission, 0, len(receivedShares))
-
-			var oldestReceivedShare *collaboration.ReceivedShare
-			for _, receivedShare := range receivedShares {
-				switch {
-				case oldestReceivedShare == nil:
-					fallthrough
-				case utils.TSToTime(receivedShare.GetShare().GetCtime()).Before(utils.TSToTime(oldestReceivedShare.GetShare().GetCtime())):
-					oldestReceivedShare = receivedShare
-				}
-
-				permission, err := cs3ReceivedShareToLibreGraphPermissions(ctx, logger, identityCache, resharing, receivedShare)
-				if err != nil {
-					return err
-				}
-
-				// If at least one of the shares was accepted, we consider the driveItem's synchronized
-				// flag enabled.
-				// Also we use the Mountpoint name of the first accepted mountpoint as the name of
-				// of the driveItem
-				if receivedShare.GetState() == collaboration.ShareState_SHARE_STATE_ACCEPTED {
-					driveItem.SetClientSynchronize(true)
-					if name := receivedShare.GetMountPoint().GetPath(); name != "" && driveItem.GetName() == "" {
-						driveItem.SetName(receivedShare.GetMountPoint().GetPath())
-					}
-				}
-
-				// if at least one share is marked as hidden, consider the whole driveItem to be hidden
-				if receivedShare.GetHidden() {
-					driveItem.SetUIHidden(true)
-				}
-
-				if userID := receivedShare.GetShare().GetCreator(); userID != nil {
-					identity, err := cs3UserIdToIdentity(ctx, identityCache, userID)
-					if err != nil {
-						logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get creator of the share")
-					}
-
-					permission.SetInvitation(
-						libregraph.SharingInvitation{
-							InvitedBy: &libregraph.IdentitySet{
-								User: &identity,
-							},
-						},
-					)
-				}
-				permissions = append(permissions, *permission)
-
+			driveItem, err := fillDriveItemPropertiesFromReceivedShare(ctx, logger, identityCache,
+				resharing, receivedShares)
+			if err != nil {
+				return err
 			}
 
-			// To stay compatible with the usershareprovider and the webdav
-			// service the id of the driveItem is composed of the StorageID and
-			// SpaceID of the sharestorage appended with the opaque ID of
-			// the oldest share for the resource:
-			// '<sharestorageid>$<sharespaceid>!<share-opaque-id>
-			// Note: This means that the driveitem ID will change when the oldest
-			//   share is removed. It would be good to have are more stable ID here (e.g.
-			//   derived from the shared resource's ID. But as we need to use the same
-			//   ID across all services this means we needed to make similar adjustments
-			//   to the sharejail (usershareprovider, webdav). Which we can't currently do
-			//   as some clients rely on the IDs used there having a special format.
-			driveItem.SetId(storagespace.FormatResourceID(storageprovider.ResourceId{
-				StorageId: utils.ShareStorageProviderID,
-				OpaqueId:  oldestReceivedShare.GetShare().GetId().GetOpaqueId(),
-				SpaceId:   utils.ShareStorageSpaceID,
-			}))
-
 			if !driveItem.HasUIHidden() {
 				driveItem.SetUIHidden(false)
 			}
@@ -257,7 +202,7 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 				}
 			}
 
-			remoteItem := libregraph.NewRemoteItem()
+			remoteItem := driveItem.RemoteItem
 			{
 				if id := shareStat.GetInfo().GetId(); id != nil {
 					remoteItem.SetId(storagespace.FormatResourceID(*id))
@@ -354,14 +299,6 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 					remoteItem.File = file
 					driveItem.File = file
 				}
-
-				if len(permissions) > 0 {
-					remoteItem.Permissions = permissions
-				}
-
-				if !reflect.ValueOf(*remoteItem).IsZero() {
-					driveItem.RemoteItem = remoteItem
-				}
 			}
 
 			ch <- *driveItem
@@ -385,6 +322,81 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
 	return driveItems, err
 }
 
+func fillDriveItemPropertiesFromReceivedShare(ctx context.Context, logger *log.Logger,
+	identityCache identity.IdentityCache, resharing bool,
+	receivedShares []*collaboration.ReceivedShare) (*libregraph.DriveItem, error) {
+
+	driveItem := libregraph.NewDriveItem()
+	permissions := make([]libregraph.Permission, 0, len(receivedShares))
+
+	var oldestReceivedShare *collaboration.ReceivedShare
+	for _, receivedShare := range receivedShares {
+		switch {
+		case oldestReceivedShare == nil:
+			fallthrough
+		case utils.TSToTime(receivedShare.GetShare().GetCtime()).Before(utils.TSToTime(oldestReceivedShare.GetShare().GetCtime())):
+			oldestReceivedShare = receivedShare
+		}
+
+		permission, err := cs3ReceivedShareToLibreGraphPermissions(ctx, logger, identityCache, resharing, receivedShare)
+		if err != nil {
+			return driveItem, err
+		}
+
+		// If at least one of the shares was accepted, we consider the driveItem's synchronized
+		// flag enabled.
+		// Also we use the Mountpoint name of the first accepted mountpoint as the name for
+		// the driveItem
+		if receivedShare.GetState() == collaboration.ShareState_SHARE_STATE_ACCEPTED {
+			driveItem.SetClientSynchronize(true)
+			if name := receivedShare.GetMountPoint().GetPath(); name != "" && driveItem.GetName() == "" {
+				driveItem.SetName(receivedShare.GetMountPoint().GetPath())
+			}
+		}
+
+		// if at least one share is marked as hidden, consider the whole driveItem to be hidden
+		if receivedShare.GetHidden() {
+			driveItem.SetUIHidden(true)
+		}
+
+		if userID := receivedShare.GetShare().GetCreator(); userID != nil {
+			identity, err := cs3UserIdToIdentity(ctx, identityCache, userID)
+			if err != nil {
+				logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get creator of the share")
+			}
+
+			permission.SetInvitation(
+				libregraph.SharingInvitation{
+					InvitedBy: &libregraph.IdentitySet{
+						User: &identity,
+					},
+				},
+			)
+		}
+		permissions = append(permissions, *permission)
+		// To stay compatible with the usershareprovider and the webdav
+		// service the id of the driveItem is composed of the StorageID and
+		// SpaceID of the sharestorage appended with the opaque ID of
+		// the oldest share for the resource:
+		// '<sharestorageid>$<sharespaceid>!<share-opaque-id>
+		// Note: This means that the driveitem ID will change when the oldest
+		//   share is removed. It would be good to have are more stable ID here (e.g.
+		//   derived from the shared resource's ID. But as we need to use the same
+		//   ID across all services this means we needed to make similar adjustments
+		//   to the sharejail (usershareprovider, webdav). Which we can't currently do
+		//   as some clients rely on the IDs used there having a special format.
+		driveItem.SetId(storagespace.FormatResourceID(storageprovider.ResourceId{
+			StorageId: utils.ShareStorageProviderID,
+			OpaqueId:  oldestReceivedShare.GetShare().GetId().GetOpaqueId(),
+			SpaceId:   utils.ShareStorageSpaceID,
+		}))
+
+	}
+	driveItem.RemoteItem = libregraph.NewRemoteItem()
+	driveItem.RemoteItem.Permissions = permissions
+	return driveItem, nil
+}
+
 func cs3ReceivedShareToLibreGraphPermissions(ctx context.Context, logger *log.Logger,
 	identityCache identity.IdentityCache, resharing bool, receivedShare *collaboration.ReceivedShare) (*libregraph.Permission, error) {
 	permission := libregraph.NewPermission()
diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go
index fce338767c1..2926299ea82 100644
--- a/services/graph/pkg/unifiedrole/unifiedrole.go
+++ b/services/graph/pkg/unifiedrole/unifiedrole.go
@@ -23,8 +23,6 @@ const (
 	UnifiedRoleSpaceEditorID = "58c63c02-1d89-4572-916a-870abc5a1b7d"
 	// UnifiedRoleFileEditorID Unified role file editor id.
 	UnifiedRoleFileEditorID = "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a"
-	// UnifiedRoleCoownerID Unified role coowner id.
-	UnifiedRoleCoownerID = "3a4ba8e9-6a0d-4235-9140-0e7a34007abe"
 	// UnifiedRoleUploaderID Unified role uploader id.
 	UnifiedRoleUploaderID = "1c996275-f1c9-4e71-abdf-a42f6495e960"
 	// UnifiedRoleManagerID Unified role manager id.
@@ -58,6 +56,18 @@ const (
 	DriveItemPermissionsDeny   = "libre.graph/driveItem/permissions/deny"
 )
 
+var legacyNames map[string]string = map[string]string{
+	UnifiedRoleViewerID: conversions.RoleViewer,
+	// one V1 api the "spaceviewer" role was call "viewer" and the "spaceeditor" was "editor",
+	// we need to stay compatible with that
+	UnifiedRoleSpaceViewerID: "viewer",
+	UnifiedRoleSpaceEditorID: "editor",
+	UnifiedRoleEditorID:      conversions.RoleEditor,
+	UnifiedRoleFileEditorID:  conversions.RoleFileEditor,
+	UnifiedRoleUploaderID:    conversions.RoleUploader,
+	UnifiedRoleManagerID:     conversions.RoleManager,
+}
+
 // NewViewerUnifiedRole creates a viewer role. `sharing` indicates if sharing permission should be added
 func NewViewerUnifiedRole(sharing bool) *libregraph.UnifiedRoleDefinition {
 	r := conversions.NewViewerRole(sharing)
@@ -143,23 +153,6 @@ func NewFileEditorUnifiedRole(sharing bool) *libregraph.UnifiedRoleDefinition {
 	}
 }
 
-// NewCoownerUnifiedRole creates a coowner role.
-func NewCoownerUnifiedRole() *libregraph.UnifiedRoleDefinition {
-	r := conversions.NewCoownerRole()
-	return &libregraph.UnifiedRoleDefinition{
-		Id:          proto.String(UnifiedRoleCoownerID),
-		Description: proto.String("Grants co-owner permissions on a resource"),
-		DisplayName: displayName(r),
-		RolePermissions: []libregraph.UnifiedRolePermission{
-			{
-				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionOwner),
-			},
-		},
-		LibreGraphWeight: proto.Int32(0),
-	}
-}
-
 // NewUploaderUnifiedRole creates an uploader role
 func NewUploaderUnifiedRole() *libregraph.UnifiedRoleDefinition {
 	r := conversions.NewUploaderRole()
@@ -214,7 +207,6 @@ func GetBuiltinRoleDefinitionList(resharing bool) []*libregraph.UnifiedRoleDefin
 		NewEditorUnifiedRole(resharing),
 		NewSpaceEditorUnifiedRole(),
 		NewFileEditorUnifiedRole(resharing),
-		NewCoownerUnifiedRole(),
 		NewUploaderUnifiedRole(),
 		NewManagerUnifiedRole(),
 	}
@@ -223,9 +215,10 @@ func GetBuiltinRoleDefinitionList(resharing bool) []*libregraph.UnifiedRoleDefin
 // GetApplicableRoleDefinitionsForActions returns a list of role definitions
 // that match the provided actions and constraints
 func GetApplicableRoleDefinitionsForActions(actions []string, constraints string, resharing, descending bool) []*libregraph.UnifiedRoleDefinition {
-	var definitions []*libregraph.UnifiedRoleDefinition
+	builtin := GetBuiltinRoleDefinitionList(resharing)
+	definitions := make([]*libregraph.UnifiedRoleDefinition, 0, len(builtin))
 
-	for _, definition := range GetBuiltinRoleDefinitionList(resharing) {
+	for _, definition := range builtin {
 		definitionMatch := true
 
 		for _, permission := range definition.GetRolePermissions() {
@@ -403,6 +396,10 @@ func CS3ResourcePermissionsToLibregraphActions(p provider.ResourcePermissions) (
 	return actions
 }
 
+func GetLegacyName(role libregraph.UnifiedRoleDefinition) string {
+	return legacyNames[role.GetId()]
+}
+
 // CS3ResourcePermissionsToUnifiedRole tries to find the UnifiedRoleDefinition that matches the supplied
 // CS3 ResourcePermissions and constraints.
 func CS3ResourcePermissionsToUnifiedRole(p provider.ResourcePermissions, constraints string, resharing bool) *libregraph.UnifiedRoleDefinition {
@@ -463,8 +460,6 @@ func displayName(role *conversions.Role) *string {
 		displayName = "Space Editor"
 	case conversions.RoleFileEditor:
 		displayName = "File Editor"
-	case conversions.RoleCoowner:
-		displayName = "Co Owner"
 	case conversions.RoleUploader:
 		displayName = "Uploader"
 	case conversions.RoleManager:
diff --git a/services/idp/pnpm-lock.yaml b/services/idp/pnpm-lock.yaml
index fd91a2ce35f..dfb1d11bf2d 100644
--- a/services/idp/pnpm-lock.yaml
+++ b/services/idp/pnpm-lock.yaml
@@ -3810,7 +3810,7 @@ packages:
   /axios@1.6.1:
     resolution: {integrity: sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==}
     dependencies:
-      follow-redirects: 1.15.4
+      follow-redirects: 1.15.6
       form-data: 4.0.0
       proxy-from-env: 1.1.0
     transitivePeerDependencies:
@@ -6235,8 +6235,8 @@ packages:
       readable-stream: 2.3.8
     dev: true
 
-  /follow-redirects@1.15.4:
-    resolution: {integrity: sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==}
+  /follow-redirects@1.15.6:
+    resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
     engines: {node: '>=4.0'}
     peerDependencies:
       debug: '*'
diff --git a/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po
index 445a465c6d7..ce3a8deeca2 100644
--- a/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po
+++ b/services/notifications/pkg/email/l10n/locale/tr/LC_MESSAGES/notifications.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-02-23 00:46+0000\n"
+"POT-Creation-Date: 2024-03-15 00:04+0000\n"
 "PO-Revision-Date: 2023-04-19 11:11+0000\n"
 "Last-Translator: Begüm Topyıldız <bgmtpyldz@gmail.com>, 2023\n"
 "Language-Team: Turkish (https://app.transifex.com/owncloud-org/teams/6149/tr/)\n"
diff --git a/services/sharing/pkg/config/config.go b/services/sharing/pkg/config/config.go
index 495c7ec03fa..9ba270cb837 100644
--- a/services/sharing/pkg/config/config.go
+++ b/services/sharing/pkg/config/config.go
@@ -21,7 +21,7 @@ type Config struct {
 
 	SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"SHARING_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"pre5.0"`
 
-	EnableResharing bool `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;SHARING_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing." introductionVersion:"5.0"`
+	EnableResharing bool `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;SHARING_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing." introductionVersion:"5.0" deprecationVersion:"5.0" deprecationInfo:"Resharing will be removed in the future."`
 
 	UserSharingDriver              string               `yaml:"user_sharing_driver" env:"SHARING_USER_DRIVER" desc:"Driver to be used to persist shares. Supported values are 'jsoncs3', 'json', 'cs3' (deprecated) and 'owncloudsql'." introductionVersion:"pre5.0"`
 	UserSharingDrivers             UserSharingDrivers   `yaml:"user_sharing_drivers"`
diff --git a/services/storage-users/README.md b/services/storage-users/README.md
index bee130106f7..c59ab5584ab 100644
--- a/services/storage-users/README.md
+++ b/services/storage-users/README.md
@@ -20,6 +20,15 @@ When hard-stopping Infinite Scale, for example with the `kill <pid>` command (SI
 
 ## CLI Commands
 
+For any command listed, use `--help` to get more details and possible options and arguments.
+
+To authenticate CLI commands use:
+
+*   `OCIS_SERVICE_ACCOUNT_SECRET=<acc-secret>` and
+*   `OCIS_SERVICE_ACCOUNT_ID=<acc-id>`.
+
+The `storage-users` CLI tool uses the default address to establish the connection to the `gateway` service. If the connection fails, check your custom `gateway` service `GATEWAY_GRPC_ADDR` configuration and set the same address in `storage-users` `OCIS_GATEWAY_GRPC_ADDR` or `STORAGE_USERS_GATEWAY_GRPC_ADDR`.
+
 ### Manage Unfinished Uploads
 
 <!-- referencing: [oCIS FS] clean up aborted uploads https://github.com/owncloud/ocis/issues/2622 -->
@@ -105,89 +114,60 @@ Incomplete uploads:
  - 455bd640-cd08-46e8-a5a0-9304908bd40a (file_example_PPT_1MB.ppt, Size: 1028608, Expires: 2022-08-17T12:35:34+02:00)
 ```
 
-### Purge Expired Space Trash-Bins Items
-
-<!-- referencing: https://github.com/owncloud/ocis/pull/5500 -->
+### Manage Trash-Bin Items
 
-This command is about the trash-bin to get an overview of items, restore items and purging old items of `project` spaces (spaces that have been created manually) and `personal` spaces.
+This command set provides commands to get an overview of trash-bin items, restore items and purge old items of `personal` spaces and `project` spaces (spaces that have been created manually). `trash-bin` commands require a `spaceID` as parameter. See [List all spaces
+](https://owncloud.dev/apis/http/graph/spaces/#list-all-spaces-get-drives) or [Listing Space IDs](https://doc.owncloud.com/ocis/5.0/maintenance/space-ids/space-ids.html) for details of how to get them.
 
 ```bash
 ocis storage-users trash-bin <command>
 ```
 
-#### Purge-expired
 ```plaintext
 COMMANDS:
-   purge-expired     Purge all expired items from the trashbin
+   purge-expired  Purge expired trash-bin items
+   list           Print a list of all trash-bin items of a space.
+   restore-all    Restore all trash-bin items for a space.
+   restore        Restore a trash-bin item by ID.
 ```
 
-The configuration for the `purge-expired` command is done by using the following environment variables.
-
-*   `STORAGE_USERS_PURGE_TRASH_BIN_USER_ID` is used to obtain space trash-bin information and takes the system admin user as the default which is the `OCIS_ADMIN_USER_ID` but can be set individually. It should be noted, that the `OCIS_ADMIN_USER_ID` is only assigned automatically when using the single binary deployment and must be manually assigned in all other deployments. The command only considers spaces to which the assigned user has access and delete permission.
+#### Purge Expired
 
-*   `STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE` has a default value of `30 days`, which means the command will delete all files older than `30 days`. The value is human-readable, valid values are `24h`, `60m`, `60s` etc. `0` is equivalent to disable and prevents the deletion of `personal space` trash-bin files.
-
-*   `STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE` has a default value of `30 days`, which means the command will delete all files older than `30 days`. The value is human-readable, valid values are `24h`, `60m`, `60s` etc. `0` is equivalent to disable and prevents the deletion of `project space` trash-bin files.
-
-#### List and Restore Trash-Bins Items
-
-To authenticate the cli command use `OCIS_SERVICE_ACCOUNT_SECRET=<acc-secret>` and `OCIS_SERVICE_ACCOUNT_ID=<acc-id>`. The `storage-users` cli tool uses the default address to establish the connection to the `gateway` service. If the connection is failed check your custom `gateway`
-service `GATEWAY_GRPC_ADDR` configuration and set the same address to `storage-users` variable `OCIS_GATEWAY_GRPC_ADDR` or `STORAGE_USERS_GATEWAY_GRPC_ADDR`. The variable `STORAGE_USERS_CLI_MAX_ATTEMPTS_RENAME_FILE`
-defines a maximum number of attempts to rename a file when the user restores the file with `--option keep-both` to existing destination with the same name.
-
-The ID sources:
--   personal 'spaceID' in a `https://{host}/graph/v1.0/me/drives?$filter=driveType+eq+personal`
--   project 'spaceID' in a `https://{host}/graph/v1.0/me/drives?$filter=driveType+eq+project`
+Purge all expired items from the trash-bin.
 
 ```bash
-NAME:
-   ocis storage-users trash-bin list - Print a list of all trash-bin items of a space.
-
-USAGE:
-   ocis storage-users trash-bin list command [command options] ['spaceID' required]
-
-COMMANDS:
-   help, h  Shows a list of commands or help for one command
-
-OPTIONS:
-   --verbose, -v  Get more verbose output (default: false)
-   --help, -h     show help
-
+ocis storage-users trash-bin purge-expired
 ```
 
-```bash
-NAME:
-   ocis storage-users trash-bin restore-all - Restore all trash-bin items for a space.
+The behaviour of the `purge-expired` command can be configured by using the following environment variables.
 
-USAGE:
-   ocis storage-users trash-bin restore-all command [command options] ['spaceID' required]
+*   `STORAGE_USERS_PURGE_TRASH_BIN_USER_ID`  
+Used to obtain space trash-bin information and takes the system admin user as the default which is the `OCIS_ADMIN_USER_ID` but can be set individually. It should be noted, that the `OCIS_ADMIN_USER_ID` is only assigned automatically when using the single binary deployment and must be manually assigned in all other deployments. The command only considers spaces to which the assigned user has access and delete permission.
 
-COMMANDS:
-   help, h  Shows a list of commands or help for one command
+*   `STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE`  
+Has a default value of `720h` which equals `30 days`. This means, the command will delete all files older than `30 days`. The value is human-readable, for valid values see the duration type described in the [Environment Variable Types](https://doc.owncloud.com/ocis/5.0/deployment/services/envvar-types-description.html). A value of `0` is equivalent to disable and prevents the deletion of `personal space` trash-bin files.
 
-OPTIONS:
-   --option value, -o value  The restore option defines the behavior for a file to be restored, where the file name already already exists in the target space. Supported values are: 'skip', 'replace' and 'keep-both'. (default: The default value is 'skip' overwriting an existing file)
-   --verbose, -v             Get more verbose output (default: false)
-   --yes, -y                 Automatic yes to prompts. Assume 'yes' as answer to all prompts and run non-interactively. (default: false)
-   --help, -h                show help
+*   `STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE`  
+Has a default value of `720h` which equals `30 days`. This means, the command will delete all files older than `30 days`. The value is human-readable, for valid values see the duration type described in the [Environment Variable Types](https://doc.owncloud.com/ocis/5.0/deployment/services/envvar-types-description.html). A value of `0` is equivalent to disable and prevents the deletion of `project space` trash-bin files.
 
-```
+#### List and Restore Trash-Bins Items
 
-```bash
-NAME:
-   ocis storage-users trash-bin restore - Restore a trash-bin item by ID.
+The variable `STORAGE_USERS_CLI_MAX_ATTEMPTS_RENAME_FILE` defines a maximum number of attempts to rename a file when the admin restores the file with the CLI option `--option keep-both` to an existing destination with the same name.
 
-USAGE:
-   ocis storage-users trash-bin restore command [command options] ['spaceID' required] ['itemID' required]
+*   Print a list of all trash-bin items of a space
+    ```bash
+    ocis storage-users trash-bin list
+    ```
 
-COMMANDS:
-   help, h  Shows a list of commands or help for one command
+* Restore all trash-bin items for a space
+    ```bash
+    ocis storage-users trash-bin restore-all
+    ```
 
-OPTIONS:
-   --option value, -o value  The restore option defines the behavior for a file to be restored, where the file name already already exists in the target space. Supported values are: 'skip', 'replace' and 'keep-both'. (default: The default value is 'skip' overwriting an existing file)
-   --verbose, -v             Get more verbose output (default: false)
-   --help, -h                show help
-```
+* Restore a trash-bin item by ID
+    ```bash
+    ocis storage-users trash-bin restore
+    ```
 
 ## Caching
 
diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go
index b615db8ba7a..78f6531223b 100644
--- a/services/storage-users/pkg/config/defaults/defaultconfig.go
+++ b/services/storage-users/pkg/config/defaults/defaultconfig.go
@@ -121,6 +121,7 @@ func DefaultConfig() *config.Config {
 				MaxAcquireLockCycles:       20,
 				MaxConcurrency:             5,
 				LockCycleDurationFactor:    30,
+				DisableMultipart:           true,
 			},
 			OCIS: config.OCISDriver{
 				MetadataBackend:            "messagepack",
diff --git a/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po
index e04cf519fa2..f2352d9dce5 100644
--- a/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/bg_BG/LC_MESSAGES/userlog.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-02-23 00:46+0000\n"
+"POT-Creation-Date: 2024-03-15 00:04+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Martin Zhekov <martin03staff@gmail.com>, 2023\n"
 "Language-Team: Bulgarian (Bulgaria) (https://app.transifex.com/owncloud-org/teams/6149/bg_BG/)\n"
diff --git a/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po
index bcf203536a6..79ff322b10f 100644
--- a/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/ko/LC_MESSAGES/userlog.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-02-23 00:46+0000\n"
+"POT-Creation-Date: 2024-03-15 00:04+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Hugo Blackwood, 2023\n"
 "Language-Team: Korean (https://app.transifex.com/owncloud-org/teams/6149/ko/)\n"
diff --git a/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po
index 4599a044f36..e5ac82d828f 100644
--- a/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/pt_BR/LC_MESSAGES/userlog.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-02-23 00:46+0000\n"
+"POT-Creation-Date: 2024-03-15 00:04+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Flávio Veras <flaviove@gmail.com>, 2023\n"
 "Language-Team: Portuguese (Brazil) (https://app.transifex.com/owncloud-org/teams/6149/pt_BR/)\n"
diff --git a/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po
index 11e5586d07e..4e930baff14 100644
--- a/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/sq/LC_MESSAGES/userlog.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-02-23 00:46+0000\n"
+"POT-Creation-Date: 2024-03-15 00:04+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Besnik Bleta <besnik@programeshqip.org>, 2023\n"
 "Language-Team: Albanian (https://app.transifex.com/owncloud-org/teams/6149/sq/)\n"
diff --git a/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po
index 9d0606e84ff..b136edb1fa2 100644
--- a/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/zh_CN/LC_MESSAGES/userlog.po
@@ -11,7 +11,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2024-02-23 00:46+0000\n"
+"POT-Creation-Date: 2024-03-15 00:04+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Shouyuan, 2023\n"
 "Language-Team: Chinese (China) (https://app.transifex.com/owncloud-org/teams/6149/zh_CN/)\n"
diff --git a/vendor/github.com/thejerf/suture/v4/LICENSE b/vendor/github.com/thejerf/suture/v4/LICENSE
index a1c666ea8ee..99c7cc597aa 100644
--- a/vendor/github.com/thejerf/suture/v4/LICENSE
+++ b/vendor/github.com/thejerf/suture/v4/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2020 Barracuda Networks, Inc.
+Copyright (c) 2014-2024 Barracuda Networks, Inc.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/github.com/thejerf/suture/v4/messages.go b/vendor/github.com/thejerf/suture/v4/messages.go
index 380fd6ec23e..8bd2153571a 100644
--- a/vendor/github.com/thejerf/suture/v4/messages.go
+++ b/vendor/github.com/thejerf/suture/v4/messages.go
@@ -21,7 +21,10 @@ type removeService struct {
 func (rs removeService) isSupervisorMessage() {}
 
 func (s *Supervisor) sync() {
-	s.control <- syncSupervisor{}
+	select {
+	case s.control <- syncSupervisor{}:
+	case <-s.liveness:
+	}
 }
 
 type syncSupervisor struct {
@@ -30,7 +33,10 @@ type syncSupervisor struct {
 func (ss syncSupervisor) isSupervisorMessage() {}
 
 func (s *Supervisor) fail(id serviceID, panicVal interface{}, stacktrace []byte) {
-	s.control <- serviceFailed{id, panicVal, stacktrace}
+	select {
+	case s.control <- serviceFailed{id, panicVal, stacktrace}:
+	case <-s.liveness:
+	}
 }
 
 type serviceFailed struct {
diff --git a/vendor/github.com/thejerf/suture/v4/supervisor.go b/vendor/github.com/thejerf/suture/v4/supervisor.go
index e7e9ed61d57..0ab5f99d2bb 100644
--- a/vendor/github.com/thejerf/suture/v4/supervisor.go
+++ b/vendor/github.com/thejerf/suture/v4/supervisor.go
@@ -14,6 +14,7 @@ import (
 	"math/rand"
 	"runtime"
 	"sync"
+	"sync/atomic"
 	"time"
 )
 
@@ -50,11 +51,10 @@ program will be to call one of the Serve methods.
 Calling ServeBackground will CORRECTLY start the supervisor running in a
 new goroutine. It is risky to directly run
 
-  go supervisor.Serve()
+	go supervisor.Serve()
 
 because that will briefly create a race condition as it starts up, if you
 try to .Add() services immediately afterward.
-
 */
 type Supervisor struct {
 	Name string
@@ -97,7 +97,6 @@ type Supervisor struct {
 }
 
 /*
-
 New is the full constructor function for a supervisor.
 
 The name is a friendly human name for the supervisor, used in logging. Suture
@@ -105,21 +104,26 @@ does not care if this is unique, but it is good for your sanity if it is.
 
 If not set, the following values are used:
 
- * EventHook:         A function is created that uses log.Print.
- * FailureDecay:      30 seconds
- * FailureThreshold:  5 failures
- * FailureBackoff:    15 seconds
- * Timeout:           10 seconds
- * BackoffJitter:     DefaultJitter
+  - EventHook:         A function is created that uses log.Print.
+  - FailureDecay:      30 seconds
+  - FailureThreshold:  5 failures
+  - FailureBackoff:    15 seconds
+  - Timeout:           10 seconds
+  - BackoffJitter:     DefaultJitter
 
 The EventHook function will be called when errors occur. Suture will log the
 following:
 
- * When a service has failed, with a descriptive message about the
-   current backoff status, and whether it was immediately restarted
- * When the supervisor has gone into its backoff mode, and when it
-   exits it
- * When a service fails to stop
+  - When a service has failed, with a descriptive message about the
+    current backoff status, and whether it was immediately restarted
+  - When the supervisor has gone into its backoff mode, and when it
+    exits it
+  - When a service fails to stop
+
+A slog-based EventHook is provided in the submodule sutureslog. Note
+that it is a separate Go module, in order to avoid imposing a Go 1.21
+requirement on this module, so a separate go get step will be
+necessary to use it.
 
 The failureRate, failureThreshold, and failureBackoff controls how failures
 are handled, in order to avoid the supervisor failure case where the
@@ -145,7 +149,6 @@ DontPropagateTermination indicates whether this supervisor tree will
 propagate a ErrTerminateTree if a child process returns it. If false,
 this supervisor will itself return an error that will terminate its
 parent. If true, it will merely return ErrDoNotRestart. false by default.
-
 */
 func New(name string, spec Spec) *Supervisor {
 	spec.configureDefaults(name)
@@ -254,7 +257,6 @@ supervisor being added will copy the EventHook function from the Supervisor it
 is being added to. This allows factoring out providing a Supervisor
 from its logging. This unconditionally overwrites the child Supervisor's
 logging functions.
-
 */
 func (s *Supervisor) Add(service Service) ServiceToken {
 	if s == nil {
@@ -277,7 +279,7 @@ func (s *Supervisor) Add(service Service) ServiceToken {
 		s.restartQueue = append(s.restartQueue, id)
 
 		s.m.Unlock()
-		return ServiceToken{uint64(s.id)<<32 | uint64(id)}
+		return ServiceToken{supervisor: s.id, service: id}
 	}
 	s.m.Unlock()
 
@@ -285,7 +287,7 @@ func (s *Supervisor) Add(service Service) ServiceToken {
 	if s.sendControl(addService{service, serviceName(service), response}) != nil {
 		return ServiceToken{}
 	}
-	return ServiceToken{uint64(s.id)<<32 | uint64(<-response)}
+	return ServiceToken{supervisor: s.id, service: <-response}
 }
 
 // ServeBackground starts running a supervisor in its own goroutine. When
@@ -652,7 +654,7 @@ SHUTTING_DOWN_SERVICES:
 				SupervisorPath: []*Supervisor{s},
 				Service:        serviceWithName.Service,
 				Name:           serviceWithName.name,
-				ServiceToken:   ServiceToken{uint64(s.id)<<32 | uint64(serviceID)},
+				ServiceToken:   ServiceToken{supervisor: s.id, service: serviceID},
 			})
 		}
 		s.m.Lock()
@@ -694,11 +696,10 @@ The ServiceID token comes from the Add() call. This returns without waiting
 for the service to stop.
 */
 func (s *Supervisor) Remove(id ServiceToken) error {
-	sID := supervisorID(id.id >> 32)
-	if sID != s.id {
+	if id.supervisor != s.id {
 		return ErrWrongSupervisor
 	}
-	err := s.sendControl(removeService{serviceID(id.id & 0xffffffff), nil})
+	err := s.sendControl(removeService{id.service, nil})
 	if err == ErrSupervisorNotRunning {
 		// No meaningful error handling if the supervisor is stopped.
 		return nil
@@ -717,8 +718,7 @@ passes, ErrTimeout is returned. (If this isn't even the right supervisor
 ErrWrongSupervisor is returned.)
 */
 func (s *Supervisor) RemoveAndWait(id ServiceToken, timeout time.Duration) error {
-	sID := supervisorID(id.id >> 32)
-	if sID != s.id {
+	if id.supervisor != s.id {
 		return ErrWrongSupervisor
 	}
 
@@ -732,7 +732,7 @@ func (s *Supervisor) RemoveAndWait(id ServiceToken, timeout time.Duration) error
 
 	notificationC := make(chan struct{})
 
-	sentControlErr := s.sendControl(removeService{serviceID(id.id & 0xffffffff), notificationC})
+	sentControlErr := s.sendControl(removeService{id.service, notificationC})
 
 	if sentControlErr != nil {
 		return sentControlErr
@@ -756,10 +756,8 @@ func (s *Supervisor) RemoveAndWait(id ServiceToken, timeout time.Duration) error
 }
 
 /*
-
 Services returns a []Service containing a snapshot of the services this
 Supervisor is managing.
-
 */
 func (s *Supervisor) Services() []Service {
 	ls := listServices{make(chan []Service)}
@@ -770,20 +768,17 @@ func (s *Supervisor) Services() []Service {
 	return nil
 }
 
-var currentSupervisorIDL sync.Mutex
 var currentSupervisorID uint32
 
 func nextSupervisorID() supervisorID {
-	currentSupervisorIDL.Lock()
-	defer currentSupervisorIDL.Unlock()
-	currentSupervisorID++
-	return supervisorID(currentSupervisorID)
+	return supervisorID(atomic.AddUint32(&currentSupervisorID, 1))
 }
 
 // ServiceToken is an opaque identifier that can be used to terminate a service that
 // has been Add()ed to a Supervisor.
 type ServiceToken struct {
-	id uint64
+	supervisor supervisorID
+	service    serviceID
 }
 
 // An UnstoppedService is the component member of an
diff --git a/vendor/go.opentelemetry.io/contrib/zpages/version.go b/vendor/go.opentelemetry.io/contrib/zpages/version.go
index 00dfb0ce618..527a1d37e28 100644
--- a/vendor/go.opentelemetry.io/contrib/zpages/version.go
+++ b/vendor/go.opentelemetry.io/contrib/zpages/version.go
@@ -16,7 +16,7 @@ package zpages // import "go.opentelemetry.io/contrib/zpages"
 
 // Version is the current release version of the zpages span processor.
 func Version() string {
-	return "0.48.0"
+	return "0.49.0"
 	// This string is updated by the pre_release.sh script during release
 }
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 6634b469c5c..17a5b2455fe 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1756,7 +1756,7 @@ github.com/studio-b12/gowebdav
 # github.com/tchap/go-patricia/v2 v2.3.1
 ## explicit; go 1.16
 github.com/tchap/go-patricia/v2/patricia
-# github.com/thejerf/suture/v4 v4.0.2
+# github.com/thejerf/suture/v4 v4.0.5
 ## explicit; go 1.9
 github.com/thejerf/suture/v4
 # github.com/tidwall/gjson v1.17.1
@@ -1936,7 +1936,7 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/inte
 ## explicit; go 1.20
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil
-# go.opentelemetry.io/contrib/zpages v0.48.0
+# go.opentelemetry.io/contrib/zpages v0.49.0
 ## explicit; go 1.20
 go.opentelemetry.io/contrib/zpages
 go.opentelemetry.io/contrib/zpages/internal


Test patch:
diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go
index 69cacd7e90a..534f233d538 100644
--- a/services/graph/pkg/service/v0/driveitems_test.go
+++ b/services/graph/pkg/service/v0/driveitems_test.go
@@ -17,6 +17,7 @@ import (
 	collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
 	link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
 	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
+	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
 	"github.com/go-chi/chi/v5"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -1027,7 +1028,7 @@ var _ = Describe("Driveitems", func() {
 		})
 
 		It("fails with wrong role", func() {
-			driveItemInvite.Roles = []string{unifiedrole.NewCoownerUnifiedRole().GetId()}
+			driveItemInvite.Roles = []string{unifiedrole.NewManagerUnifiedRole().GetId()}
 			svc.Invite(
 				rr,
 				httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
@@ -1156,6 +1157,7 @@ var _ = Describe("Driveitems", func() {
 			listSharesResponse       *collaboration.ListSharesResponse
 			listPublicSharesMock     *mock.Call
 			listPublicSharesResponse *link.ListPublicSharesResponse
+			rctx                     *chi.Context
 		)
 
 		toResourceID := func(in string) *provider.ResourceId {
@@ -1166,10 +1168,7 @@ var _ = Describe("Driveitems", func() {
 		}
 
 		BeforeEach(func() {
-			rctx := chi.NewRouteContext()
-			rctx.URLParams.Add("driveID", "1$2")
-			rctx.URLParams.Add("itemID", "1$2!3")
-
+			rctx = chi.NewRouteContext()
 			ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx)
 			ctx = revactx.ContextSetUser(ctx, currentUser)
 
@@ -1210,6 +1209,9 @@ var _ = Describe("Driveitems", func() {
 		})
 
 		It("lists permissions", func() {
+			rctx.URLParams.Add("driveID", "1$2")
+			rctx.URLParams.Add("itemID", "1$2!3")
+
 			svc.ListPermissions(
 				rr,
 				httptest.NewRequest(http.MethodGet, "/", nil).
@@ -1230,6 +1232,62 @@ var _ = Describe("Driveitems", func() {
 			Expect(value.Get("#").Num).To(Equal(float64(1)))
 			Expect(value.Get("0.id").Str).To(Equal("123"))
 		})
+		It("lists permissions on a storage space", func() {
+			rctx.URLParams.Add("driveID", "1$2")
+			rctx.URLParams.Add("itemID", "1$2!2")
+			statResponse.Info.Id.OpaqueId = "2"
+			grantMap := map[string]*provider.ResourcePermissions{
+				"userid": roleconversions.NewSpaceViewerRole().CS3ResourcePermissions(),
+			}
+			grantMapJSON, _ := json.Marshal(grantMap)
+			spaceOpaque := &types.Opaque{
+				Map: map[string]*types.OpaqueEntry{
+					"grants": {
+						Decoder: "json",
+						Value:   grantMapJSON,
+					},
+				},
+			}
+
+			listSpacesMock := gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything)
+			listSpacesResponse := &provider.ListStorageSpacesResponse{
+				Status: status.NewOK(ctx),
+				StorageSpaces: []*provider.StorageSpace{
+					{
+						Id: &provider.StorageSpaceId{
+							OpaqueId: "2",
+						},
+						Opaque: spaceOpaque,
+					},
+				},
+			}
+			listSpacesMock.Return(listSpacesResponse, nil)
+
+			getUserMock := gatewayClient.On("GetUser", mock.Anything, mock.Anything)
+			getUserMockResponse := &userpb.GetUserResponse{
+				Status: status.NewOK(ctx),
+				User: &userpb.User{
+					Id:          &userpb.UserId{OpaqueId: "userid"},
+					DisplayName: "Test User",
+				},
+			}
+			getUserMock.Return(getUserMockResponse, nil)
+
+			svc.ListPermissions(
+				rr,
+				httptest.NewRequest(http.MethodGet, "/", nil).
+					WithContext(ctx),
+			)
+
+			Expect(rr.Code).To(Equal(http.StatusOK))
+			p := libregraph.NewCollectionOfPermissions()
+			err := json.Unmarshal(rr.Body.Bytes(), p)
+			Expect(err).To(BeNil())
+			permissions := p.GetValue()
+			Expect(len(permissions)).To(Equal(1))
+			Expect(permissions[0].GetId()).ToNot(Equal(""))
+		})
+
 	})
 
 	Describe("GetRootDriveChildren", func() {
diff --git a/services/graph/pkg/service/v0/graph_test.go b/services/graph/pkg/service/v0/graph_test.go
index b82b6686d9c..cb56ee268d6 100644
--- a/services/graph/pkg/service/v0/graph_test.go
+++ b/services/graph/pkg/service/v0/graph_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/tidwall/gjson"
 	"google.golang.org/grpc"
 
+	"github.com/cs3org/reva/v2/pkg/conversions"
 	revactx "github.com/cs3org/reva/v2/pkg/ctx"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
@@ -520,16 +521,17 @@ var _ = Describe("Graph", func() {
 
 				check(jsonData)
 			},
-			Entry("injects grantedToV2", func(jsonData gjson.Result) {}, provider.ResourcePermissions{RemoveGrant: true}),
+			Entry("injects grantedToV2", func(jsonData gjson.Result) {},
+				*conversions.NewSpaceViewerRole().CS3ResourcePermissions()),
 			Entry("remaps manager role to the unified counterpart", func(jsonData gjson.Result) {
 				Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleManagerID))
-			}, provider.ResourcePermissions{RemoveGrant: true}),
+			}, *conversions.NewManagerRole().CS3ResourcePermissions()),
 			Entry("remaps editor role to the unified counterpart", func(jsonData gjson.Result) {
 				Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleSpaceEditorID))
-			}, provider.ResourcePermissions{InitiateFileUpload: true}),
+			}, *conversions.NewSpaceEditorRole().CS3ResourcePermissions()),
 			Entry("remaps viewer role to the unified counterpart", func(jsonData gjson.Result) {
 				Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleSpaceViewerID))
-			}, provider.ResourcePermissions{Stat: true}),
+			}, *conversions.NewSpaceViewerRole().CS3ResourcePermissions()),
 		)
 		Describe("Create Drive", func() {
 			It("cannot create a space without valid user in context", func() {
diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go
index 12248a375b2..add5ba63f2d 100644
--- a/services/graph/pkg/unifiedrole/unifiedrole_test.go
+++ b/services/graph/pkg/unifiedrole/unifiedrole_test.go
@@ -27,7 +27,6 @@ var _ = Describe("unifiedroles", func() {
 		Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
 		Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
 		Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
-		Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
 		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),
@@ -53,7 +52,6 @@ var _ = Describe("unifiedroles", func() {
 		Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), true),
 		Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), true),
 		Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), true),
-		Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), true),
 		Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), true),
 		Entry("no match", rConversions.NewFileEditorRole(true), unifiedrole.NewManagerUnifiedRole(), false),
 	)
@@ -218,7 +216,6 @@ var _ = Describe("unifiedroles", func() {
 				[]*libregraph.UnifiedRoleDefinition{
 					unifiedrole.NewSpaceViewerUnifiedRole(),
 					unifiedrole.NewSpaceEditorUnifiedRole(),
-					unifiedrole.NewCoownerUnifiedRole(),
 					unifiedrole.NewManagerUnifiedRole(),
 				},
 			),
diff --git a/tests/acceptance/features/apiContract/propfind.feature b/tests/acceptance/features/apiContract/propfind.feature
index 200ea281c53..1e7bf966196 100644
--- a/tests/acceptance/features/apiContract/propfind.feature
+++ b/tests/acceptance/features/apiContract/propfind.feature
@@ -15,7 +15,7 @@ Feature: Propfind test
 
   Scenario: space-admin checks the PROPFIND request of a space
     Given user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt"
-    When user "Alice" sends PROPFIND request to space "new-space" using the WebDAV API
+    When user "Alice" sends PROPFIND request to space "new-space" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the following headers should match these regular expressions
       | X-Request-Id | /^[a-zA-Z]+\/[a-zA-Z]+\.feature:\d+(-\d+)?$/ |
@@ -33,7 +33,7 @@ Feature: Propfind test
     And user "Alice" has shared a space "new-space" with settings:
       | shareWith | Brian  |
       | role      | <role> |
-    When user "Brian" sends PROPFIND request to space "new-space" using the WebDAV API
+    When user "Brian" sends PROPFIND request to space "new-space" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the following headers should match these regular expressions
       | X-Request-Id | /^[a-zA-Z]+\/[a-zA-Z]+\.feature:\d+(-\d+)?$/ |
@@ -56,7 +56,7 @@ Feature: Propfind test
     And user "Alice" has shared a space "new-space" with settings:
       | shareWith | Brian  |
       | role      | <role> |
-    When user "Brian" sends PROPFIND request from the space "new-space" to the resource "folderMain" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "new-space" to the resource "folderMain" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "new-space" with these key and value pairs:
       | key            | value             |
@@ -77,7 +77,7 @@ Feature: Propfind test
     And user "Alice" has shared a space "new-space" with settings:
       | shareWith | Brian  |
       | role      | <role> |
-    When user "Brian" sends PROPFIND request from the space "new-space" to the resource "folderMain/subFolder1/subFolder2" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "new-space" to the resource "folderMain/subFolder1/subFolder2" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "new-space" with these key and value pairs:
       | key            | value                                   |
@@ -98,7 +98,7 @@ Feature: Propfind test
     And user "Alice" has shared a space "new-space" with settings:
       | shareWith | Brian  |
       | role      | <role> |
-    When user "Brian" sends PROPFIND request from the space "new-space" to the resource "testfile.txt" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "new-space" to the resource "testfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "new-space" with these key and value pairs:
       | key            | value               |
diff --git a/tests/acceptance/features/apiLocks/lockFiles.feature b/tests/acceptance/features/apiLocks/lockFiles.feature
index dece6da8bce..65c181e1e05 100644
--- a/tests/acceptance/features/apiLocks/lockFiles.feature
+++ b/tests/acceptance/features/apiLocks/lockFiles.feature
@@ -15,7 +15,7 @@ Feature: lock files
     When user "Alice" locks file "textfile.txt" using the WebDAV API setting the following properties
       | lockscope | exclusive |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
@@ -37,7 +37,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-5000 |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
@@ -59,7 +59,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-3600 |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
@@ -100,7 +100,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-3600 |
     Then the HTTP status code should be "200"
-    When user "Brian" sends PROPFIND request from the space "Project" to the resource "textfile.txt" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "Project" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Brian" should contain a space "Project" with these key and value pairs:
       | key                                                  | value        |
@@ -127,7 +127,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-3600 |
     Then the HTTP status code should be "200"
-    When user "Brian" sends PROPFIND request from the space "Project" to the resource "textfile.txt" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "Project" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Brian" should contain a space "Project" with these key and value pairs:
       | key                                                  | value        |
@@ -168,7 +168,7 @@ Feature: lock files
     When user "Brian" locks file "/Shares/textfile.txt" using the WebDAV API setting the following properties
       | lockscope | exclusive |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
@@ -192,7 +192,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-3600 |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
@@ -229,7 +229,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-3600 |
     Then the HTTP status code should be "423"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
@@ -250,7 +250,7 @@ Feature: lock files
       | lockscope | exclusive   |
       | timeout   | Second-3600 |
     Then the HTTP status code should be "423"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "textfile.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a space "Alice Hansen" with these key and value pairs:
       | key                                                  | value        |
diff --git a/tests/acceptance/features/apiSharingNg/listPermissions.feature b/tests/acceptance/features/apiSharingNg/listPermissions.feature
index 9c70cb1251d..1ea0c11e725 100644
--- a/tests/acceptance/features/apiSharingNg/listPermissions.feature
+++ b/tests/acceptance/features/apiSharingNg/listPermissions.feature
@@ -242,8 +242,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":  [
@@ -305,29 +305,6 @@ Feature: List a sharing permissions
                   "@libre.graph.weight": {
                     "const": 3
                   },
-                  "description": {
-                    "const": "Grants co-owner permissions on a resource"
-                  },
-                  "displayName": {
-                    "const": "Co Owner"
-                  },
-                  "id": {
-                    "const": "3a4ba8e9-6a0d-4235-9140-0e7a34007abe"
-                  }
-                }
-              },
-              {
-                "type": "object",
-                "required": [
-                  "@libre.graph.weight",
-                  "description",
-                  "displayName",
-                  "id"
-                ],
-                "properties": {
-                  "@libre.graph.weight": {
-                    "const": 4
-                  },
                   "description": {
                     "const": "Grants manager permissions on a resource. Semantically equivalent to co-owner"
                   },
@@ -344,4 +321,4 @@ Feature: List a sharing permissions
         }
       }
     }
-    """
\ No newline at end of file
+    """
diff --git a/tests/acceptance/features/apiSharingNg/propfindShares.feature b/tests/acceptance/features/apiSharingNg/propfindShares.feature
new file mode 100644
index 00000000000..5897c8040b3
--- /dev/null
+++ b/tests/acceptance/features/apiSharingNg/propfindShares.feature
@@ -0,0 +1,47 @@
+Feature: propfind a shares
+  As a user
+  I want to check the PROPFIND response
+  So that I can make sure that the response contains all the relevant values
+
+  Background:
+    Given these users have been created with default attributes and without skeleton files:
+      | username |
+      | Alice    |
+      | Brian    |
+      | Carol    |
+
+  @issue-4421
+  Scenario Outline: sharee PROPFIND same name shares shared by multiple users
+    Given user "Alice" has uploaded file with content "to share" to "textfile.txt"
+    And user "Alice" has created folder "folderToShare"
+    And user "Carol" has uploaded file with content "to share" to "textfile.txt"
+    And user "Carol" has created folder "folderToShare"
+    And user "Alice" has sent the following share invitation:
+      | resource        | <path>   |
+      | space           | Personal |
+      | sharee          | Brian    |
+      | shareType       | user     |
+      | permissionsRole | Viewer   |
+    And user "Carol" has sent the following share invitation:
+      | resource        | <path>   |
+      | space           | Personal |
+      | sharee          | Brian    |
+      | shareType       | user     |
+      | permissionsRole | Viewer   |
+    When user "Brian" sends PROPFIND request to space "Shares" using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the "PROPFIND" response to user "Brian" should contain a space "Shares" with these key and value pairs:
+      | key       | value         |
+      | oc:fileid | UUIDof:Shares |
+    And the "PROPFIND" response to user "Brian" should contain a mountpoint "Shares" with these key and value pairs:
+      | key            | value  |
+      | oc:name        | <path> |
+      | oc:permissions | SR     |
+    And the "PROPFIND" response to user "Brian" should contain a mountpoint "Shares" with these key and value pairs:
+      | key            | value   |
+      | oc:name        | <path2> |
+      | oc:permissions | SR      |
+    Examples:
+      | path          | path2             |
+      | textfile.txt  | textfile (1).txt  |
+      | folderToShare | folderToShare (1) |
diff --git a/tests/acceptance/features/apiSharingNg/shareInvitations.feature b/tests/acceptance/features/apiSharingNg/shareInvitations.feature
index 6787c3b705e..214a1834dc7 100644
--- a/tests/acceptance/features/apiSharingNg/shareInvitations.feature
+++ b/tests/acceptance/features/apiSharingNg/shareInvitations.feature
@@ -1037,7 +1037,6 @@ Feature: Send a sharing invitations
       | File Editor      | /textfile1.txt |
       | Viewer           | FolderToShare  |
       | Editor           | FolderToShare  |
-      | Co Owner         | FolderToShare  |
       | Uploader         | FolderToShare  |
       | Manager          | FolderToShare  |
 
@@ -1546,11 +1545,9 @@ Feature: Send a sharing invitations
       """
     Examples:
       | permissions-role | path           |
-      | Co Owner         | /textfile1.txt |
       | Manager          | /textfile1.txt |
       | Space Viewer     | /textfile1.txt |
       | Space Editor     | /textfile1.txt |
-      | Co Owner         | FolderToShare  |
       | Manager          | FolderToShare  |
       | Space Viewer     | FolderToShare  |
       | Space Editor     | FolderToShare  |
@@ -1728,7 +1725,6 @@ Feature: Send a sharing invitations
       | permissions-role |
       | Space Viewer     |
       | Space Editor     |
-      | Co Owner         |
       | Manager          |
 
 
@@ -1775,7 +1771,6 @@ Feature: Send a sharing invitations
       | permissions-role |
       | Space Viewer     |
       | Space Editor     |
-      | Co Owner         |
       | Manager          |
 
 
@@ -1823,7 +1818,6 @@ Feature: Send a sharing invitations
       | permissions-role |
       | Space Viewer     |
       | Space Editor     |
-      | Co Owner         |
       | Manager          |
 
 
@@ -1908,7 +1902,6 @@ Feature: Send a sharing invitations
       | permissions-role |
       | Space Viewer     |
       | Space Editor     |
-      | Co Owner         |
       | Manager          |
 
 
@@ -1961,7 +1954,6 @@ Feature: Send a sharing invitations
       | permissions-role |
       | Space Viewer     |
       | Space Editor     |
-      | Co Owner         |
       | Manager          |
 
 
@@ -2015,7 +2007,6 @@ Feature: Send a sharing invitations
       | permissions-role |
       | Space Viewer     |
       | Space Editor     |
-      | Co Owner         |
       | Manager          |
 
 
diff --git a/tests/acceptance/features/apiSpaces/tag.feature b/tests/acceptance/features/apiSpaces/tag.feature
index 652a2380aa1..8d847e50153 100644
--- a/tests/acceptance/features/apiSpaces/tag.feature
+++ b/tests/acceptance/features/apiSpaces/tag.feature
@@ -26,7 +26,7 @@ Feature: Tag
       | tag level#1                    |
       | tag with symbols @^$#^%$@%!_+) |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
       | key     | value                                      |
@@ -34,7 +34,7 @@ Feature: Tag
     When user "Alice" creates the following tags for file "folderMain/insideTheFolder.txt" of space "use-tag":
       | fileTag |
     Then the HTTP status code should be "200"
-    When user "Brian" sends PROPFIND request from the space "use-tag" to the resource "folderMain/insideTheFolder.txt" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "use-tag" to the resource "folderMain/insideTheFolder.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
       | key     | value   |
@@ -64,12 +64,12 @@ Feature: Tag
       | fileTag                       |
       | tag with symbol @^$#^%$@%!_+) |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "folderMain" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "folderMain" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a mountpoint "Alice Hansen" with these key and value pairs:
       | key     | value            |
       | oc:tags | my tag,important |
-    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "file.txt" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "Alice Hansen" to the resource "file.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response to user "Alice" should contain a mountpoint "Alice Hansen" with these key and value pairs:
       | key     | value                                 |
@@ -177,7 +177,7 @@ Feature: Tag
     When user "Alice" removes the following tags for folder "folderMain" of space "use-tag":
       | folderTag |
       | marketing |
-    And user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API
+    And user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
       | key     | value       |
@@ -255,7 +255,7 @@ Feature: Tag
     When user "Alice" creates the following tags for folder "folderMain" of space "use-tag":
       | finance,नेपाल |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
       | key     | value         |
@@ -263,7 +263,7 @@ Feature: Tag
     When user "Alice" creates the following tags for file "folderMain/insideTheFolder.txt" of space "use-tag":
       | file,नेपाल,Tag |
     Then the HTTP status code should be "200"
-    When user "Brian" sends PROPFIND request from the space "use-tag" to the resource "folderMain/insideTheFolder.txt" using the WebDAV API
+    When user "Brian" sends PROPFIND request from the space "use-tag" to the resource "folderMain/insideTheFolder.txt" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
       | key     | value          |
@@ -283,7 +283,7 @@ Feature: Tag
     When user "Alice" creates the following tags for folder "folderMain" of space "use-tag":
       | engineering,finance,qa |
     Then the HTTP status code should be "200"
-    When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API
+    When user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" with depth "0" using the WebDAV API
     Then the HTTP status code should be "207"
     And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
       | key     | value                     |
diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php
index 6ce5bd0b2d2..4ec063d2df1 100644
--- a/tests/acceptance/features/bootstrap/SpacesContext.php
+++ b/tests/acceptance/features/bootstrap/SpacesContext.php
@@ -3597,11 +3597,11 @@ class SpacesContext implements Context {
 
 	/**
 	 * @When /^user "([^"]*)" sends PROPFIND request to space "([^"]*)" using the WebDAV API$/
-	 * @When /^user "([^"]*)" sends PROPFIND request from the space "([^"]*)" to the resource "([^"]*)" using the WebDAV API$/
+	 * @When /^user "([^"]*)" sends PROPFIND request to space "([^"]*)" with depth "([^"]*)" using the WebDAV API$/
 	 *
 	 * @param string $user
 	 * @param string $spaceName
-	 * @param ?string $resource
+	 * @param ?string $folderDepth
 	 *
 	 * @return void
 	 *
@@ -3609,9 +3609,29 @@ class SpacesContext implements Context {
 	 *
 	 * @throws GuzzleException
 	 */
-	public function userSendsPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = ""): void {
+	public function userSendsPropfindRequestToSpaceUsingTheWebdavApi(string $user, string $spaceName, ?string $folderDepth = "1"): void {
 		$this->featureContext->setResponse(
-			$this->sendPropfindRequestToSpace($user, $spaceName, $resource)
+			$this->sendPropfindRequestToSpace($user, $spaceName, "", null, $folderDepth)
+		);
+	}
+
+	/**
+	 * @When /^user "([^"]*)" sends PROPFIND request from the space "([^"]*)" to the resource "([^"]*)" with depth "([^"]*)" using the WebDAV API$/
+	 *
+	 * @param string $user
+	 * @param string $spaceName
+	 * @param string $resource
+	 * @param string $folderDepth
+	 *
+	 * @return void
+	 *
+	 * @throws JsonException
+	 *
+	 * @throws GuzzleException
+	 */
+	public function userSendsPropfindRequestFromTheSpaceToTheResourceWithDepthUsingTheWebdavApi(string $user, string $spaceName, string $resource, string $folderDepth): void {
+		$this->featureContext->setResponse(
+			$this->sendPropfindRequestToSpace($user, $spaceName, $resource, null, $folderDepth)
 		);
 	}
 
@@ -3637,7 +3657,7 @@ class SpacesContext implements Context {
 			$headers[$row['header']] = $row ['value'];
 		}
 		$this->featureContext->setResponse(
-			$this->sendPropfindRequestToSpace($user, $spaceName, '', $headers)
+			$this->sendPropfindRequestToSpace($user, $spaceName, '', $headers, '0')
 		);
 	}
 
@@ -3646,15 +3666,36 @@ class SpacesContext implements Context {
 	 * @param string $spaceName
 	 * @param string|null $resource
 	 * @param array|null $headers
+	 * @param string|null $folderDepth
 	 *
 	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 *
 	 * @throws JsonException
 	 */
-	public function sendPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = "", ?array $headers = []): ResponseInterface {
+	public function sendPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = "", ?array $headers = [], ?string $folderDepth = "1"): ResponseInterface {
 		$this->setSpaceIDByName($user, $spaceName);
-		$properties = ['oc:permissions','oc:file-parent','oc:fileid','oc:share-types','oc:privatelink','d:resourcetype','oc:size','oc:name','d:getcontenttype','oc:tags','d:lockdiscovery','d:activelock'];
+		$properties = [
+			'oc:id',
+			'oc:fileid',
+			'oc:spaceid',
+			'oc:file-parent',
+			'oc:shareid',
+			'oc:name',
+			'd:displayname',
+			'd:getetag',
+			'oc:permissions',
+			'd:resourcetype',
+			'oc:size',
+			'd:getlastmodified',
+			'oc:tags',
+			'oc:favorite',
+			'oc:share-types',
+			'oc:privatelink',
+			'd:getcontenttype',
+			'd:lockdiscovery',
+			'd:activelock'
+		];
 		return  WebDavHelper::propfind(
 			$this->featureContext->getBaseUrl(),
 			$this->featureContext->getActualUsername($user),
@@ -3662,10 +3703,10 @@ class SpacesContext implements Context {
 			$resource,
 			$properties,
 			$this->featureContext->getStepLineRef(),
-			"0",
+			$folderDepth,
 			"files",
 			WebDavHelper::DAV_VERSION_SPACES,
-			"",
+			null,
 			$headers
 		);
 	}
@@ -3718,50 +3759,62 @@ class SpacesContext implements Context {
 		$xmlRes = $this->featureContext->getResponseXml();
 		foreach ($table->getHash() as $row) {
 			$findItem = $row['key'];
+			$xmlResponses = $xmlRes->xpath("//d:response/d:propstat/d:prop/$findItem");
 			Assert::assertNotEmpty(
-				$xmlRes->xpath("//d:response/d:propstat/d:prop/$findItem"),
+				$xmlResponses,
 				'The xml response "' . $xmlRes->asXML() . '" did not contain "<' . $findItem . '>" element'
 			);
-			$responseValue = $xmlRes->xpath("//d:response/d:propstat/d:prop/$findItem")[0]->__toString();
+
+			$responseValues = [];
+			foreach ($xmlResponses as $xmlResponse) {
+				$responseValues[] = $xmlResponse[0]->__toString();
+			}
+
 			$value = str_replace('UUIDof:', '', $row['value']);
 			switch ($findItem) {
 				case "oc:fileid":
 					$resourceType = $xmlRes->xpath("//d:response/d:propstat/d:prop/d:getcontenttype")[0]->__toString();
 					if ($method === 'PROPFIND') {
 						if (!$resourceType) {
-							Assert::assertEquals($this->getResourceId($user, $spaceNameOrMountPoint, $value), $responseValue, 'wrong fileId in the response');
+							Assert::assertContainsEquals($this->getResourceId($user, $spaceNameOrMountPoint, $value), $responseValues, 'wrong fileId in the response');
 						} else {
-							Assert::assertEquals($this->getFileId($user, $spaceNameOrMountPoint, $value), $responseValue, 'wrong fileId in the response');
+							Assert::assertContainsEquals($this->getFileId($user, $spaceNameOrMountPoint, $value), $responseValues, 'wrong fileId in the response');
 						}
 					} else {
 						if ($resourceType === 'httpd/unix-directory') {
-							Assert::assertEquals($this->getResourceId($user, $spaceNameOrMountPoint, $value), $responseValue, 'wrong fileId in the response');
+							Assert::assertContainsEquals($this->getResourceId($user, $spaceNameOrMountPoint, $value), $responseValues, 'wrong fileId in the response');
 						} else {
-							Assert::assertEquals($this->getFileId($user, $spaceNameOrMountPoint, $value), $responseValue, 'wrong fileId in the response');
+							Assert::assertContainsEquals($this->getFileId($user, $spaceNameOrMountPoint, $value), $responseValues, 'wrong fileId in the response');
 						}
 					}
 					break;
 				case "oc:file-parent":
-					Assert::assertEquals($this->getResourceId($user, $spaceNameOrMountPoint, $value), $responseValue, 'wrong file-parentId in the response');
+					Assert::assertContainsEquals($this->getResourceId($user, $spaceNameOrMountPoint, $value), $responseValues, 'wrong file-parentId in the response');
 					break;
 				case "oc:privatelink":
-					Assert::assertEquals($this->getPrivateLink($user, $spaceNameOrMountPoint), $responseValue, 'cannot find private link for space or resource in the response');
+					Assert::assertContainsEquals($this->getPrivateLink($user, $spaceNameOrMountPoint), $responseValues, 'cannot find private link for space or resource in the response');
 					break;
 				case "oc:tags":
 					// The value should be a comma-separated string of tag names.
 					// We do not care what order they happen to be in, so compare as sorted lists.
 					$expectedTags = \explode(",", $value);
-					$expectedTags = \sort($expectedTags);
-					$actualTags = \explode(",", $responseValue);
-					$actualTags = \sort($actualTags);
-					Assert::assertEquals($expectedTags, $actualTags, "wrong $findItem in the response");
+					\sort($expectedTags);
+					$expectedTags = \implode(",", $expectedTags);
+					$actualTags = [];
+					foreach ($responseValues as $responseValue) {
+						$responseValue = \explode(",", $responseValue);
+						\sort($responseValue);
+						$responseValue = \implode(",", $responseValue);
+						$actualTags[] = $responseValue;
+					}
+					Assert::assertContainsEquals($expectedTags, $actualTags, "wrong $findItem in the response");
 					break;
 				case "d:lockdiscovery/d:activelock/d:timeout":
 					if ($value === "Infinity") {
-						Assert::assertEquals($value, $responseValue, "wrong $findItem in the response");
+						Assert::assertContainsEquals($value, $responseValues, "wrong $findItem in the response");
 					} else {
 						// some time may be required between a lock and propfind request.
-						$responseValue = explode('-', $responseValue);
+						$responseValue = explode('-', $responseValues[0]);
 						$responseValue = \intval($responseValue[1]);
 						$value = explode('-', $value);
 						$value = \intval($value[1]);
@@ -3769,7 +3822,7 @@ class SpacesContext implements Context {
 					}
 					break;
 				default:
-					Assert::assertEquals($value, $responseValue, "wrong $findItem in the response");
+					Assert::assertContainsEquals($value, $responseValues, "wrong $findItem in the response");
 					break;
 			}
 		}
diff --git a/tests/acceptance/features/bootstrap/WebDavLockingContext.php b/tests/acceptance/features/bootstrap/WebDavLockingContext.php
index 3fc79a939b5..a74135f9881 100644
--- a/tests/acceptance/features/bootstrap/WebDavLockingContext.php
+++ b/tests/acceptance/features/bootstrap/WebDavLockingContext.php
@@ -914,7 +914,7 @@ class WebDavLockingContext implements Context {
 	 * @throws GuzzleException
 	 */
 	public function numberOfLockShouldBeReportedInProjectSpace(int $count, string $file, string $spaceName, string $user) {
-		$response = $this->spacesContext->sendPropfindRequestToSpace($user, $spaceName, $file);
+		$response = $this->spacesContext->sendPropfindRequestToSpace($user, $spaceName, $file, null, '0');
 		$this->featureContext->theHTTPStatusCodeShouldBe(207, "", $response);
 		$responseXml = $this->featureContext->getResponseXml($response);
 		$xmlPart = $responseXml->xpath("//d:response//d:lockdiscovery/d:activelock");