Repository: owncloud/ocis
Pull request #8668 (2024-03-15 16:52:16)

internal links shouldn't have a password
<!--
Thanks for submitting a change to ownCloud!

This is the bug tracker for the oCIS component. Find other components at https://github.com/owncloud/core/blob/master/.github/CONTRIBUTING.md#guidelines

For fixing potential security issues please see https://owncloud.org/security/

To make it possible for us to get your change reviewed and merged please carefully fill out the requested information below.

Please note that any kind of change needs first be submitted to the master branch which holds the next version of oCIS.

Please set the following labels:

- Set label "Status:Needs-Review" for review or "Status:In-Progress" if the PR still has open tasks.
- Assignment: assign to self
- Reviewers: pick at least one
-->

## Description
<!--- Describe your changes in detail -->
Internal links shouldn't have a password

## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
- Fixes [https://github.com/owncloud/ocis/issues/8619](https://github.com/owncloud/ocis/issues/8619)

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- test environment:
- test case 1:
- test case 2:
- ...

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [x] Code changes
- [x] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd33411812c..8869d174de3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,6 +46,7 @@ The following sections list the changes for unreleased.
 * Bugfix - Fix remove/update share permissions: [#8529](https://github.com/owncloud/ocis/pull/8529)
 * Bugfix - Correct the default mapping of roles: [#8534](https://github.com/owncloud/ocis/pull/8534)
 * Bugfix - Fix graph drive invite: [#8538](https://github.com/owncloud/ocis/pull/8538)
+* Bugfix - Fix the mount points naming: [#8543](https://github.com/owncloud/ocis/pull/8543)
 * 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)
@@ -59,6 +60,7 @@ The following sections list the changes for unreleased.
 * Enhancement - Bump Reva: [#8606](https://github.com/owncloud/ocis/pull/8606)
 * Enhancement - Add the spaceID to sse: [#8614](https://github.com/owncloud/ocis/pull/8614)
 * Enhancement - Update web to v9.0.0-alpha.2: [#8634](https://github.com/owncloud/ocis/pull/8634)
+* Enhancement - The graph endpoints for listing permission works for spaces now: [#8642](https://github.com/owncloud/ocis/pull/8642)
 
 ## Details
 
@@ -95,6 +97,14 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/issues/8494
    https://github.com/owncloud/ocis/pull/8538
 
+* Bugfix - Fix the mount points naming: [#8543](https://github.com/owncloud/ocis/pull/8543)
+
+   We fixed a bug that caused inconsistent naming when multiple users share the
+   resource with same name to another user.
+
+   https://github.com/owncloud/ocis/issues/8471
+   https://github.com/owncloud/ocis/pull/8543
+
 * Bugfix - We now always select the next clients when autoaccepting shares: [#8570](https://github.com/owncloud/ocis/pull/8570)
 
    https://github.com/owncloud/ocis/pull/8570
@@ -231,6 +241,15 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/pull/8634
    https://github.com/owncloud/web/releases/tag/v9.0.0-alpha.2
 
+* Enhancement - The graph endpoints for listing permission works for spaces now: [#8642](https://github.com/owncloud/ocis/pull/8642)
+
+   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/issues/8352
+   https://github.com/owncloud/ocis/pull/8642
+
 # Changelog for [5.0.0-rc.5] (2024-02-26)
 
 The following sections list the changes for 5.0.0-rc.5.
diff --git a/changelog/unreleased/fix-graph-internal-link.md b/changelog/unreleased/fix-graph-internal-link.md
new file mode 100644
index 00000000000..ba9a72c9da4
--- /dev/null
+++ b/changelog/unreleased/fix-graph-internal-link.md
@@ -0,0 +1,6 @@
+Bugfix: Internal links shouldn't have a password
+
+Internal links shouldn't have a password when create/update
+
+https://github.com/owncloud/ocis/pull/8668
+https://github.com/owncloud/ocis/issues/8619
diff --git a/changelog/unreleased/fix-mount-points-naming.md b/changelog/unreleased/fix-mount-points-naming.md
new file mode 100644
index 00000000000..f188aeb72a9
--- /dev/null
+++ b/changelog/unreleased/fix-mount-points-naming.md
@@ -0,0 +1,6 @@
+Bugfix: Fix the mount points naming
+
+We fixed a bug that caused inconsistent naming when multiple users share the resource with same name to another user.
+
+https://github.com/owncloud/ocis/pull/8543
+https://github.com/owncloud/ocis/issues/8471
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 859e7cb34fd..d068ec8b8a6 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/cenkalti/backoff v2.2.1+incompatible
 	github.com/coreos/go-oidc/v3 v3.9.0
 	github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
-	github.com/cs3org/reva/v2 v2.19.2-0.20240312150024-04c0a3136ac9
+	github.com/cs3org/reva/v2 v2.19.2-0.20240313154849-352a246529ff
 	github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
 	github.com/disintegration/imaging v1.6.2
 	github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
diff --git a/go.sum b/go.sum
index d465ca97e72..38deb30ad8a 100644
--- a/go.sum
+++ b/go.sum
@@ -1018,8 +1018,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
 github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
-github.com/cs3org/reva/v2 v2.19.2-0.20240312150024-04c0a3136ac9 h1:7DdURTnE38PoSLxsYjZ8ZXyknc53PgD4M1KQT24eX88=
-github.com/cs3org/reva/v2 v2.19.2-0.20240312150024-04c0a3136ac9/go.mod h1:GRUrOp5HbFVwZTgR9bVrMZ/MvVy+Jhxw1PdMmhhKP9E=
+github.com/cs3org/reva/v2 v2.19.2-0.20240313154849-352a246529ff h1:XW1j4lf3EWfB9/fKN3D8Q1mehNvrlmGuXdVVzWLtFDs=
+github.com/cs3org/reva/v2 v2.19.2-0.20240313154849-352a246529ff/go.mod h1:GRUrOp5HbFVwZTgR9bVrMZ/MvVy+Jhxw1PdMmhhKP9E=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
 github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
 github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
diff --git a/ocis-pkg/middleware/oidc.go b/ocis-pkg/middleware/oidc.go
index d66c2213e51..4c8c533467c 100644
--- a/ocis-pkg/middleware/oidc.go
+++ b/ocis-pkg/middleware/oidc.go
@@ -44,7 +44,7 @@ func OidcAuth(opts ...Option) func(http.Handler) http.Handler {
 		)
 	}
 	var provider OIDCProvider
-	getProviderOnce := sync.Once{}
+	initializeProviderLock := sync.Mutex{}
 
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -52,13 +52,22 @@ func OidcAuth(opts ...Option) func(http.Handler) http.Handler {
 			authHeader := r.Header.Get("Authorization")
 			switch {
 			case strings.HasPrefix(authHeader, "Bearer "):
-				getProviderOnce.Do(func() {
+				if provider == nil {
+					// lazy initialize provider
+					initializeProviderLock.Lock()
 					var err error
-					provider, err = providerFunc()
+					// ensure no other request initialized the provider
+					if provider == nil {
+						provider, err = providerFunc()
+					}
+					initializeProviderLock.Unlock()
 					if err != nil {
+						opt.Logger.Error().Err(err).Msg("could not initialize OIDC provider")
+						w.WriteHeader(http.StatusInternalServerError)
 						return
 					}
-				})
+					opt.Logger.Debug().Msg("initialized OIDC provider")
+				}
 
 				oauth2Token := &oauth2.Token{
 					AccessToken: strings.TrimPrefix(authHeader, "Bearer "),
diff --git a/services/frontend/pkg/command/events.go b/services/frontend/pkg/command/events.go
index 248843115e3..b3d39331c3d 100644
--- a/services/frontend/pkg/command/events.go
+++ b/services/frontend/pkg/command/events.go
@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"path"
 	"path/filepath"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 
@@ -171,49 +171,53 @@ func getMountpoint(ctx context.Context, l log.Logger, itemid *provider.ResourceI
 	// we need to sort the received shares by mount point in order to make things easier to evaluate.
 	base := path.Base(info.GetPath())
 	mount := base
-	mountedShares := make([]*collaboration.ReceivedShare, 0, len(lrs.Shares))
+	mounts := make([]string, 0, len(lrs.Shares))
+	var exists bool
+
 	for _, s := range lrs.Shares {
 		if s.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
 			// we don't care about unaccepted shares
 			continue
 		}
 
-		if utils.ResourceIDEqual(s.Share.ResourceId, itemid) {
+		if utils.ResourceIDEqual(s.GetShare().GetResourceId(), itemid) {
 			// a share to the same resource already exists and is mounted
-			return s.MountPoint.Path, nil
+			return s.GetMountPoint().GetPath(), nil
 		}
 
-		mountedShares = append(mountedShares, s)
-	}
-
-	sort.Slice(mountedShares, func(i, j int) bool {
-		return mountedShares[i].MountPoint.Path > mountedShares[j].MountPoint.Path
-	})
-
-	// now we have a list of shares, we want to iterate over all of them and check for name collisions
-	for i, ms := range mountedShares {
-		if ms.MountPoint.Path == mount {
+		if s.GetMountPoint().GetPath() == mount {
 			// does the shared resource still exist?
 			gwc, err := gatewaySelector.Next()
 			if err != nil {
 				l.Error().Err(err).Msg("cannot get gateway client")
 				continue
 			}
-			_, err = utils.GetResourceByID(ctx, ms.Share.ResourceId, gwc)
+			_, err = utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
 			if err == nil {
-				// The mount point really already exists, we need to insert a number into the filename
-				ext := filepath.Ext(base)
-				name := strings.TrimSuffix(base, ext)
-				// be smart about .tar.(gz|bz) files
-				if strings.HasSuffix(name, ".tar") {
-					name = strings.TrimSuffix(name, ".tar")
-					ext = ".tar" + ext
-				}
-
-				mount = fmt.Sprintf("%s (%s)%s", name, strconv.Itoa(i+1), ext)
+				exists = true
 			}
 			// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
 		}
+		// collect all mount points
+		mounts = append(mounts, s.GetMountPoint().GetPath())
+	}
+
+	// If the mount point really already exists, we need to insert a number into the filename
+	if exists {
+		// now we have a list of shares, we want to iterate over all of them and check for name collisions agents a mount points list
+		for i := 1; i <= len(mounts)+1; i++ {
+			ext := filepath.Ext(base)
+			name := strings.TrimSuffix(base, ext)
+			// be smart about .tar.(gz|bz) files
+			if strings.HasSuffix(name, ".tar") {
+				name = strings.TrimSuffix(name, ".tar")
+				ext = ".tar" + ext
+			}
+			mount = name + " (" + strconv.Itoa(i) + ")" + ext
+			if !slices.Contains(mounts, mount) {
+				return mount, nil
+			}
+		}
 	}
 	return mount, nil
 }
diff --git a/services/frontend/pkg/config/defaults/defaultconfig.go b/services/frontend/pkg/config/defaults/defaultconfig.go
index 5a9b81c9acb..c19f3fb45ef 100644
--- a/services/frontend/pkg/config/defaults/defaultconfig.go
+++ b/services/frontend/pkg/config/defaults/defaultconfig.go
@@ -38,6 +38,7 @@ func DefaultConfig() *config.Config {
 					"GET",
 					"PUT",
 					"POST",
+					"PATCH",
 					"DELETE",
 					"MKCOL",
 					"PROPFIND",
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/links.go b/services/graph/pkg/service/v0/links.go
index 0a71dc4fef1..8c8dbc3022e 100644
--- a/services/graph/pkg/service/v0/links.go
+++ b/services/graph/pkg/service/v0/links.go
@@ -133,6 +133,9 @@ func (g Graph) createLink(ctx context.Context, driveItemID *providerv1beta1.Reso
 		g.logger.Debug().Interface("createLink", createLink).Msg(err.Error())
 		return nil, errorcode.New(errorcode.InvalidRequest, "invalid link type")
 	}
+	if createLink.GetType() == libregraph.INTERNAL && len(createLink.GetPassword()) > 0 {
+		return nil, errorcode.New(errorcode.InvalidRequest, "password is redundant for the internal link")
+	}
 	req := link.CreatePublicShareRequest{
 		ResourceInfo: statResp.GetInfo(),
 		Grant: &link.Grant{
@@ -312,6 +315,13 @@ func (g Graph) updatePublicLinkPermission(ctx context.Context, permissionID stri
 		if err != nil {
 			return nil, err
 		}
+		// reset the password for the internal link
+		if changedLink == libregraph.INTERNAL {
+			perm, err = g.updatePublicLinkPassword(ctx, permissionID, "")
+			if err != nil {
+				return nil, err
+			}
+		}
 	}
 
 	return perm, err
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/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go
index ca6c28df5d9..5ab1c1782c8 100644
--- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go
+++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go
@@ -24,7 +24,7 @@ import (
 	"net/http"
 	"path"
 	"path/filepath"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 
@@ -128,55 +128,62 @@ func GetMountpointAndUnmountedShares(ctx context.Context, gwc gateway.GatewayAPI
 	}
 
 	// we need to sort the received shares by mount point in order to make things easier to evaluate.
-	mount := filepath.Clean(info.Name)
+	base := filepath.Clean(info.Name)
+	mount := base
 	existingMountpoint := ""
-	mountedShares := make([]*collaboration.ReceivedShare, 0, len(receivedShares))
+	mountedShares := make([]string, 0, len(receivedShares))
+	var pathExists bool
+
 	for _, s := range receivedShares {
-		if utils.ResourceIDEqual(s.Share.ResourceId, info.GetId()) {
-			if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
-				// a share to the resource already exists and is mounted, remember the mount point
-				_, err := utils.GetResourceByID(ctx, s.Share.ResourceId, gwc)
-				if err == nil {
-					existingMountpoint = s.MountPoint.Path
-				}
-			} else {
-				// a share to the resource already exists but is not mounted, collect the unmounted share
-				unmountedShares = append(unmountedShares, s)
+		resourceIDEqual := utils.ResourceIDEqual(s.GetShare().GetResourceId(), info.GetId())
+
+		if resourceIDEqual && s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
+			// a share to the resource already exists and is mounted, remember the mount point
+			_, err := utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
+			if err == nil {
+				existingMountpoint = s.GetMountPoint().GetPath()
 			}
 		}
 
+		if resourceIDEqual && s.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
+			// a share to the resource already exists but is not mounted, collect the unmounted share
+			unmountedShares = append(unmountedShares, s)
+		}
+
 		if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
-			mountedShares = append(mountedShares, s)
+			// collect all accepted mount points
+			mountedShares = append(mountedShares, s.GetMountPoint().GetPath())
+			if s.GetMountPoint().GetPath() == mount {
+				// does the shared resource still exist?
+				_, err := utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
+				if err == nil {
+					pathExists = true
+				}
+				// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
+			}
 		}
 	}
 
-	sort.Slice(mountedShares, func(i, j int) bool {
-		return mountedShares[i].MountPoint.Path > mountedShares[j].MountPoint.Path
-	})
-
 	if existingMountpoint != "" {
 		// we want to reuse the same mountpoint for all unmounted shares to the same resource
 		return existingMountpoint, unmountedShares, nil
 	}
 
-	// we have a list of shares, we want to iterate over all of them and check for name collisions
-	for i, ms := range mountedShares {
-		if ms.MountPoint.Path == mount {
-			// does the shared resource still exist?
-			_, err := utils.GetResourceByID(ctx, ms.Share.ResourceId, gwc)
-			if err == nil {
-				// The mount point really already exists, we need to insert a number into the filename
-				ext := filepath.Ext(mount)
-				name := strings.TrimSuffix(mount, ext)
-				// be smart about .tar.(gz|bz) files
-				if strings.HasSuffix(name, ".tar") {
-					name = strings.TrimSuffix(name, ".tar")
-					ext = ".tar" + ext
-				}
-
-				mount = fmt.Sprintf("%s (%s)%s", name, strconv.Itoa(i+1), ext)
+	// If the mount point really already exists, we need to insert a number into the filename
+	if pathExists {
+		// now we have a list of shares, we want to iterate over all of them and check for name collisions agents a mount points list
+		for i := 1; i <= len(mountedShares)+1; i++ {
+			ext := filepath.Ext(base)
+			name := strings.TrimSuffix(base, ext)
+			// be smart about .tar.(gz|bz) files
+			if strings.HasSuffix(name, ".tar") {
+				name = strings.TrimSuffix(name, ".tar")
+				ext = ".tar" + ext
+			}
+			mount = name + " (" + strconv.Itoa(i) + ")" + ext
+			if !slices.Contains(mountedShares, mount) {
+				return mount, unmountedShares, nil
 			}
-			// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
 		}
 	}
 	return mount, unmountedShares, nil
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 17a5b2455fe..31c0df2157b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -359,7 +359,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
 github.com/cs3org/go-cs3apis/cs3/types/v1beta1
-# github.com/cs3org/reva/v2 v2.19.2-0.20240312150024-04c0a3136ac9
+# github.com/cs3org/reva/v2 v2.19.2-0.20240313154849-352a246529ff
 ## explicit; go 1.21
 github.com/cs3org/reva/v2/cmd/revad/internal/grace
 github.com/cs3org/reva/v2/cmd/revad/runtime


Test patch:
diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go
index 69cacd7e90a..3f5f71305ed 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"
@@ -813,6 +814,75 @@ var _ = Describe("Driveitems", func() {
 			linkType := res.Link.GetType()
 			Expect(string(linkType)).To(Equal("edit"))
 		})
+		It("updates the public share to internal link", func() {
+			getShareMockResponse.Share = nil
+			getShareMockResponse.Status = status.NewNotFound(ctx, "not found")
+
+			getPublicShareMock := gatewayClient.On("GetPublicShare",
+				mock.Anything,
+				mock.MatchedBy(func(req *link.GetPublicShareRequest) bool {
+					return req.GetRef().GetId().GetOpaqueId() == "permissionid"
+				}),
+			)
+			getPublicShareMockResponse = &link.GetPublicShareResponse{
+				Status: status.NewOK(ctx),
+				Share: &link.PublicShare{
+					Id: &link.PublicShareId{
+						OpaqueId: "permissionid",
+					},
+					ResourceId: &provider.ResourceId{
+						StorageId: "1",
+						SpaceId:   "2",
+						OpaqueId:  "3",
+					},
+					PasswordProtected: true,
+					Permissions: &link.PublicSharePermissions{
+						Permissions: linktype.NewFileEditLinkPermissionSet().GetPermissions(),
+					},
+					Token: "token",
+				},
+			}
+			getPublicShareMock.Return(getPublicShareMockResponse, nil)
+
+			newLink := libregraph.NewSharingLink()
+			newLinkType, err := libregraph.NewSharingLinkTypeFromValue("internal")
+			Expect(err).ToNot(HaveOccurred())
+			newLink.SetType(*newLinkType)
+
+			updatePublicShareMock := gatewayClient.On("UpdatePublicShare",
+				mock.Anything,
+				mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool {
+					return req.GetRef().GetId().GetOpaqueId() == "permissionid"
+				}),
+			)
+
+			updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{
+				Permissions: linktype.NewInternalLinkPermissionSet().Permissions,
+			}
+			updatePublicShareMock.Return(updatePublicShareMockResponse, nil)
+
+			driveItemPermission.SetLink(*newLink)
+			body, err := driveItemPermission.MarshalJSON()
+			Expect(err).To(BeNil())
+			svc.UpdatePermission(
+				rr,
+				httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))).
+					WithContext(ctx),
+			)
+			Expect(rr.Code).To(Equal(http.StatusOK))
+			data, err := io.ReadAll(rr.Body)
+			Expect(err).ToNot(HaveOccurred())
+
+			res := libregraph.Permission{}
+
+			err = json.Unmarshal(data, &res)
+			Expect(err).ToNot(HaveOccurred())
+			linkType := res.Link.GetType()
+			Expect(string(linkType)).To(Equal("internal"))
+			pp, hasPP := res.GetHasPasswordOk()
+			Expect(hasPP).To(Equal(true))
+			Expect(*pp).To(Equal(false))
+		})
 		It("updates the password on a public share", func() {
 			getShareMockResponse.Share = nil
 			getShareMockResponse.Status = status.NewNotFound(ctx, "not found")
@@ -1027,7 +1097,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 +1226,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 +1237,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 +1278,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 +1301,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/apiAccountsHashDifficulty/addUser.feature b/tests/acceptance/features/apiAccountsHashDifficulty/addUser.feature
index 4d7b03b80bf..f7bee673a2b 100644
--- a/tests/acceptance/features/apiAccountsHashDifficulty/addUser.feature
+++ b/tests/acceptance/features/apiAccountsHashDifficulty/addUser.feature
@@ -9,14 +9,14 @@ Feature: add user
 
 
   Scenario Outline: admin creates a user
-    Given using OCS API version "<ocs_api_version>"
+    Given using OCS API version "<ocs-api-version>"
     And user "brand-new-user" has been deleted
     When the administrator sends a user creation request for user "brand-new-user" password "%alt1%" using the provisioning API
-    Then the OCS status code should be "<ocs_status_code>"
+    Then the OCS status code should be "<ocs-status-code>"
     And the HTTP status code should be "200"
     And user "brand-new-user" should exist
     And user "brand-new-user" should be able to access a skeleton file
     Examples:
-      | ocs_api_version | ocs_status_code |
+      | ocs-api-version | ocs-status-code |
       | 1               | 100             |
       | 2               | 200             |
diff --git a/tests/acceptance/features/apiAccountsHashDifficulty/assignRole.feature b/tests/acceptance/features/apiAccountsHashDifficulty/assignRole.feature
index cdcd2710663..e4ec81b7834 100644
--- a/tests/acceptance/features/apiAccountsHashDifficulty/assignRole.feature
+++ b/tests/acceptance/features/apiAccountsHashDifficulty/assignRole.feature
@@ -6,11 +6,11 @@ Feature: assign role
 
   Scenario Outline: only admin user can see all existing roles
     Given user "Alice" has been created with default attributes and without skeleton files
-    And the administrator has given "Alice" the role "<userRole>" using the settings api
+    And the administrator has given "Alice" the role "<user-role>" using the settings api
     When user "Alice" tries to get all existing roles
-    Then the HTTP status code should be "<statusCode>"
+    Then the HTTP status code should be "<http-status-code>"
     Examples:
-      | userRole    | statusCode |
+      | user-role    | http-status-code |
       | Admin       | 201        |
       | Space Admin | 201        |
       | User        | 201        |
@@ -18,11 +18,11 @@ Feature: assign role
   @issue-5032
   Scenario Outline: only admin user can see assignments list
     Given user "Alice" has been created with default attributes and without skeleton files
-    And the administrator has given "Alice" the role "<userRole>" using the settings api
+    And the administrator has given "Alice" the role "<user-role>" using the settings api
     When user "Alice" tries to get list of assignment
-    Then the HTTP status code should be "<statusCode>"
+    Then the HTTP status code should be "<http-status-code>"
     Examples:
-      | userRole    | statusCode |
+      | user-role    | http-status-code |
       | Admin       | 201        |
       | Space Admin | 401        |
       | User        | 401        |
@@ -30,12 +30,12 @@ Feature: assign role
 
   Scenario Outline: a user cannot change own role
     Given user "Alice" has been created with default attributes and without skeleton files
-    And the administrator has given "Alice" the role "<userRole>" using the settings api
-    When user "Alice" changes his own role to "<desiredRole>"
+    And the administrator has given "Alice" the role "<user-role>" using the settings api
+    When user "Alice" changes his own role to "<desired-role>"
     Then the HTTP status code should be "400"
-    And user "Alice" should have the role "<userRole>"
+    And user "Alice" should have the role "<user-role>"
     Examples:
-      | userRole    | desiredRole |
+      | user-role    | desired-role |
       | Admin       | User        |
       | Admin       | Space Admin |
       | Space Admin | Admin       |
@@ -49,12 +49,12 @@ Feature: assign role
       | username |
       | Alice    |
       | Brian    |
-    And the administrator has given "Alice" the role "<userRole>" using the settings api
-    When user "Alice" changes the role "<desiredRole>" for user "Brian"
-    Then the HTTP status code should be "<statusCode>"
-    And user "Brian" should have the role "<expectedRole>"
+    And the administrator has given "Alice" the role "<user-role>" using the settings api
+    When user "Alice" changes the role "<desired-role>" for user "Brian"
+    Then the HTTP status code should be "<http-status-code>"
+    And user "Brian" should have the role "<expected-role>"
     Examples:
-      | userRole    | desiredRole | statusCode | expectedRole |
+      | user-role    | desired-role | http-status-code | expected-role |
       | Admin       | User        | 201        | User         |
       | Admin       | Space Admin | 201        | Space Admin  |
       | Admin       | Admin       | 201        | Admin        |
diff --git a/tests/acceptance/features/apiAccountsHashDifficulty/createShareToSharesFolder.feature b/tests/acceptance/features/apiAccountsHashDifficulty/createShareToSharesFolder.feature
index 2f606e96447..df198e48281 100644
--- a/tests/acceptance/features/apiAccountsHashDifficulty/createShareToSharesFolder.feature
+++ b/tests/acceptance/features/apiAccountsHashDifficulty/createShareToSharesFolder.feature
@@ -9,13 +9,13 @@ Feature: sharing
 
 
   Scenario Outline: creating a share of a file with a user
-    Given using OCS API version "<ocs_api_version>"
+    Given using OCS API version "<ocs-api-version>"
     And user "Alice" has been created with default attributes and without skeleton files
     And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt"
     And user "Brian" has been created with default attributes and without skeleton files
     When user "Alice" shares file "textfile0.txt" with user "Brian" using the sharing API
     And the content of file "/Shares/textfile0.txt" for user "Brian" should be "ownCloud test text file 0"
     Examples:
-      | ocs_api_version |
+      | ocs-api-version |
       | 1               |
-      | 2               |
\ No newline at end of file
+      | 2               |
diff --git a/tests/acceptance/features/apiAccountsHashDifficulty/uploadFile.feature b/tests/acceptance/features/apiAccountsHashDifficulty/uploadFile.feature
index df9a0aa3f88..04232a2bea2 100644
--- a/tests/acceptance/features/apiAccountsHashDifficulty/uploadFile.feature
+++ b/tests/acceptance/features/apiAccountsHashDifficulty/uploadFile.feature
@@ -9,13 +9,13 @@ Feature: upload file
 
 
   Scenario Outline: upload a file and check download content
-    Given using OCS API version "<ocs_api_version>"
+    Given using OCS API version "<ocs-api-version>"
     And user "Alice" has been created with default attributes and without skeleton files
     And using <dav-path-version> DAV path
     When user "Alice" uploads file with content "uploaded content" to "/upload.txt" using the WebDAV API
     Then the content of file "/upload.txt" for user "Alice" should be "uploaded content"
     Examples:
-      | ocs_api_version | dav-path-version |
+      | ocs-api-version | dav-path-version |
       | 1               | old              |
       | 1               | new              |
       | 2               | old              |
diff --git a/tests/acceptance/features/apiSharingNg/linkShare.feature b/tests/acceptance/features/apiSharingNg/linkShare.feature
index 836514b7041..e2c5c218e7a 100644
--- a/tests/acceptance/features/apiSharingNg/linkShare.feature
+++ b/tests/acceptance/features/apiSharingNg/linkShare.feature
@@ -72,7 +72,7 @@ Feature: Create a share link for a resource
       | permissionsRole |
       | view            |
       | edit            |
-      | internal        |
+#      | internal        |
       | upload          |
       | createOnly      |
       | blocksDownload  |
@@ -143,7 +143,7 @@ Feature: Create a share link for a resource
       | permissionsRole |
       | view            |
       | edit            |
-      | internal        |
+#      | internal        |
       | blocksDownload  |
 
   @issue-7879
@@ -219,7 +219,7 @@ Feature: Create a share link for a resource
       | permissionsRole |
       | view            |
       | edit            |
-      | internal        |
+#      | internal        |
       | upload          |
       | createOnly      |
       | blocksDownload  |
@@ -297,7 +297,7 @@ Feature: Create a share link for a resource
       | permissionsRole |
       | view            |
       | edit            |
-      | internal        |
+#      | internal        |
       | blocksDownload  |
 
   @env-config @issue-7879
@@ -440,14 +440,14 @@ Feature: Create a share link for a resource
     Examples:
       | previousPermissionsRole | newPermissionsRole |
       | view                    | edit               |
-      | view                    | internal           |
+#      | view                    | internal           |
       | view                    | blocksDownload     |
       | edit                    | view               |
       | edit                    | blocksDownload     |
-      | view                    | internal           |
+#      | view                    | internal           |
       | blocksDownload          | edit               |
       | blocksDownload          | blocksDownload     |
-      | view                    | internal           |
+#      | view                    | internal           |
 
 
   Scenario: update expiration date of a file's link share
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
index 13938fdcfa5..5897c8040b3 100644
--- a/tests/acceptance/features/apiSharingNg/propfindShares.feature
+++ b/tests/acceptance/features/apiSharingNg/propfindShares.feature
@@ -11,35 +11,37 @@ Feature: propfind a shares
       | Carol    |
 
   @issue-4421
-  Scenario: sharee PROPFIND a shares when multiple user shares resources with same name
+  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        | textfile.txt |
-      | space           | Personal     |
-      | sharee          | Brian        |
-      | shareType       | user         |
-      | permissionsRole | Viewer       |
+      | resource        | <path>   |
+      | space           | Personal |
+      | sharee          | Brian    |
+      | shareType       | user     |
+      | permissionsRole | Viewer   |
     And user "Carol" has sent the following share invitation:
-      | resource        | textfile.txt |
-      | space           | Personal     |
-      | sharee          | Brian        |
-      | shareType       | user         |
-      | permissionsRole | Viewer       |
+      | 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          | textfile.txt |
-      | oc:permissions   | SR           |
-      | oc:size          | 8            |
-      | d:getcontenttype | text/plain   |
+      | 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          | textfile (1).txt |
-      | oc:permissions   | SR               |
-      | oc:size          | 8                |
-      | d:getcontenttype | text/plain       |
+      | 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          |