Repository: owncloud/ocis
Pull request #8254 (2024-01-19 16:18:16)

[test-only] add more CORS tests
We had CORS tests only for OCS api but test is skipped because: https://github.com/owncloud/ocis/issues/5195

I added tests for graph api(lists all available spaces) and webDave(propfind request). I'm not sure that I need cover all endpoints. If I wrong please point it out to me in the review

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=8254) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=8254&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=8254&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
No data about Duplication  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=8254)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=8254) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=8254&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=8254&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
No data about Duplication  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=8254)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=8254) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=8254&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=8254&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
No data about Duplication  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=8254)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c749f8486b..77349dfafe9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,7 +54,9 @@ The following sections list the changes for unreleased.
 * Bugfix - Graph/sharedWithMe works for shares from project spaces now: [#8233](https://github.com/owncloud/ocis/pull/8233)
 * Bugfix - Fix PATCH/DELETE status code for drives that don't support them: [#8235](https://github.com/owncloud/ocis/pull/8235)
 * Bugfix - Fix nats authentication: [#8236](https://github.com/owncloud/ocis/pull/8236)
+* Bugfix - Apply role constraints when creating shares via the graph API: [#8247](https://github.com/owncloud/ocis/pull/8247)
 * Bugfix - Fix concurrent access to a map: [#8269](https://github.com/owncloud/ocis/pull/8269)
+* Bugfix - Fix nats registry: [#8281](https://github.com/owncloud/ocis/pull/8281)
 * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985)
 * Enhancement - Update antivirus service: [#8062](https://github.com/owncloud/ocis/pull/8062)
 * Enhancement - Remove deprecated environment variables: [#8149](https://github.com/owncloud/ocis/pull/8149)
@@ -164,6 +166,15 @@ The following sections list the changes for unreleased.
 
    https://github.com/owncloud/ocis/pull/8236
 
+* Bugfix - Apply role constraints when creating shares via the graph API: [#8247](https://github.com/owncloud/ocis/pull/8247)
+
+   We fixed a bug in the graph API for creating and updating shares so that
+   Spaceroot specific roles like 'Manager' and 'Co-owner' can no longer be assigned
+   for shares on files or directories.
+
+   https://github.com/owncloud/ocis/issues/8131
+   https://github.com/owncloud/ocis/pull/8247
+
 * Bugfix - Fix concurrent access to a map: [#8269](https://github.com/owncloud/ocis/pull/8269)
 
    We fixed the race condition that led to concurrent map access in a publicshare
@@ -173,6 +184,15 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/pull/8269
    https://github.com/cs3org/reva/pull/4472
 
+* Bugfix - Fix nats registry: [#8281](https://github.com/owncloud/ocis/pull/8281)
+
+   The nats registry would behave badly when configuring `nats-js-kv` via envvar.
+   Reason is the way go-micro initializes. It took 5 developers to find the issue
+   and the fix so the details cannot be shared here. Just accept that it is working
+   now
+
+   https://github.com/owncloud/ocis/pull/8281
+
 * Enhancement - Disable the password policy: [#7985](https://github.com/owncloud/ocis/pull/7985)
 
    We add the environment variable that allow to disable the password policy.
diff --git a/changelog/unreleased/fix-nats-registry.md b/changelog/unreleased/fix-nats-registry.md
new file mode 100644
index 00000000000..ba5b29d0e38
--- /dev/null
+++ b/changelog/unreleased/fix-nats-registry.md
@@ -0,0 +1,6 @@
+Bugfix: Fix nats registry
+
+The nats registry would behave badly when configuring `nats-js-kv` via envvar. Reason is the way go-micro initializes.
+It took 5 developers to find the issue and the fix so the details cannot be shared here. Just accept that it is working now
+
+https://github.com/owncloud/ocis/pull/8281
diff --git a/changelog/unreleased/sharing-ng-roleconditions.md b/changelog/unreleased/sharing-ng-roleconditions.md
new file mode 100644
index 00000000000..efad06c1043
--- /dev/null
+++ b/changelog/unreleased/sharing-ng-roleconditions.md
@@ -0,0 +1,8 @@
+Bugfix: apply role constraints when creating shares via the graph API
+
+We fixed a bug in the graph API for creating and updating shares so that
+Spaceroot specific roles like 'Manager' and 'Co-owner' can no longer be
+assigned for shares on files or directories.
+
+https://github.com/owncloud/ocis/pull/8247
+https://github.com/owncloud/ocis/issues/8131
diff --git a/ocis-pkg/natsjsregistry/options.go b/ocis-pkg/natsjsregistry/options.go
index a6e862a62a2..dfcda616a59 100644
--- a/ocis-pkg/natsjsregistry/options.go
+++ b/ocis-pkg/natsjsregistry/options.go
@@ -10,7 +10,6 @@ import (
 
 type storeOptionsKey struct{}
 type expiryKey struct{}
-type authKey struct{}
 
 // StoreOptions sets the options for the underlying store
 func StoreOptions(opts []store.Option) registry.Option {
@@ -31,13 +30,3 @@ func ServiceExpiry(t time.Duration) registry.Option {
 		o.Context = context.WithValue(o.Context, expiryKey{}, t)
 	}
 }
-
-// Authenticate sets the username/password for the nats connection
-func Authenticate(username, password string) registry.Option {
-	return func(o *registry.Options) {
-		if o.Context == nil {
-			o.Context = context.Background()
-		}
-		o.Context = context.WithValue(o.Context, authKey{}, []string{username, password})
-	}
-}
diff --git a/ocis-pkg/natsjsregistry/registry.go b/ocis-pkg/natsjsregistry/registry.go
index 857b4e5198b..feeac744b9f 100644
--- a/ocis-pkg/natsjsregistry/registry.go
+++ b/ocis-pkg/natsjsregistry/registry.go
@@ -5,6 +5,8 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
+	"os"
+	"strings"
 	"time"
 
 	natsjskv "github.com/go-micro/plugins/v4/store/nats-js-kv"
@@ -14,7 +16,12 @@ import (
 	"go-micro.dev/v4/util/cmd"
 )
 
-var _registryName = "nats-js-kv"
+var (
+	_registryName        = "nats-js-kv"
+	_registryAddressEnv  = "MICRO_REGISTRY_ADDRESS"
+	_registryUsernameEnv = "MICRO_REGISTRY_AUTH_USERNAME"
+	_registryPasswordEnv = "MICRO_REGISTRY_AUTH_PASSWORD"
+)
 
 func init() {
 	cmd.DefaultRegistries[_registryName] = NewRegistry
@@ -31,7 +38,7 @@ func NewRegistry(opts ...registry.Option) registry.Registry {
 	exp, _ := options.Context.Value(expiryKey{}).(time.Duration)
 	return &storeregistry{
 		opts:   options,
-		store:  natsjskv.NewStore(append(storeOptions(options), natsjskv.DefaultMemory())...),
+		store:  natsjskv.NewStore(storeOptions(options)...),
 		typ:    _registryName,
 		expiry: exp,
 	}
@@ -127,18 +134,31 @@ func (n *storeregistry) String() string {
 
 func storeOptions(opts registry.Options) []store.Option {
 	storeoptions := []store.Option{
-		store.Nodes(opts.Addrs...),
 		store.Database("service-registry"),
 		store.Table("service-registry"),
+		natsjskv.DefaultMemory(),
 	}
-	if so, ok := opts.Context.Value(storeOptionsKey{}).([]store.Option); ok {
-		storeoptions = append(storeoptions, so...)
+
+	addr := []string{"127.0.0.1:9233"}
+	if len(opts.Addrs) > 0 {
+		addr = opts.Addrs
+	} else if a := strings.Split(os.Getenv(_registryAddressEnv), ","); len(a) > 0 && a[0] != "" {
+		addr = a
 	}
+	storeoptions = append(storeoptions, store.Nodes(addr...))
+
 	natsOptions := nats.GetDefaultOptions()
 	natsOptions.Name = "nats-js-kv-registry"
-	if auth, ok := opts.Context.Value(authKey{}).([]string); ok {
-		natsOptions.User = auth[0]
-		natsOptions.Password = auth[1]
+	natsOptions.User, natsOptions.Password = getAuth()
+	storeoptions = append(storeoptions, natsjskv.NatsOptions(natsOptions))
+
+	if so, ok := opts.Context.Value(storeOptionsKey{}).([]store.Option); ok {
+		storeoptions = append(storeoptions, so...)
 	}
-	return append(storeoptions, natsjskv.NatsOptions(natsOptions))
+
+	return storeoptions
+}
+
+func getAuth() (string, string) {
+	return os.Getenv(_registryUsernameEnv), os.Getenv(_registryPasswordEnv)
 }
diff --git a/ocis-pkg/registry/registry.go b/ocis-pkg/registry/registry.go
index 0b6b3e71f03..1672a1a8e17 100644
--- a/ocis-pkg/registry/registry.go
+++ b/ocis-pkg/registry/registry.go
@@ -20,10 +20,8 @@ import (
 )
 
 const (
-	registryEnv         = "MICRO_REGISTRY"
-	registryAddressEnv  = "MICRO_REGISTRY_ADDRESS"
-	registryUsernameEnv = "MICRO_REGISTRY_AUTH_USERNAME"
-	registryPasswordEnv = "MICRO_REGISTRY_AUTH_PASSWORD"
+	_registryEnv        = "MICRO_REGISTRY"
+	_registryAddressEnv = "MICRO_REGISTRY_ADDRESS"
 )
 
 var (
@@ -64,7 +62,6 @@ func GetRegistry(opts ...Option) mRegistry.Registry {
 		case "natsjs", "nats-js", "nats-js-kv": // for backwards compatibility - we will stick with one of those
 			_reg = natsjsregistry.NewRegistry(
 				mRegistry.Addrs(cfg.Addresses...),
-				natsjsregistry.Authenticate(cfg.Username, cfg.Password),
 			)
 		case "memory":
 			_reg = memr.NewRegistry()
@@ -112,15 +109,13 @@ func getEnvs(opts ...Option) *Config {
 	cfg := &Config{
 		Type:      "nats-js-kv",
 		Addresses: []string{"127.0.0.1:9233"},
-		Username:  os.Getenv(registryUsernameEnv),
-		Password:  os.Getenv(registryPasswordEnv),
 	}
 
-	if s := os.Getenv(registryEnv); s != "" {
+	if s := os.Getenv(_registryEnv); s != "" {
 		cfg.Type = s
 	}
 
-	if s := strings.Split(os.Getenv(registryAddressEnv), ","); len(s) > 0 && s[0] != "" {
+	if s := strings.Split(os.Getenv(_registryAddressEnv), ","); len(s) > 0 && s[0] != "" {
 		cfg.Addresses = s
 	}
 
diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go
index da010478b4f..ea6fc0ad757 100644
--- a/services/graph/pkg/service/v0/driveitems.go
+++ b/services/graph/pkg/service/v0/driveitems.go
@@ -378,6 +378,11 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	condition := unifiedrole.UnifiedRoleConditionGrantee
+	if IsSpaceRoot(statResponse.GetInfo().GetId()) {
+		condition = unifiedrole.UnifiedRoleConditionOwner
+	}
+
 	permissionSet := *statResponse.GetInfo().GetPermissionSet()
 	allowedActions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(permissionSet)
 
@@ -386,7 +391,7 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
 		LibreGraphPermissionsRolesAllowedValues: conversions.ToValueSlice(
 			unifiedrole.GetApplicableRoleDefinitionsForActions(
 				allowedActions,
-				unifiedrole.UnifiedRoleConditionGrantee,
+				condition,
 				g.config.FilesSharing.EnableResharing,
 				false,
 			),
@@ -452,6 +457,18 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
+	if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
+		g.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
+		errCode.Render(w, r)
+		return
+	}
+
+	condition := unifiedrole.UnifiedRoleConditionGrantee
+	if IsSpaceRoot(statResponse.GetInfo().GetId()) {
+		condition = unifiedrole.UnifiedRoleConditionOwner
+	}
+
 	unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: driveItemInvite.LibreGraphPermissionsActions}}
 	for _, roleID := range driveItemInvite.GetRoles() {
 		role, err := unifiedrole.NewUnifiedRoleFromID(roleID, g.config.FilesSharing.EnableResharing)
@@ -461,14 +478,13 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
-		unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...)
-	}
+		allowedResourceActions := unifiedrole.GetAllowedResourceActions(role, condition)
+		if len(allowedResourceActions) == 0 {
+			errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "role not applicable to this resource")
+			return
+		}
 
-	statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
-	if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
-		g.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
-		errCode.Render(w, r)
-		return
+		unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...)
 	}
 
 	driveRecipient := driveItemInvite.GetRecipients()[0]
@@ -486,7 +502,7 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
 	}
 
 	permission := &libregraph.Permission{}
-	if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, unifiedrole.UnifiedRoleConditionGrantee, g.config.FilesSharing.EnableResharing); role != nil {
+	if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition, g.config.FilesSharing.EnableResharing); role != nil {
 		permission.Roles = []string{role.GetId()}
 	}
 
diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go
index 58890f8e81c..fce338767c1 100644
--- a/services/graph/pkg/unifiedrole/unifiedrole.go
+++ b/services/graph/pkg/unifiedrole/unifiedrole.go
@@ -153,7 +153,7 @@ func NewCoownerUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		RolePermissions: []libregraph.UnifiedRolePermission{
 			{
 				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionGrantee),
+				Condition:              proto.String(UnifiedRoleConditionOwner),
 			},
 		},
 		LibreGraphWeight: proto.Int32(0),
@@ -185,10 +185,6 @@ func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition {
 		Description: proto.String("Grants manager permissions on a resource. Semantically equivalent to co-owner"),
 		DisplayName: displayName(r),
 		RolePermissions: []libregraph.UnifiedRolePermission{
-			{
-				AllowedResourceActions: convert(r),
-				Condition:              proto.String(UnifiedRoleConditionGrantee),
-			},
 			{
 				AllowedResourceActions: convert(r),
 				Condition:              proto.String(UnifiedRoleConditionOwner),


Test patch:
diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go
index 7513ec290cd..69cacd7e90a 100644
--- a/services/graph/pkg/service/v0/driveitems_test.go
+++ b/services/graph/pkg/service/v0/driveitems_test.go
@@ -685,6 +685,25 @@ var _ = Describe("Driveitems", func() {
 			_, ok := res.GetRolesOk()
 			Expect(ok).To(BeTrue())
 		})
+		It("fails to update the share permissions for a file share when setting a space specific role", func() {
+			updateShareMock := gatewayClient.On("UpdateShare",
+				mock.Anything,
+				mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool {
+					return req.GetShare().GetId().GetOpaqueId() == "permissionid"
+				}),
+			)
+			updateShareMock.Return(updateShareMockResponse, nil)
+
+			driveItemPermission.SetRoles([]string{unifiedrole.NewSpaceViewerUnifiedRole().GetId()})
+			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.StatusBadRequest))
+		})
 		It("updates the share permissions when changing the resource permission actions", func() {
 			updateShareMock := gatewayClient.On("UpdateShare",
 				mock.Anything,
@@ -1007,6 +1026,17 @@ var _ = Describe("Driveitems", func() {
 			Expect(jsonData.Get("0.roles.0").String()).To(Equal(unifiedrole.NewViewerUnifiedRole(true).GetId()))
 		})
 
+		It("fails with wrong role", func() {
+			driveItemInvite.Roles = []string{unifiedrole.NewCoownerUnifiedRole().GetId()}
+			svc.Invite(
+				rr,
+				httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
+					WithContext(ctx),
+			)
+
+			Expect(rr.Code).To(Equal(http.StatusBadRequest))
+		})
+
 		It("with actions (happy path)", func() {
 			driveItemInvite.Roles = nil
 			driveItemInvite.LibreGraphPermissionsActions = []string{unifiedrole.DriveItemContentRead}
diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go
index f11225a5a5e..12248a375b2 100644
--- a/services/graph/pkg/unifiedrole/unifiedrole_test.go
+++ b/services/graph/pkg/unifiedrole/unifiedrole_test.go
@@ -27,8 +27,7 @@ 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.UnifiedRoleConditionGrantee),
-		Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), 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),
@@ -208,6 +207,17 @@ var _ = Describe("unifiedroles", func() {
 					unifiedrole.NewViewerUnifiedRole(false),
 					unifiedrole.NewFileEditorUnifiedRole(false),
 					unifiedrole.NewEditorUnifiedRole(false),
+				},
+			),
+
+			Entry(
+				"GetBuiltinRoleDefinitionList",
+				rolesToAction(unifiedrole.GetBuiltinRoleDefinitionList(false)...),
+				unifiedrole.UnifiedRoleConditionOwner,
+				false,
+				[]*libregraph.UnifiedRoleDefinition{
+					unifiedrole.NewSpaceViewerUnifiedRole(),
+					unifiedrole.NewSpaceEditorUnifiedRole(),
 					unifiedrole.NewCoownerUnifiedRole(),
 					unifiedrole.NewManagerUnifiedRole(),
 				},
@@ -223,8 +233,6 @@ var _ = Describe("unifiedroles", func() {
 					unifiedrole.NewViewerUnifiedRole(true),
 					unifiedrole.NewFileEditorUnifiedRole(true),
 					unifiedrole.NewEditorUnifiedRole(true),
-					unifiedrole.NewCoownerUnifiedRole(),
-					unifiedrole.NewManagerUnifiedRole(),
 				},
 			),
 
diff --git a/tests/TestHelpers/WebDavHelper.php b/tests/TestHelpers/WebDavHelper.php
index 651ac675370..728416f8939 100644
--- a/tests/TestHelpers/WebDavHelper.php
+++ b/tests/TestHelpers/WebDavHelper.php
@@ -179,6 +179,7 @@ class WebDavHelper {
 	 * @param string|null $type
 	 * @param int|null $davPathVersionToUse
 	 * @param string|null $doDavRequestAsUser
+	 * @param array|null $headers
 	 *
 	 * @return ResponseInterface
 	 * @throws Exception
@@ -194,7 +195,8 @@ class WebDavHelper {
 		?string $folderDepth = '1',
 		?string $type = "files",
 		?int $davPathVersionToUse = self::DAV_VERSION_NEW,
-		?string $doDavRequestAsUser = null
+		?string $doDavRequestAsUser = null,
+		?array $headers = []
 	):ResponseInterface {
 		$body = self::getBodyForPropfind($properties);
 		$folderDepth = (string) $folderDepth;
diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
index 83703bdc479..e4ecfd70210 100644
--- a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
@@ -258,10 +258,10 @@ The expected failures in this file are from features in the owncloud/ocis repo.
 - [apiSharingNg/linkShare.feature:453](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L453)
 - [apiSharingNg/linkShare.feature:455](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L455)
 - [apiSharingNg/linkShare.feature:456](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/linkShare.feature#L456)
-- [apiSharingNg/deletePermissions.feature:138](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L138)
-- [apiSharingNg/deletePermissions.feature:155](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L155)
-- [apiSharingNg/deletePermissions.feature:176](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L176)
-- [apiSharingNg/deletePermissions.feature:195](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L195)
+- [apiSharingNg/deletePermissions.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L130)
+- [apiSharingNg/deletePermissions.feature:147](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L147)
+- [apiSharingNg/deletePermissions.feature:168](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L168)
+- [apiSharingNg/deletePermissions.feature:187](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSharingNg/deletePermissions.feature#L187)
 
 ### [sharee (editor role) MOVE a file by file-id into same shared folder returns 403](https://github.com/owncloud/ocis/issues/7617)
 
diff --git a/tests/acceptance/features/apiCors/cors.feature b/tests/acceptance/features/apiCors/cors.feature
index 28131c8a08f..6b59cd45f72 100644
--- a/tests/acceptance/features/apiCors/cors.feature
+++ b/tests/acceptance/features/apiCors/cors.feature
@@ -33,8 +33,8 @@ Feature: CORS headers
   Scenario Outline: CORS headers should not be returned when CORS domain does not match origin header
     Given using OCS API version "<ocs_api_version>"
     When user "Alice" sends HTTP method "GET" to OCS API endpoint "<endpoint>" with headers
-      | header | value               |
-      | Origin | https://mero.badal  |
+      | header | value              |
+      | Origin | https://mero.badal |
     Then the OCS status code should be "<ocs-code>"
     And the HTTP status code should be "<http-code>"
     And the following headers should not be set
@@ -70,3 +70,23 @@ Feature: CORS headers
       | 2               |  | /apps/files_sharing/api/v1/shares | PUT            |
       | 1               |  | /apps/files_sharing/api/v1/shares | DELETE         |
       | 2               |  | /apps/files_sharing/api/v1/shares | POST           |
+
+
+  Scenario: CORS headers should be returned when setting CORS domain sending origin header in the Graph api
+    When user "Alice" lists all available spaces with headers using the Graph API
+      | header | value               |
+      | Origin | https://aphno.badal |
+    Then the HTTP status code should be "200"
+    And the following headers should be set
+      | header                      | value               |
+      | Access-Control-Allow-Origin | https://aphno.badal |
+
+  @issue-8231
+  Scenario: CORS headers should be returned when setting CORS domain sending origin header in the Webdav api
+    When user "Alice" sends PROPFIND request to space "Alice Hansen" with headers using the WebDAV API
+      | header | value               |
+      | Origin | https://aphno.badal |
+    Then the HTTP status code should be "207"
+    And the following headers should be set
+      | header                      | value               |
+      | Access-Control-Allow-Origin | https://aphno.badal |
diff --git a/tests/acceptance/features/apiSharingNg/deletePermissions.feature b/tests/acceptance/features/apiSharingNg/deletePermissions.feature
index 83053a67184..222514e4c43 100644
--- a/tests/acceptance/features/apiSharingNg/deletePermissions.feature
+++ b/tests/acceptance/features/apiSharingNg/deletePermissions.feature
@@ -29,9 +29,7 @@ Feature: Remove access to a drive item
       | File Editor     | file          | textfile.txt  |
       | Viewer          | folder        | FolderToShare |
       | Editor          | folder        | FolderToShare |
-      | Co Owner        | folder        | FolderToShare |
       | Uploader        | folder        | FolderToShare |
-      | Manager         | folder        | FolderToShare |
 
 
   Scenario Outline: user removes access to resource inside of a project space in the user share
@@ -56,9 +54,7 @@ Feature: Remove access to a drive item
       | File Editor     | file          | textfile.txt  |
       | Viewer          | folder        | FolderToShare |
       | Editor          | folder        | FolderToShare |
-      | Co Owner        | folder        | FolderToShare |
       | Uploader        | folder        | FolderToShare |
-      | Manager         | folder        | FolderToShare |
 
 
   Scenario Outline: user removes access to a resource in a group share
@@ -84,9 +80,7 @@ Feature: Remove access to a drive item
       | File Editor     | file          | textfile.txt  |
       | Viewer          | folder        | FolderToShare |
       | Editor          | folder        | FolderToShare |
-      | Co Owner        | folder        | FolderToShare |
       | Uploader        | folder        | FolderToShare |
-      | Manager         | folder        | FolderToShare |
 
 
   Scenario Outline: user removes access to a resource inside of a project space in group share
@@ -114,9 +108,7 @@ Feature: Remove access to a drive item
       | File Editor     | file          | textfile.txt  |
       | Viewer          | folder        | FolderToShare |
       | Editor          | folder        | FolderToShare |
-      | Co Owner        | folder        | FolderToShare |
       | Uploader        | folder        | FolderToShare |
-      | Manager         | folder        | FolderToShare |
 
 
   Scenario Outline: user removes access to a folder in link share
diff --git a/tests/acceptance/features/apiSharingNg/shareInvitations.feature b/tests/acceptance/features/apiSharingNg/shareInvitations.feature
index c670ced7eb1..368e34dd9f2 100644
--- a/tests/acceptance/features/apiSharingNg/shareInvitations.feature
+++ b/tests/acceptance/features/apiSharingNg/shareInvitations.feature
@@ -93,9 +93,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: send share invitation to group with different roles
@@ -187,9 +185,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: send share invitation for a file to user with different permissions
@@ -665,9 +661,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: send share invitation with expiration date to group with different roles
@@ -763,9 +757,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
   @issue-7962
   Scenario Outline: send share invitation to disabled user
@@ -848,9 +840,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: send sharing invitation to a deleted group with different roles
@@ -909,9 +899,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: send share invitation to deleted user
@@ -962,9 +950,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: try to send sharing invitation to multiple groups
@@ -1090,9 +1076,7 @@ Feature: Send a sharing invitations
       | File Editor      | file          | /textfile1.txt |
       | Viewer           | folder        | FolderToShare  |
       | Editor           | folder        | FolderToShare  |
-      | Co Owner         | folder        | FolderToShare  |
       | Uploader         | folder        | FolderToShare  |
-      | Manager          | folder        | FolderToShare  |
 
 
   Scenario Outline: send sharing invitation to non-existing group
diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php
index 1fd4615bab4..23107d7fe8f 100644
--- a/tests/acceptance/features/bootstrap/SpacesContext.php
+++ b/tests/acceptance/features/bootstrap/SpacesContext.php
@@ -544,19 +544,22 @@ class SpacesContext implements Context {
 	/**
 	 * @param string $user
 	 * @param string $query
+	 * @param array $headers
 	 *
 	 * @return ResponseInterface
 	 *
 	 * @throws GuzzleException
 	 * @throws Exception
 	 */
-	public function listAllAvailableSpacesOfUser(string $user, string $query = ''): ResponseInterface {
+	public function listAllAvailableSpacesOfUser(string $user, string $query = '', array $headers = []): ResponseInterface {
 		$response = GraphHelper::getMySpaces(
 			$this->featureContext->getBaseUrl(),
 			$user,
 			$this->featureContext->getPasswordForUser($user),
 			"?" . $query,
-			$this->featureContext->getStepLineRef()
+			$this->featureContext->getStepLineRef(),
+			[],
+			$headers
 		);
 		$this->rememberTheAvailableSpaces($response);
 		return $response;
@@ -578,6 +581,28 @@ class SpacesContext implements Context {
 		$this->featureContext->setResponse($this->listAllAvailableSpacesOfUser($user, $query));
 	}
 
+	/**
+	 * @When /^user "([^"]*)" lists all available spaces with headers using the Graph API$/
+	 *
+	 * @param string $user
+	 * @param TableNode $headersTable
+	 *
+	 * @return void
+	 *
+	 * @throws GuzzleException
+	 * @throws Exception
+	 */
+	public function theUserListsAllHisAvailableSpacesWithHeadersUsingTheGraphApi(string $user, TableNode $headersTable): void {
+		$this->featureContext->verifyTableNodeColumns(
+			$headersTable,
+			['header', 'value']
+		);
+		foreach ($headersTable as $row) {
+			$headers[$row['header']] = $row ['value'];
+		}
+		$this->featureContext->setResponse($this->listAllAvailableSpacesOfUser($user, '', $headers));
+	}
+
 	/**
 	 * The method is used on the administration setting tab, which only the Admin user and the Space admin user have access to
 	 *
@@ -3528,17 +3553,44 @@ class SpacesContext implements Context {
 		);
 	}
 
+	/**
+	 * @When /^user "([^"]*)" sends PROPFIND request to space "([^"]*)" with headers using the WebDAV API$/
+	 *
+	 * @param string $user
+	 * @param string $spaceName
+	 * @param TableNode $headersTable
+	 *
+	 * @return void
+	 *
+	 * @throws JsonException
+	 *
+	 * @throws GuzzleException
+	 */
+	public function userSendsPropfindRequestToSpaceWithHeaders(string $user, string $spaceName, $headersTable): void {
+		$this->featureContext->verifyTableNodeColumns(
+			$headersTable,
+			['header', 'value']
+		);
+		foreach ($headersTable as $row) {
+			$headers[$row['header']] = $row ['value'];
+		}
+		$this->featureContext->setResponse(
+			$this->sendPropfindRequestToSpace($user, $spaceName, '', $headers)
+		);
+	}
+
 	/**
 	 * @param string $user
 	 * @param string $spaceName
 	 * @param string|null $resource
+	 * @param array|null $headers
 	 *
 	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 *
 	 * @throws JsonException
 	 */
-	public function sendPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = ""): ResponseInterface {
+	public function sendPropfindRequestToSpace(string $user, string $spaceName, ?string $resource = "", ?array $headers = []): 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'];
 		return  WebDavHelper::propfind(
@@ -3550,7 +3602,9 @@ class SpacesContext implements Context {
 			$this->featureContext->getStepLineRef(),
 			"0",
 			"files",
-			WebDavHelper::DAV_VERSION_SPACES
+			WebDavHelper::DAV_VERSION_SPACES,
+			"",
+			$headers
 		);
 	}