Repository: owncloud/ocis
Pull request #6888 (2023-07-25 15:21:26)

enhancement: Evaluate policy resource information on single file shares
## Description
The policy environment for single file shares now also includes information about the resource.
As a result, it is now possible to set up and check rules for them.

## Related Issue
- Fixes - private repo issue

## Motivation and Context
be able to obtain information on single file shares from rego.

## How Has This Been Tested?
- see unit tests

## Screenshots (if appropriate):

## Types of changes
- [X] Bug fix (non-breaking change which fixes an issue)
- [X] 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:
- [X] Code changes
- [X] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=6888)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6888&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6888&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6888&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=CODE_SMELL) [9 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=CODE_SMELL)

[![72.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '72.7%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_coverage&view=list) [72.7% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=6888)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6888&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6888&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6888&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=CODE_SMELL) [9 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6888&resolved=false&types=CODE_SMELL)

[![42.9%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '42.9%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_coverage&view=list) [42.9% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6888&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b0634871f6..7befd737c52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,7 @@ The following sections list the changes for unreleased.
    Bumps reva version
 
    https://github.com/owncloud/ocis/pull/6899
+   https://github.com/owncloud/ocis/pull/6919
 
 * Enhancement - Configure max grpc message size: [#6849](https://github.com/owncloud/ocis/pull/6849)
 
diff --git a/changelog/unreleased/bump-reva.md b/changelog/unreleased/bump-reva.md
index eed0c160feb..b55e5829435 100644
--- a/changelog/unreleased/bump-reva.md
+++ b/changelog/unreleased/bump-reva.md
@@ -3,3 +3,4 @@ Enhancement: Bump reva
 bumps reva version
 
 https://github.com/owncloud/ocis/pull/6899
+https://github.com/owncloud/ocis/pull/6919
diff --git a/changelog/unreleased/enhancement-evaluate-policy-file-share-info.md b/changelog/unreleased/enhancement-evaluate-policy-file-share-info.md
new file mode 100644
index 00000000000..5073b293942
--- /dev/null
+++ b/changelog/unreleased/enhancement-evaluate-policy-file-share-info.md
@@ -0,0 +1,6 @@
+Enhancement: Evaluate policy resource information on single file shares
+
+The policy environment for single file shares now also includes information about the resource.
+As a result, it is now possible to set up and check rules for them.
+
+https://github.com/owncloud/ocis/pull/6888
diff --git a/deployments/examples/service_policies/policies/proxy.rego b/deployments/examples/service_policies/policies/proxy.rego
index 076bb280192..7f49842b3ff 100644
--- a/deployments/examples/service_policies/policies/proxy.rego
+++ b/deployments/examples/service_policies/policies/proxy.rego
@@ -6,14 +6,28 @@ import data.utils
 default granted := true
 
 granted = false if {
-    print("PRINT MESSAGE EXAMPLE")
     input.request.method == "PUT"
-    not startswith(input.request.path, "/ocs")
-    not utils.is_extension_allowed(input.request.path)
+    pathPrefixes := [
+        "/dav",
+        "/remote.php/webdav",
+        "/remote.php/dav",
+        "/webdav",
+    ]
+    restricted := pathPrefixes[_]
+    startswith(input.request.path, restricted)
+    not utils.is_extension_allowed(input.resource.name)
 }
 
 granted = false if {
     input.request.method == "POST"
-    startswith(input.request.path, "/remote.php")
+    pathPrefixes := [
+        "/data",
+        "/dav",
+        "/remote.php/webdav",
+        "/remote.php/dav",
+        "/webdav",
+    ]
+    restricted := pathPrefixes[_]
+    startswith(input.request.path, restricted)
     not utils.is_extension_allowed(input.resource.name)
 }
diff --git a/go.mod b/go.mod
index 63f45e551fe..829a9301aa3 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/coreos/go-oidc v2.2.1+incompatible
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/cs3org/go-cs3apis v0.0.0-20230516150832-730ac860c71d
-	github.com/cs3org/reva/v2 v2.15.1-0.20230731061316-db79e9b61738
+	github.com/cs3org/reva/v2 v2.15.1-0.20230731073021-26b2f3432d11
 	github.com/disintegration/imaging v1.6.2
 	github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
 	github.com/egirna/icap-client v0.1.1
diff --git a/go.sum b/go.sum
index 7461298d0c2..9c0a4fcce4e 100644
--- a/go.sum
+++ b/go.sum
@@ -625,8 +625,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo
 github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
 github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc=
 github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA=
-github.com/cs3org/reva/v2 v2.15.1-0.20230731061316-db79e9b61738 h1:EILZCEJMYRla6cktKLpi1c3KwISyoYMGTX0AKCuUTZA=
-github.com/cs3org/reva/v2 v2.15.1-0.20230731061316-db79e9b61738/go.mod h1:4z5EQghS2LhSWZWocH51Dw9VAs16No1zSFvFgQtgS7w=
+github.com/cs3org/reva/v2 v2.15.1-0.20230731073021-26b2f3432d11 h1:44FU3t+cJFBEiheA1Vj8JxLVXt8eWY1J0h4lNsaOgxo=
+github.com/cs3org/reva/v2 v2.15.1-0.20230731073021-26b2f3432d11/go.mod h1:4z5EQghS2LhSWZWocH51Dw9VAs16No1zSFvFgQtgS7w=
 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI=
 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
diff --git a/services/proxy/Makefile b/services/proxy/Makefile
index 44e10f95e46..eec748b4da9 100644
--- a/services/proxy/Makefile
+++ b/services/proxy/Makefile
@@ -24,7 +24,8 @@ docs-generate: config-docs-generate
 include ../../.make/generate.mk
 
 .PHONY: ci-go-generate
-ci-go-generate: # CI runs ci-node-generate automatically before this target
+ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this target
+	$(MOCKERY) --dir ../../protogen/gen/ocis/services/policies/v0 --case underscore --name PoliciesProviderService
 
 .PHONY: ci-node-generate
 ci-node-generate:
diff --git a/services/proxy/mocks/policies_provider_service.go b/services/proxy/mocks/policies_provider_service.go
new file mode 100644
index 00000000000..3ace0f52203
--- /dev/null
+++ b/services/proxy/mocks/policies_provider_service.go
@@ -0,0 +1,66 @@
+// Code generated by mockery v2.22.1. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	client "go-micro.dev/v4/client"
+
+	mock "github.com/stretchr/testify/mock"
+
+	v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/policies/v0"
+)
+
+// PoliciesProviderService is an autogenerated mock type for the PoliciesProviderService type
+type PoliciesProviderService struct {
+	mock.Mock
+}
+
+// Evaluate provides a mock function with given fields: ctx, in, opts
+func (_m *PoliciesProviderService) Evaluate(ctx context.Context, in *v0.EvaluateRequest, opts ...client.CallOption) (*v0.EvaluateResponse, error) {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, in)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 *v0.EvaluateResponse
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *v0.EvaluateRequest, ...client.CallOption) (*v0.EvaluateResponse, error)); ok {
+		return rf(ctx, in, opts...)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *v0.EvaluateRequest, ...client.CallOption) *v0.EvaluateResponse); ok {
+		r0 = rf(ctx, in, opts...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*v0.EvaluateResponse)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *v0.EvaluateRequest, ...client.CallOption) error); ok {
+		r1 = rf(ctx, in, opts...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewPoliciesProviderService interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewPoliciesProviderService creates a new instance of PoliciesProviderService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewPoliciesProviderService(t mockConstructorTestingTNewPoliciesProviderService) *PoliciesProviderService {
+	mock := &PoliciesProviderService{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go
index 9e0497bb1ed..edc3d10bdea 100644
--- a/services/proxy/pkg/command/server.go
+++ b/services/proxy/pkg/command/server.go
@@ -8,14 +8,19 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
-	"github.com/cs3org/reva/v2/pkg/store"
-	"github.com/cs3org/reva/v2/pkg/token/manager/jwt"
 	"github.com/go-chi/chi/v5"
 	chimiddleware "github.com/go-chi/chi/v5/middleware"
 	"github.com/go-chi/render"
 	"github.com/justinas/alice"
 	"github.com/oklog/run"
+	"github.com/urfave/cli/v2"
+	microstore "go-micro.dev/v4/store"
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
+	"go.opentelemetry.io/otel/trace"
+
+	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+	"github.com/cs3org/reva/v2/pkg/store"
+	"github.com/cs3org/reva/v2/pkg/token/manager/jwt"
 	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware"
@@ -24,6 +29,7 @@ import (
 	"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
 	"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
 	"github.com/owncloud/ocis/v2/ocis-pkg/version"
+	policiessvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/policies/v0"
 	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0"
 	"github.com/owncloud/ocis/v2/services/proxy/pkg/autoprovision"
@@ -38,10 +44,6 @@ import (
 	proxyHTTP "github.com/owncloud/ocis/v2/services/proxy/pkg/server/http"
 	"github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend"
 	"github.com/owncloud/ocis/v2/services/proxy/pkg/userroles"
-	"github.com/urfave/cli/v2"
-	microstore "go-micro.dev/v4/store"
-	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
-	"go.opentelemetry.io/otel/trace"
 )
 
 // Server is the entrypoint for the server command.
@@ -273,6 +275,7 @@ func (h *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
 
 func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userInfoCache microstore.Store, traceProvider trace.TracerProvider) alice.Chain {
 	rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", cfg.GrpcClient)
+	policiesProviderClient := policiessvc.NewPoliciesProviderService("com.owncloud.api.policies", cfg.GrpcClient)
 	gatewaySelector, err := pool.GatewaySelector(cfg.Reva.Address, append(cfg.Reva.GetRevaOptions(), pool.WithRegistry(registry.GetRegistry()))...)
 	if err != nil {
 		logger.Fatal().Err(err).Msg("Failed to get gateway selector")
@@ -411,7 +414,12 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config,
 			middleware.Logger(logger),
 			middleware.PolicySelectorConfig(*cfg.PolicySelector),
 		),
-		middleware.Policies(logger, cfg.PoliciesMiddleware.Query, cfg.GrpcClient),
+		middleware.Policies(
+			cfg.PoliciesMiddleware.Query,
+			middleware.Logger(logger),
+			middleware.WithRevaGatewaySelector(gatewaySelector),
+			middleware.PoliciesProviderService(policiesProviderClient),
+		),
 		// finally, trigger home creation when a user logs in
 		middleware.CreateHome(
 			middleware.Logger(logger),
diff --git a/services/proxy/pkg/middleware/options.go b/services/proxy/pkg/middleware/options.go
index cd64b44724d..a86b7874e52 100644
--- a/services/proxy/pkg/middleware/options.go
+++ b/services/proxy/pkg/middleware/options.go
@@ -5,16 +5,19 @@ import (
 	"time"
 
 	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	"go-micro.dev/v4/store"
+	"go.opentelemetry.io/otel/trace"
+
 	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/ocis-pkg/oidc"
+	policiessvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/policies/v0"
 	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0"
 	"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
 	"github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend"
 	"github.com/owncloud/ocis/v2/services/proxy/pkg/userroles"
-	store "go-micro.dev/v4/store"
-	"go.opentelemetry.io/otel/trace"
 )
 
 // Option defines a single option function.
@@ -36,6 +39,8 @@ type Options struct {
 	UserRoleAssigner userroles.UserRoleAssigner
 	// SettingsRoleService for the roles API in settings
 	SettingsRoleService settingssvc.RoleService
+	// PoliciesProviderService for policy evaluation
+	PoliciesProviderService policiessvc.PoliciesProviderService
 	// OIDCProviderFunc to lazily initialize an oidc provider, must be set for the oidc_auth middleware
 	OIDCClient oidc.OIDCClient
 	// OIDCIss is the oidcAuth-issuer
@@ -118,7 +123,14 @@ func SettingsRoleService(rc settingssvc.RoleService) Option {
 	}
 }
 
-// OIDCClient provides a function to set the the oidc client option.
+// PoliciesProviderService provides a function to set the policies provider option.
+func PoliciesProviderService(pps policiessvc.PoliciesProviderService) Option {
+	return func(o *Options) {
+		o.PoliciesProviderService = pps
+	}
+}
+
+// OIDCClient provides a function to set the oidc client option.
 func OIDCClient(val oidc.OIDCClient) Option {
 	return func(o *Options) {
 		o.OIDCClient = val
@@ -139,7 +151,7 @@ func CredentialsByUserAgent(v map[string]string) Option {
 	}
 }
 
-// WithRevaGatewaySelector provides a function to set the the reva gateway service selector option.
+// WithRevaGatewaySelector provides a function to set the reva gateway service selector option.
 func WithRevaGatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option {
 	return func(o *Options) {
 		o.RevaGatewaySelector = val
diff --git a/services/proxy/pkg/middleware/policies.go b/services/proxy/pkg/middleware/policies.go
index 604c17dcaf2..590eb96f64f 100644
--- a/services/proxy/pkg/middleware/policies.go
+++ b/services/proxy/pkg/middleware/policies.go
@@ -3,17 +3,23 @@ package middleware
 import (
 	"net/http"
 	"path"
+	"path/filepath"
+	"strings"
 	"time"
 
-	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	"github.com/go-chi/chi/v5/middleware"
 	"github.com/go-chi/render"
-	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+	tusd "github.com/tus/tusd/pkg/handler"
+	"google.golang.org/grpc/metadata"
+
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/storagespace"
+	"github.com/cs3org/reva/v2/pkg/utils"
+
 	pMessage "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/policies/v0"
 	pService "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/policies/v0"
 	"github.com/owncloud/ocis/v2/services/webdav/pkg/net"
-	tusd "github.com/tus/tusd/pkg/handler"
-	"go-micro.dev/v4/client"
 )
 
 type (
@@ -34,8 +40,11 @@ type (
 const DeniedMessage = "Operation denied due to security policies"
 
 // Policies verifies if a request is granted or not.
-func Policies(logger log.Logger, qs string, grpcClient client.Client) func(next http.Handler) http.Handler {
-	pClient := pService.NewPoliciesProviderService("com.owncloud.api.policies", grpcClient)
+func Policies(qs string, opts ...Option) func(next http.Handler) http.Handler {
+	options := newOptions(opts...)
+	logger := options.Logger
+	gatewaySelector := options.RevaGatewaySelector
+	policiesProviderClient := options.PoliciesProviderService
 
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -55,11 +64,53 @@ func Policies(logger log.Logger, qs string, grpcClient client.Client) func(next
 				},
 			}
 
+			resource := &pMessage.Resource{}
+
+			// tus
 			meta := tusd.ParseMetadataHeader(r.Header.Get(net.HeaderUploadMetadata))
-			req.Environment.Resource = &pMessage.Resource{
-				Name: meta["filename"],
+			resource.Name = meta["filename"]
+
+			// name is part of the request path
+			if resource.Name == "" && filepath.Ext(r.URL.Path) != "" {
+				resource.Name = filepath.Base(r.URL.Path)
 			}
 
+			// no resource info in path, stat the resource and try to obtain the file information.
+			// this should only be used as last bastion, every request goes through the proxy and doing stats is expensive!
+			// needed for:
+			// - if a single resource is shared -> the url only contains the resourceID (spaceRef)
+			if resource.Name == "" && filepath.Ext(r.URL.Path) == "" && r.Method == http.MethodPut && strings.HasPrefix(r.URL.Path, "/remote.php/dav/spaces") {
+				client, err := gatewaySelector.Next()
+				if err != nil {
+					logger.Err(err).Msg("error selecting next gateway client")
+					RenderError(w, r, req, http.StatusForbidden, DeniedMessage)
+					return
+				}
+
+				resourceID, err := storagespace.ParseID(strings.TrimPrefix(r.URL.Path, "/remote.php/dav/spaces/"))
+				if err != nil {
+					logger.Debug().Err(err).Msg("error parsing the resourceId")
+					RenderError(w, r, req, http.StatusForbidden, DeniedMessage)
+					return
+				}
+
+				if resourceID.StorageId == "" && resourceID.SpaceId == utils.ShareStorageSpaceID {
+					resourceID.StorageId = utils.ShareStorageProviderID
+				}
+
+				token := r.Header.Get(revactx.TokenHeader)
+				ctx := metadata.AppendToOutgoingContext(r.Context(), revactx.TokenHeader, token)
+				sRes, err := client.Stat(ctx, &provider.StatRequest{
+					Ref: &provider.Reference{
+						ResourceId: &resourceID,
+					},
+				})
+
+				resource.Name = sRes.GetInfo().GetName()
+			}
+
+			req.Environment.Resource = resource
+
 			if user, ok := revactx.ContextGetUser(r.Context()); ok {
 				req.Environment.User = &pMessage.User{
 					Id: &pMessage.User_ID{
@@ -72,7 +123,7 @@ func Policies(logger log.Logger, qs string, grpcClient client.Client) func(next
 				}
 			}
 
-			rsp, err := pClient.Evaluate(r.Context(), req)
+			rsp, err := policiesProviderClient.Evaluate(r.Context(), req)
 			if err != nil {
 				logger.Err(err).Msg("error evaluating request")
 				w.WriteHeader(http.StatusInternalServerError)
diff --git a/services/storage-users/pkg/config/config.go b/services/storage-users/pkg/config/config.go
index 7c738792bfc..a2be82ea3f1 100644
--- a/services/storage-users/pkg/config/config.go
+++ b/services/storage-users/pkg/config/config.go
@@ -91,9 +91,16 @@ type Drivers struct {
 	Local LocalDriver `yaml:",omitempty"` // not supported by the oCIS product, therefore not part of docs
 }
 
+// AsyncPropagatorOptions configures the async propagator
+type AsyncPropagatorOptions struct {
+	PropagationDelay time.Duration `yaml:"propagation_delay" env:"STORAGE_USERS_ASYNC_PROPAGATOR_PROPAGATION_DELAY" desc:"The delay in seconds between a change made to a tree and the propagation start on treesize and treetime. Multiple propagations are computed to a single one."`
+}
+
 // OCISDriver is the storage driver configuration when using 'ocis' storage driver
 type OCISDriver struct {
-	MetadataBackend string `yaml:"metadata_backend" env:"OCIS_DECOMPOSEDFS_METADATA_BACKEND;STORAGE_USERS_OCIS_METADATA_BACKEND" desc:"The backend to use for storing metadata. Supported values are 'messagepack' and 'xattrs'. The setting 'messagepack' uses a dedicated file to store file metadata while 'xattrs' uses extended attributes to store file metadata. Defaults to 'messagepack'."`
+	MetadataBackend        string                 `yaml:"metadata_backend" env:"OCIS_DECOMPOSEDFS_METADATA_BACKEND;STORAGE_USERS_OCIS_METADATA_BACKEND" desc:"The backend to use for storing metadata. Supported values are 'messagepack' and 'xattrs'. The setting 'messagepack' uses a dedicated file to store file metadata while 'xattrs' uses extended attributes to store file metadata. Defaults to 'messagepack'."`
+	Propagator             string                 `yaml:"propagator" env:"OCIS_DECOMPOSEDFS_PROPAGATOR;STORAGE_USERS_OCIS_PROPAGATOR" desc:"The propagator used for decomposedfs. At the moment, only 'sync' is fully supported, 'async' is available as an experimental option."`
+	AsyncPropagatorOptions AsyncPropagatorOptions `yaml:"async_propagator_options"`
 	// Root is the absolute path to the location of the data
 	Root                string `yaml:"root" env:"STORAGE_USERS_OCIS_ROOT" desc:"The directory where the filesystem storage will store blobs and metadata. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage/users."`
 	UserLayout          string `yaml:"user_layout" env:"STORAGE_USERS_OCIS_USER_LAYOUT" desc:"Template string for the user storage layout in the user directory."`
@@ -115,7 +122,9 @@ type OCISDriver struct {
 
 // S3NGDriver is the storage driver configuration when using 's3ng' storage driver
 type S3NGDriver struct {
-	MetadataBackend string `yaml:"metadata_backend" env:"STORAGE_USERS_S3NG_METADATA_BACKEND" desc:"The backend to use for storing metadata. Supported values are 'xattrs' and 'messagepack'. The setting 'xattrs' uses extended attributes to store file metadata while 'messagepack' uses a dedicated file to store file metadata. Defaults to 'xattrs'."`
+	MetadataBackend        string                 `yaml:"metadata_backend" env:"STORAGE_USERS_S3NG_METADATA_BACKEND" desc:"The backend to use for storing metadata. Supported values are 'xattrs' and 'messagepack'. The setting 'xattrs' uses extended attributes to store file metadata while 'messagepack' uses a dedicated file to store file metadata. Defaults to 'xattrs'."`
+	Propagator             string                 `yaml:"propagator" env:"OCIS_DECOMPOSEDFS_PROPAGATOR;STORAGE_USERS_S3NG_PROPAGATOR" desc:"The propagator used for decomposedfs. At the moment, only 'sync' is fully supported, 'async' is available as an experimental option."`
+	AsyncPropagatorOptions AsyncPropagatorOptions `yaml:"async_propagator_options"`
 	// Root is the absolute path to the location of the data
 	Root                string `yaml:"root" env:"STORAGE_USERS_S3NG_ROOT" desc:"The directory where the filesystem storage will store metadata for blobs. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage/users."`
 	UserLayout          string `yaml:"user_layout" env:"STORAGE_USERS_S3NG_USER_LAYOUT" desc:"Template string for the user storage layout in the user directory."`
diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go
index 4674a3b1e8a..9cb71e4f26e 100644
--- a/services/storage-users/pkg/config/defaults/defaultconfig.go
+++ b/services/storage-users/pkg/config/defaults/defaultconfig.go
@@ -63,6 +63,7 @@ func DefaultConfig() *config.Config {
 			},
 			S3NG: config.S3NGDriver{
 				MetadataBackend:            "messagepack",
+				Propagator:                 "sync",
 				Root:                       filepath.Join(defaults.BaseDataPath(), "storage", "users"),
 				ShareFolder:                "/Shares",
 				UserLayout:                 "{{.Id.OpaqueId}}",
@@ -75,6 +76,7 @@ func DefaultConfig() *config.Config {
 			},
 			OCIS: config.OCISDriver{
 				MetadataBackend:            "messagepack",
+				Propagator:                 "sync",
 				Root:                       filepath.Join(defaults.BaseDataPath(), "storage", "users"),
 				ShareFolder:                "/Shares",
 				UserLayout:                 "{{.Id.OpaqueId}}",
diff --git a/services/storage-users/pkg/revaconfig/drivers.go b/services/storage-users/pkg/revaconfig/drivers.go
index 7fb10475788..097f16250f9 100644
--- a/services/storage-users/pkg/revaconfig/drivers.go
+++ b/services/storage-users/pkg/revaconfig/drivers.go
@@ -115,7 +115,11 @@ func OwnCloudSQL(cfg *config.Config) map[string]interface{} {
 // Ocis is the config mapping for the Ocis storage driver
 func Ocis(cfg *config.Config) map[string]interface{} {
 	return map[string]interface{}{
-		"metadata_backend":            cfg.Drivers.OCIS.MetadataBackend,
+		"metadata_backend": cfg.Drivers.OCIS.MetadataBackend,
+		"propagator":       cfg.Drivers.OCIS.Propagator,
+		"async_propagator_options": map[string]interface{}{
+			"propagation_delay": cfg.Drivers.OCIS.AsyncPropagatorOptions.PropagationDelay,
+		},
 		"root":                        cfg.Drivers.OCIS.Root,
 		"user_layout":                 cfg.Drivers.OCIS.UserLayout,
 		"share_folder":                cfg.Drivers.OCIS.ShareFolder,
@@ -170,7 +174,11 @@ func Ocis(cfg *config.Config) map[string]interface{} {
 // OcisNoEvents is the config mapping for the ocis storage driver emitting no events
 func OcisNoEvents(cfg *config.Config) map[string]interface{} {
 	return map[string]interface{}{
-		"metadata_backend":            cfg.Drivers.OCIS.MetadataBackend,
+		"metadata_backend": cfg.Drivers.OCIS.MetadataBackend,
+		"propagator":       cfg.Drivers.OCIS.Propagator,
+		"async_propagator_options": map[string]interface{}{
+			"propagation_delay": cfg.Drivers.OCIS.AsyncPropagatorOptions.PropagationDelay,
+		},
 		"root":                        cfg.Drivers.OCIS.Root,
 		"user_layout":                 cfg.Drivers.OCIS.UserLayout,
 		"share_folder":                cfg.Drivers.OCIS.ShareFolder,
@@ -224,7 +232,11 @@ func S3(cfg *config.Config) map[string]interface{} {
 // S3NG is the config mapping for the s3ng storage driver
 func S3NG(cfg *config.Config) map[string]interface{} {
 	return map[string]interface{}{
-		"metadata_backend":            cfg.Drivers.S3NG.MetadataBackend,
+		"metadata_backend": cfg.Drivers.S3NG.MetadataBackend,
+		"propagator":       cfg.Drivers.S3NG.Propagator,
+		"async_propagator_options": map[string]interface{}{
+			"propagation_delay": cfg.Drivers.S3NG.AsyncPropagatorOptions.PropagationDelay,
+		},
 		"root":                        cfg.Drivers.S3NG.Root,
 		"user_layout":                 cfg.Drivers.S3NG.UserLayout,
 		"share_folder":                cfg.Drivers.S3NG.ShareFolder,
@@ -283,7 +295,11 @@ func S3NG(cfg *config.Config) map[string]interface{} {
 // S3NGNoEvents is the config mapping for the s3ng storage driver emitting no events
 func S3NGNoEvents(cfg *config.Config) map[string]interface{} {
 	return map[string]interface{}{
-		"metadata_backend":            cfg.Drivers.S3NG.MetadataBackend,
+		"metadata_backend": cfg.Drivers.S3NG.MetadataBackend,
+		"propagator":       cfg.Drivers.S3NG.Propagator,
+		"async_propagator_options": map[string]interface{}{
+			"propagation_delay": cfg.Drivers.S3NG.AsyncPropagatorOptions.PropagationDelay,
+		},
 		"root":                        cfg.Drivers.S3NG.Root,
 		"user_layout":                 cfg.Drivers.S3NG.UserLayout,
 		"share_folder":                cfg.Drivers.S3NG.ShareFolder,
diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go
index 574cc6f66b9..3e398ff50a8 100644
--- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go
+++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go
@@ -161,9 +161,9 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request,
 	}
 
 	if !sufficientPermissions(statInfo.PermissionSet, permissions, true) {
-		response.WriteOCSError(w, r, http.StatusNotFound, "no share permission", nil)
+		response.WriteOCSError(w, r, http.StatusForbidden, "no share permission", nil)
 		return nil, &ocsError{
-			Code:    http.StatusNotFound,
+			Code:    http.StatusForbidden,
 			Message: "Cannot set the requested share permissions",
 			Error:   errors.New("cannot set the requested share permissions"),
 		}
@@ -417,7 +417,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar
 
 	// empty permissions mean internal link here - NOT denial. Hence we need an extra check
 	if !sufficientPermissions(statRes.Info.PermissionSet, newPermissions, true) {
-		response.WriteOCSError(w, r, http.StatusNotFound, "no share permission", nil)
+		response.WriteOCSError(w, r, http.StatusForbidden, "no share permission", nil)
 		return
 	}
 
diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go
index b272c569c1c..afe888047bf 100644
--- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go
+++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go
@@ -232,7 +232,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) {
 		case rpc.Code_CODE_NOT_FOUND:
 			response.WriteOCSData(w, r, response.MetaPathNotFound, nil, nil)
 		case rpc.Code_CODE_PERMISSION_DENIED:
-			response.WriteOCSError(w, r, http.StatusNotFound, "No share permission", nil)
+			response.WriteOCSError(w, r, http.StatusForbidden, "No share permission", nil)
 		default:
 			sublog.Error().Interface("status", statRes.Status).Msg("CreateShare: stat failed")
 			w.WriteHeader(http.StatusInternalServerError)
@@ -250,7 +250,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) {
 
 	// check user has share permissions
 	if !conversions.RoleFromResourcePermissions(statRes.Info.PermissionSet, false).OCSPermissions().Contain(conversions.PermissionShare) {
-		response.WriteOCSError(w, r, http.StatusNotFound, "No share permission", nil)
+		response.WriteOCSError(w, r, http.StatusForbidden, "No share permission", nil)
 		return
 	}
 
@@ -298,7 +298,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) {
 		// public links default to read only
 		_, _, ocsErr := h.extractPermissions(reqRole, reqPermissions, statRes.Info, conversions.NewViewerRole(h.resharing))
 		if ocsErr != nil && ocsErr.Error != conversions.ErrZeroPermission {
-			response.WriteOCSError(w, r, http.StatusNotFound, "No share permission", nil)
+			response.WriteOCSError(w, r, http.StatusForbidden, "No share permission", nil)
 			return
 		}
 		share, ocsErr := h.createPublicLinkShare(w, r, statRes.Info)
@@ -459,7 +459,7 @@ func (h *Handler) extractPermissions(reqRole string, reqPermissions string, ri *
 
 	if !sufficientPermissions(ri.PermissionSet, role.CS3ResourcePermissions(), false) && role.Name != conversions.RoleDenied {
 		return nil, nil, &ocsError{
-			Code:    http.StatusNotFound,
+			Code:    http.StatusForbidden,
 			Message: "Cannot set the requested share permissions",
 			Error:   errors.New("cannot set the requested share permissions"),
 		}
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go
index 273e094f9d4..a3377083e4f 100644
--- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go
+++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup/lookup.go
@@ -44,6 +44,20 @@ func init() {
 	tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/lookup")
 }
 
+// PathLookup defines the interface for the lookup component
+type PathLookup interface {
+	NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error)
+	NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error)
+
+	InternalRoot() string
+	InternalPath(spaceID, nodeID string) string
+	Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (path string, err error)
+	MetadataBackend() metadata.Backend
+	ReadBlobSizeAttr(ctx context.Context, path string) (int64, error)
+	ReadBlobIDAttr(ctx context.Context, path string) (string, error)
+	TypeFromPath(ctx context.Context, path string) provider.ResourceType
+}
+
 // Lookup implements transformations from filepath to node and back
 type Lookup struct {
 	Options *options.Options
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options/options.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options/options.go
index f60c912a829..6e081fdcea4 100644
--- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options/options.go
+++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options/options.go
@@ -21,6 +21,7 @@ package options
 import (
 	"path/filepath"
 	"strings"
+	"time"
 
 	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
 	"github.com/cs3org/reva/v2/pkg/sharedconf"
@@ -41,6 +42,11 @@ type Options struct {
 	// the metadata backend to use, currently supports `xattr` or `ini`
 	MetadataBackend string `mapstructure:"metadata_backend"`
 
+	// the propagator to use for this fs. currently only `sync` is fully supported, `async` is available as an experimental feature
+	Propagator string `mapstructure:"propagator"`
+	// Options specific to the async propagator
+	AsyncPropagatorOptions AsyncPropagatorOptions `mapstructure:"async_propagator_options"`
+
 	// ocis fs works on top of a dir of uuid nodes
 	Root string `mapstructure:"root"`
 
@@ -78,6 +84,11 @@ type Options struct {
 	MaxQuota uint64 `mapstructure:"max_quota"`
 }
 
+// AsyncPropagatorOptions holds the configuration for the async propagator
+type AsyncPropagatorOptions struct {
+	PropagationDelay time.Duration `mapstructure:"propagation_delay"`
+}
+
 // EventOptions are the configurable options for events
 type EventOptions struct {
 	NatsAddress          string `mapstructure:"natsaddress"`
@@ -145,5 +156,12 @@ func New(m map[string]interface{}) (*Options, error) {
 		o.MaxConcurrency = 100
 	}
 
+	if o.Propagator == "" {
+		o.Propagator = "sync"
+	}
+	if o.AsyncPropagatorOptions.PropagationDelay == 0 {
+		o.AsyncPropagatorOptions.PropagationDelay = 5 * time.Second
+	}
+
 	return o, nil
 }
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go
new file mode 100644
index 00000000000..3bd6864d991
--- /dev/null
+++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/async.go
@@ -0,0 +1,440 @@
+// Copyright 2018-2021 CERN
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// In applying this license, CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+package propagator
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/cs3org/reva/v2/pkg/appctx"
+	"github.com/cs3org/reva/v2/pkg/logger"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
+	"github.com/google/renameio/v2"
+	"github.com/google/uuid"
+	"github.com/pkg/errors"
+	"github.com/rogpeppe/go-internal/lockedfile"
+	"github.com/rs/zerolog"
+	"github.com/shamaton/msgpack/v2"
+)
+
+var _propagationGracePeriod = 3 * time.Minute
+
+// AsyncPropagator implements asynchronous treetime & treesize propagation
+type AsyncPropagator struct {
+	treeSizeAccounting bool
+	treeTimeAccounting bool
+	propagationDelay   time.Duration
+	lookup             lookup.PathLookup
+}
+
+// Change represents a change to the tree
+type Change struct {
+	SyncTime time.Time
+	SizeDiff int64
+}
+
+// NewAsyncPropagator returns a new AsyncPropagator instance
+func NewAsyncPropagator(treeSizeAccounting, treeTimeAccounting bool, o options.AsyncPropagatorOptions, lookup lookup.PathLookup) AsyncPropagator {
+	p := AsyncPropagator{
+		treeSizeAccounting: treeSizeAccounting,
+		treeTimeAccounting: treeTimeAccounting,
+		propagationDelay:   o.PropagationDelay,
+		lookup:             lookup,
+	}
+
+	log := logger.New()
+
+	log.Info().Msg("async propagator starting up...")
+
+	// spawn a goroutine that watches for stale .processing dirs and fixes them
+	go func() {
+		if !p.treeTimeAccounting && !p.treeSizeAccounting {
+			// no propagation enabled
+			log.Debug().Msg("propagation disabled or nothing to propagate")
+			return
+		}
+
+		changesDirPath := filepath.Join(p.lookup.InternalRoot(), "changes")
+		doSleep := false // switch to not sleep on the first iteration
+		for {
+			if doSleep {
+				time.Sleep(5 * time.Minute)
+			}
+			doSleep = true
+			log.Debug().Msg("scanning for stale .processing dirs")
+
+			entries, err := filepath.Glob(changesDirPath + "/**/*")
+			if err != nil {
+				log.Error().Err(err).Msg("failed to list changes")
+				continue
+			}
+
+			for _, e := range entries {
+				changesDirPath := e
+				entry, err := os.Stat(changesDirPath)
+				if err != nil {
+					continue
+				}
+				// recover all dirs that seem to have been stuck
+				if !entry.IsDir() || time.Now().Before(entry.ModTime().Add(_propagationGracePeriod)) {
+					continue
+				}
+
+				go func() {
+					if !strings.HasSuffix(changesDirPath, ".processing") {
+						// first rename the existing node dir
+						err = os.Rename(changesDirPath, changesDirPath+".processing")
+						if err != nil {
+							return
+						}
+						changesDirPath += ".processing"
+					}
+
+					log.Debug().Str("dir", changesDirPath).Msg("propagating stale .processing dir")
+					parts := strings.SplitN(entry.Name(), ":", 2)
+					if len(parts) != 2 {
+						log.Error().Str("file", entry.Name()).Msg("encountered invalid .processing dir")
+						return
+					}
+
+					now := time.Now()
+					_ = os.Chtimes(changesDirPath, now, now)
+					p.propagate(context.Background(), parts[0], strings.TrimSuffix(parts[1], ".processing"), true, *log)
+				}()
+			}
+		}
+	}()
+
+	return p
+}
+
+// Propagate triggers a propagation
+func (p AsyncPropagator) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) error {
+	ctx, span := tracer.Start(ctx, "Propagate")
+	defer span.End()
+	log := appctx.GetLogger(ctx).With().
+		Str("method", "async.Propagate").
+		Str("spaceid", n.SpaceID).
+		Str("nodeid", n.ID).
+		Str("parentid", n.ParentID).
+		Int64("sizeDiff", sizeDiff).
+		Logger()
+
+	if !p.treeTimeAccounting && (!p.treeSizeAccounting || sizeDiff == 0) {
+		// no propagation enabled
+		log.Debug().Msg("propagation disabled or nothing to propagate")
+		return nil
+	}
+
+	// add a change to the parent node
+	c := Change{
+		// use a sync time and don't rely on the mtime of the current node, as the stat might not change when a rename happened too quickly
+		SyncTime: time.Now().UTC(),
+		SizeDiff: sizeDiff,
+	}
+	go p.queuePropagation(ctx, n.SpaceID, n.ParentID, c, log)
+
+	return nil
+}
+
+func (p AsyncPropagator) queuePropagation(ctx context.Context, spaceID, nodeID string, change Change, log zerolog.Logger) {
+	// add a change to the parent node
+	changePath := p.changesPath(spaceID, nodeID, uuid.New().String()+".mpk")
+
+	data, err := msgpack.Marshal(change)
+	if err != nil {
+		log.Error().Err(err).Msg("failed to marshal Change")
+		return
+	}
+
+	_, subspan := tracer.Start(ctx, "write changes file")
+	ready := false
+	triggerPropagation := false
+	_ = os.MkdirAll(filepath.Dir(filepath.Dir(changePath)), 0700)
+	err = os.Mkdir(filepath.Dir(changePath), 0700)
+	triggerPropagation = err == nil || os.IsExist(err) // only the first goroutine, which succeeds to create the directory, is supposed to actually trigger the propagation
+	for retries := 0; retries <= 500; retries++ {
+		err := renameio.WriteFile(changePath, data, 0644)
+		if err == nil {
+			ready = true
+			break
+		}
+		log.Error().Err(err).Msg("failed to write Change to disk (retrying)")
+		err = os.Mkdir(filepath.Dir(changePath), 0700)
+		triggerPropagation = err == nil || os.IsExist(err) // only the first goroutine, which succeeds to create the directory, is supposed to actually trigger the propagation
+	}
+
+	if !ready {
+		log.Error().Err(err).Msg("failed to write Change to disk")
+		return
+	}
+	subspan.End()
+
+	if !triggerPropagation {
+		return
+	}
+
+	_, subspan = tracer.Start(ctx, "delay propagation")
+	time.Sleep(p.propagationDelay) // wait a moment before propagating
+	subspan.End()
+
+	log.Debug().Msg("propagating")
+	// add a change to the parent node
+	changeDirPath := p.changesPath(spaceID, nodeID, "")
+
+	// first rename the existing node dir
+	err = os.Rename(changeDirPath, changeDirPath+".processing")
+	if err != nil {
+		// This can fail in 2 ways
+		// 1. source does not exist anymore as it has already been propagated by another goroutine
+		//    -> ignore, as the change is already being processed
+		// 2. target already exists because a previous propagation is still running
+		//    -> ignore, the previous propagation will pick the new changes up
+		return
+	}
+	p.propagate(ctx, spaceID, nodeID, false, log)
+}
+
+func (p AsyncPropagator) propagate(ctx context.Context, spaceID, nodeID string, recalculateTreeSize bool, log zerolog.Logger) {
+	changeDirPath := p.changesPath(spaceID, nodeID, "")
+	processingPath := changeDirPath + ".processing"
+
+	cleanup := func() {
+		err := os.RemoveAll(processingPath)
+		if err != nil {
+			log.Error().Err(err).Msg("Could not remove .processing dir")
+		}
+	}
+
+	_, subspan := tracer.Start(ctx, "list changes files")
+	d, err := os.Open(processingPath)
+	if err != nil {
+		log.Error().Err(err).Msg("Could not open change .processing dir")
+		cleanup()
+		return
+	}
+	defer d.Close()
+	names, err := d.Readdirnames(0)
+	if err != nil {
+		log.Error().Err(err).Msg("Could not read dirnames")
+		cleanup()
+		return
+	}
+	subspan.End()
+
+	_, subspan = tracer.Start(ctx, "read changes files")
+	pc := Change{}
+	for _, name := range names {
+		if !strings.HasSuffix(name, ".mpk") {
+			continue
+		}
+
+		b, err := os.ReadFile(filepath.Join(processingPath, name))
+		if err != nil {
+			log.Error().Err(err).Msg("Could not read change")
+			cleanup()
+			return
+		}
+		c := Change{}
+		err = msgpack.Unmarshal(b, &c)
+		if err != nil {
+			log.Error().Err(err).Msg("Could not unmarshal change")
+			cleanup()
+			return
+		}
+		if c.SyncTime.After(pc.SyncTime) {
+			pc.SyncTime = c.SyncTime
+		}
+		pc.SizeDiff += c.SizeDiff
+	}
+	subspan.End()
+
+	// TODO do we need to write an aggregated parentchange file?
+
+	attrs := node.Attributes{}
+
+	var f *lockedfile.File
+	// lock parent before reading treesize or tree time
+	nodePath := filepath.Join(p.lookup.InternalRoot(), "spaces", lookup.Pathify(spaceID, 1, 2), "nodes", lookup.Pathify(nodeID, 4, 2))
+
+	_, subspan = tracer.Start(ctx, "lockedfile.OpenFile")
+	lockFilepath := p.lookup.MetadataBackend().LockfilePath(nodePath)
+	f, err = lockedfile.OpenFile(lockFilepath, os.O_RDWR|os.O_CREATE, 0600)
+	subspan.End()
+	if err != nil {
+		log.Error().Err(err).
+			Str("lock filepath", lockFilepath).
+			Msg("Propagation failed. Could not open metadata for node with lock.")
+		cleanup()
+		return
+	}
+	// always log error if closing node fails
+	defer func() {
+		// ignore already closed error
+		cerr := f.Close()
+		if err == nil && cerr != nil && !errors.Is(cerr, os.ErrClosed) {
+			err = cerr // only overwrite err with en error from close if the former was nil
+		}
+	}()
+
+	_, subspan = tracer.Start(ctx, "node.ReadNode")
+	var n *node.Node
+	if n, err = node.ReadNode(ctx, p.lookup, spaceID, nodeID, false, nil, false); err != nil {
+		log.Error().Err(err).
+			Msg("Propagation failed. Could not read node.")
+		cleanup()
+		return
+	}
+	subspan.End()
+
+	if !n.Exists {
+		log.Debug().Str("attr", prefixes.PropagationAttr).Msg("node does not exist anymore, not propagating")
+		cleanup()
+		return
+	}
+
+	if !n.HasPropagation(ctx) {
+		log.Debug().Str("attr", prefixes.PropagationAttr).Msg("propagation attribute not set or unreadable, not propagating")
+		cleanup()
+		return
+	}
+
+	if p.treeTimeAccounting {
+		// update the parent tree time if it is older than the nodes mtime
+		updateSyncTime := false
+
+		var tmTime time.Time
+		tmTime, err = n.GetTMTime(ctx)
+		switch {
+		case err != nil:
+			// missing attribute, or invalid format, overwrite
+			log.Debug().Err(err).
+				Msg("could not read tmtime attribute, overwriting")
+			updateSyncTime = true
+		case tmTime.Before(pc.SyncTime):
+			log.Debug().
+				Time("tmtime", tmTime).
+				Time("stime", pc.SyncTime).
+				Msg("parent tmtime is older than node mtime, updating")
+			updateSyncTime = true
+		default:
+			log.Debug().
+				Time("tmtime", tmTime).
+				Time("stime", pc.SyncTime).
+				Dur("delta", pc.SyncTime.Sub(tmTime)).
+				Msg("node tmtime is younger than stime, not updating")
+		}
+
+		if updateSyncTime {
+			// update the tree time of the parent node
+			attrs.SetString(prefixes.TreeMTimeAttr, pc.SyncTime.UTC().Format(time.RFC3339Nano))
+		}
+
+		attrs.SetString(prefixes.TmpEtagAttr, "")
+	}
+
+	// size accounting
+	if p.treeSizeAccounting && pc.SizeDiff != 0 {
+		var newSize uint64
+
+		// read treesize
+		treeSize, err := n.GetTreeSize(ctx)
+		switch {
+		case recalculateTreeSize || metadata.IsAttrUnset(err):
+			// fallback to calculating the treesize
+			log.Warn().Msg("treesize attribute unset, falling back to calculating the treesize")
+			newSize, err = calculateTreeSize(ctx, p.lookup, n.InternalPath())
+			if err != nil {
+				log.Error().Err(err).
+					Msg("Error when calculating treesize of node.") // FIXME wat?
+				cleanup()
+				return
+			}
+		case err != nil:
+			log.Error().Err(err).
+				Msg("Failed to propagate treesize change. Error when reading the treesize attribute from node")
+			cleanup()
+			return
+		case pc.SizeDiff > 0:
+			newSize = treeSize + uint64(pc.SizeDiff)
+		case uint64(-pc.SizeDiff) > treeSize:
+			// The sizeDiff is larger than the current treesize. Which would result in
+			// a negative new treesize. Something must have gone wrong with the accounting.
+			// Reset the current treesize to 0.
+			log.Error().Uint64("treeSize", treeSize).Int64("sizeDiff", pc.SizeDiff).
+				Msg("Error when updating treesize of node. Updated treesize < 0. Reestting to 0")
+			newSize = 0
+		default:
+			newSize = treeSize - uint64(-pc.SizeDiff)
+		}
+
+		// update the tree size of the node
+		attrs.SetString(prefixes.TreesizeAttr, strconv.FormatUint(newSize, 10))
+		log.Debug().Uint64("newSize", newSize).Msg("updated treesize of node")
+	}
+
+	if err = n.SetXattrsWithContext(ctx, attrs, false); err != nil {
+		log.Error().Err(err).Msg("Failed to update extend attributes of node")
+		cleanup()
+		return
+	}
+
+	// Release node lock early, ignore already closed error
+	_, subspan = tracer.Start(ctx, "f.Close")
+	cerr := f.Close()
+	subspan.End()
+	if cerr != nil && !errors.Is(cerr, os.ErrClosed) {
+		log.Error().Err(cerr).Msg("Failed to close node and release lock")
+	}
+
+	log.Info().Msg("Propagation done. cleaning up")
+	cleanup()
+
+	if !n.IsSpaceRoot(ctx) { // This does not seem robust as it checks the space name property
+		p.queuePropagation(ctx, n.SpaceID, n.ParentID, pc, log)
+	}
+
+	// Check for a changes dir that might have been added meanwhile and pick it up
+	if _, err = os.Open(changeDirPath); err == nil {
+		log.Info().Msg("Found a new changes dir. starting next propagation")
+		time.Sleep(p.propagationDelay) // wait a moment before propagating
+		err = os.Rename(changeDirPath, processingPath)
+		if err != nil {
+			// This can fail in 2 ways
+			// 1. source does not exist anymore as it has already been propagated by another goroutine
+			//    -> ignore, as the change is already being processed
+			// 2. target already exists because a previous propagation is still running
+			//    -> ignore, the previous propagation will pick the new changes up
+			return
+		}
+		p.propagate(ctx, spaceID, nodeID, false, log)
+	}
+}
+
+func (p AsyncPropagator) changesPath(spaceID, nodeID, filename string) string {
+	return filepath.Join(p.lookup.InternalRoot(), "changes", spaceID[0:2], spaceID+":"+nodeID, filename)
+}
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go
new file mode 100644
index 00000000000..6f12e43c616
--- /dev/null
+++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/propagator.go
@@ -0,0 +1,101 @@
+// Copyright 2018-2021 CERN
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// In applying this license, CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+package propagator
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
+	"github.com/cs3org/reva/v2/pkg/appctx"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
+	"github.com/pkg/errors"
+	"go.opentelemetry.io/otel"
+	"go.opentelemetry.io/otel/trace"
+)
+
+var tracer trace.Tracer
+
+func init() {
+	tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree/propagator")
+}
+
+type Propagator interface {
+	Propagate(ctx context.Context, node *node.Node, sizediff int64) error
+}
+
+func New(lookup lookup.PathLookup, o *options.Options) Propagator {
+	switch o.Propagator {
+	case "async":
+		return NewAsyncPropagator(o.TreeSizeAccounting, o.TreeTimeAccounting, o.AsyncPropagatorOptions, lookup)
+	default:
+		return NewSyncPropagator(o.TreeSizeAccounting, o.TreeTimeAccounting, lookup)
+	}
+}
+
+func calculateTreeSize(ctx context.Context, lookup lookup.PathLookup, childrenPath string) (uint64, error) {
+	ctx, span := tracer.Start(ctx, "calculateTreeSize")
+	defer span.End()
+	var size uint64
+
+	f, err := os.Open(childrenPath)
+	if err != nil {
+		appctx.GetLogger(ctx).Error().Err(err).Str("childrenPath", childrenPath).Msg("could not open dir")
+		return 0, err
+	}
+	defer f.Close()
+
+	names, err := f.Readdirnames(0)
+	if err != nil {
+		appctx.GetLogger(ctx).Error().Err(err).Str("childrenPath", childrenPath).Msg("could not read dirnames")
+		return 0, err
+	}
+	for i := range names {
+		cPath := filepath.Join(childrenPath, names[i])
+		resolvedPath, err := filepath.EvalSymlinks(cPath)
+		if err != nil {
+			appctx.GetLogger(ctx).Error().Err(err).Str("childpath", cPath).Msg("could not resolve child entry symlink")
+			continue // continue after an error
+		}
+
+		// raw read of the attributes for performance reasons
+		attribs, err := lookup.MetadataBackend().All(ctx, resolvedPath)
+		if err != nil {
+			appctx.GetLogger(ctx).Error().Err(err).Str("childpath", cPath).Msg("could not read attributes of child entry")
+			continue // continue after an error
+		}
+		sizeAttr := ""
+		if string(attribs[prefixes.TypeAttr]) == strconv.FormatUint(uint64(sprovider.ResourceType_RESOURCE_TYPE_FILE), 10) {
+			sizeAttr = string(attribs[prefixes.BlobsizeAttr])
+		} else {
+			sizeAttr = string(attribs[prefixes.TreesizeAttr])
+		}
+		csize, err := strconv.ParseInt(sizeAttr, 10, 64)
+		if err != nil {
+			return 0, errors.Wrapf(err, "invalid blobsize xattr format")
+		}
+		size += uint64(csize)
+	}
+	return size, err
+}
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go
new file mode 100644
index 00000000000..4e03aada684
--- /dev/null
+++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator/sync.go
@@ -0,0 +1,208 @@
+// Copyright 2018-2021 CERN
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// In applying this license, CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+package propagator
+
+import (
+	"context"
+	"errors"
+	"os"
+	"strconv"
+	"time"
+
+	"github.com/cs3org/reva/v2/pkg/appctx"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
+	"github.com/rogpeppe/go-internal/lockedfile"
+)
+
+// SyncPropagator implements synchronous treetime & treesize propagation
+type SyncPropagator struct {
+	treeSizeAccounting bool
+	treeTimeAccounting bool
+	lookup             lookup.PathLookup
+}
+
+// NewSyncPropagator returns a new AsyncPropagator instance
+func NewSyncPropagator(treeSizeAccounting, treeTimeAccounting bool, lookup lookup.PathLookup) SyncPropagator {
+	return SyncPropagator{
+		treeSizeAccounting: treeSizeAccounting,
+		treeTimeAccounting: treeTimeAccounting,
+		lookup:             lookup,
+	}
+}
+
+// Propagate triggers a propagation
+func (p SyncPropagator) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) error {
+	ctx, span := tracer.Start(ctx, "Propagate")
+	defer span.End()
+	sublog := appctx.GetLogger(ctx).With().
+		Str("method", "sync.Propagate").
+		Str("spaceid", n.SpaceID).
+		Str("nodeid", n.ID).
+		Int64("sizeDiff", sizeDiff).
+		Logger()
+
+	if !p.treeTimeAccounting && (!p.treeSizeAccounting || sizeDiff == 0) {
+		// no propagation enabled
+		sublog.Debug().Msg("propagation disabled or nothing to propagate")
+		return nil
+	}
+
+	// is propagation enabled for the parent node?
+	root := n.SpaceRoot
+
+	// use a sync time and don't rely on the mtime of the current node, as the stat might not change when a rename happened too quickly
+	sTime := time.Now().UTC()
+
+	// we loop until we reach the root
+	var err error
+	for err == nil && n.ID != root.ID {
+		sublog.Debug().Msg("propagating")
+
+		attrs := node.Attributes{}
+
+		var f *lockedfile.File
+		// lock parent before reading treesize or tree time
+
+		_, subspan := tracer.Start(ctx, "lockedfile.OpenFile")
+		parentFilename := p.lookup.MetadataBackend().LockfilePath(n.ParentPath())
+		f, err = lockedfile.OpenFile(parentFilename, os.O_RDWR|os.O_CREATE, 0600)
+		subspan.End()
+		if err != nil {
+			sublog.Error().Err(err).
+				Str("parent filename", parentFilename).
+				Msg("Propagation failed. Could not open metadata for parent with lock.")
+			return err
+		}
+		// always log error if closing node fails
+		defer func() {
+			// ignore already closed error
+			cerr := f.Close()
+			if err == nil && cerr != nil && !errors.Is(cerr, os.ErrClosed) {
+				err = cerr // only overwrite err with en error from close if the former was nil
+			}
+		}()
+
+		if n, err = n.Parent(ctx); err != nil {
+			sublog.Error().Err(err).
+				Msg("Propagation failed. Could not read parent node.")
+			return err
+		}
+
+		if !n.HasPropagation(ctx) {
+			sublog.Debug().Str("attr", prefixes.PropagationAttr).Msg("propagation attribute not set or unreadable, not propagating")
+			// if the attribute is not set treat it as false / none / no propagation
+			return nil
+		}
+
+		sublog = sublog.With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger()
+
+		if p.treeTimeAccounting {
+			// update the parent tree time if it is older than the nodes mtime
+			updateSyncTime := false
+
+			var tmTime time.Time
+			tmTime, err = n.GetTMTime(ctx)
+			switch {
+			case err != nil:
+				// missing attribute, or invalid format, overwrite
+				sublog.Debug().Err(err).
+					Msg("could not read tmtime attribute, overwriting")
+				updateSyncTime = true
+			case tmTime.Before(sTime):
+				sublog.Debug().
+					Time("tmtime", tmTime).
+					Time("stime", sTime).
+					Msg("parent tmtime is older than node mtime, updating")
+				updateSyncTime = true
+			default:
+				sublog.Debug().
+					Time("tmtime", tmTime).
+					Time("stime", sTime).
+					Dur("delta", sTime.Sub(tmTime)).
+					Msg("parent tmtime is younger than node mtime, not updating")
+			}
+
+			if updateSyncTime {
+				// update the tree time of the parent node
+				attrs.SetString(prefixes.TreeMTimeAttr, sTime.UTC().Format(time.RFC3339Nano))
+			}
+
+			attrs.SetString(prefixes.TmpEtagAttr, "")
+		}
+
+		// size accounting
+		if p.treeSizeAccounting && sizeDiff != 0 {
+			var newSize uint64
+
+			// read treesize
+			treeSize, err := n.GetTreeSize(ctx)
+			switch {
+			case metadata.IsAttrUnset(err):
+				// fallback to calculating the treesize
+				sublog.Warn().Msg("treesize attribute unset, falling back to calculating the treesize")
+				newSize, err = calculateTreeSize(ctx, p.lookup, n.InternalPath())
+				if err != nil {
+					return err
+				}
+			case err != nil:
+				sublog.Error().Err(err).
+					Msg("Faild to propagate treesize change. Error when reading the treesize attribute from parent")
+				return err
+			case sizeDiff > 0:
+				newSize = treeSize + uint64(sizeDiff)
+			case uint64(-sizeDiff) > treeSize:
+				// The sizeDiff is larger than the current treesize. Which would result in
+				// a negative new treesize. Something must have gone wrong with the accounting.
+				// Reset the current treesize to 0.
+				sublog.Error().Uint64("treeSize", treeSize).Int64("sizeDiff", sizeDiff).
+					Msg("Error when updating treesize of parent node. Updated treesize < 0. Reestting to 0")
+				newSize = 0
+			default:
+				newSize = treeSize - uint64(-sizeDiff)
+			}
+
+			// update the tree size of the node
+			attrs.SetString(prefixes.TreesizeAttr, strconv.FormatUint(newSize, 10))
+			sublog.Debug().Uint64("newSize", newSize).Msg("updated treesize of parent node")
+		}
+
+		if err = n.SetXattrsWithContext(ctx, attrs, false); err != nil {
+			sublog.Error().Err(err).Msg("Failed to update extend attributes of parent node")
+			return err
+		}
+
+		// Release node lock early, ignore already closed error
+		_, subspan = tracer.Start(ctx, "f.Close")
+		cerr := f.Close()
+		subspan.End()
+		if cerr != nil && !errors.Is(cerr, os.ErrClosed) {
+			sublog.Error().Err(cerr).Msg("Failed to close parent node and release lock")
+			return cerr
+		}
+	}
+	if err != nil {
+		sublog.Error().Err(err).Msg("error propagating")
+		return err
+	}
+	return nil
+
+}
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go
index 67beadc83ec..4ab4bda4d12 100644
--- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go
+++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/tree.go
@@ -28,7 +28,6 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
-	"strconv"
 	"strings"
 	"time"
 
@@ -36,14 +35,13 @@ import (
 	"github.com/cs3org/reva/v2/pkg/appctx"
 	"github.com/cs3org/reva/v2/pkg/errtypes"
 	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
-	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
 	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
 	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
 	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
+	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator"
 	"github.com/cs3org/reva/v2/pkg/utils"
 	"github.com/google/uuid"
 	"github.com/pkg/errors"
-	"github.com/rogpeppe/go-internal/lockedfile"
 	"github.com/rs/zerolog/log"
 	"go-micro.dev/v4/store"
 	"go.opentelemetry.io/otel"
@@ -66,24 +64,11 @@ type Blobstore interface {
 	Delete(node *node.Node) error
 }
 
-// PathLookup defines the interface for the lookup component
-type PathLookup interface {
-	NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error)
-	NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error)
-
-	InternalRoot() string
-	InternalPath(spaceID, nodeID string) string
-	Path(ctx context.Context, n *node.Node, hasPermission node.PermissionFunc) (path string, err error)
-	MetadataBackend() metadata.Backend
-	ReadBlobSizeAttr(ctx context.Context, path string) (int64, error)
-	ReadBlobIDAttr(ctx context.Context, path string) (string, error)
-	TypeFromPath(ctx context.Context, path string) provider.ResourceType
-}
-
 // Tree manages a hierarchical tree
 type Tree struct {
-	lookup    PathLookup
-	blobstore Blobstore
+	lookup     lookup.PathLookup
+	blobstore  Blobstore
+	propagator propagator.Propagator
 
 	options *options.Options
 
@@ -94,12 +79,13 @@ type Tree struct {
 type PermissionCheckFunc func(rp *provider.ResourcePermissions) bool
 
 // New returns a new instance of Tree
-func New(lu PathLookup, bs Blobstore, o *options.Options, cache store.Store) *Tree {
+func New(lu lookup.PathLookup, bs Blobstore, o *options.Options, cache store.Store) *Tree {
 	return &Tree{
-		lookup:    lu,
-		blobstore: bs,
-		options:   o,
-		idCache:   cache,
+		lookup:     lu,
+		blobstore:  bs,
+		options:    o,
+		idCache:    cache,
+		propagator: propagator.New(lu, o),
 	}
 }
 
@@ -718,205 +704,7 @@ func (t *Tree) removeNode(ctx context.Context, path string, n *node.Node) error
 
 // Propagate propagates changes to the root of the tree
 func (t *Tree) Propagate(ctx context.Context, n *node.Node, sizeDiff int64) (err error) {
-	ctx, span := tracer.Start(ctx, "Propagate")
-	defer span.End()
-	sublog := appctx.GetLogger(ctx).With().
-		Str("method", "tree.Propagate").
-		Str("spaceid", n.SpaceID).
-		Str("nodeid", n.ID).
-		Int64("sizeDiff", sizeDiff).
-		Logger()
-
-	if !t.options.TreeTimeAccounting && (!t.options.TreeSizeAccounting || sizeDiff == 0) {
-		// no propagation enabled
-		sublog.Debug().Msg("propagation disabled or nothing to propagate")
-		return
-	}
-
-	// is propagation enabled for the parent node?
-	root := n.SpaceRoot
-
-	// use a sync time and don't rely on the mtime of the current node, as the stat might not change when a rename happened too quickly
-	sTime := time.Now().UTC()
-
-	// we loop until we reach the root
-	for err == nil && n.ID != root.ID {
-		sublog.Debug().Msg("propagating")
-
-		attrs := node.Attributes{}
-
-		var f *lockedfile.File
-		// lock parent before reading treesize or tree time
-
-		_, subspan := tracer.Start(ctx, "lockedfile.OpenFile")
-		parentFilename := t.lookup.MetadataBackend().LockfilePath(n.ParentPath())
-		f, err = lockedfile.OpenFile(parentFilename, os.O_RDWR|os.O_CREATE, 0600)
-		subspan.End()
-		if err != nil {
-			sublog.Error().Err(err).
-				Str("parent filename", parentFilename).
-				Msg("Propagation failed. Could not open metadata for parent with lock.")
-			return err
-		}
-		// always log error if closing node fails
-		defer func() {
-			// ignore already closed error
-			cerr := f.Close()
-			if err == nil && cerr != nil && !errors.Is(cerr, os.ErrClosed) {
-				err = cerr // only overwrite err with en error from close if the former was nil
-			}
-		}()
-
-		if n, err = n.Parent(ctx); err != nil {
-			sublog.Error().Err(err).
-				Msg("Propagation failed. Could not read parent node.")
-			return err
-		}
-
-		// TODO none, sync and async?
-		if !n.HasPropagation(ctx) {
-			sublog.Debug().Str("attr", prefixes.PropagationAttr).Msg("propagation attribute not set or unreadable, not propagating")
-			// if the attribute is not set treat it as false / none / no propagation
-			return nil
-		}
-
-		sublog = sublog.With().Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Logger()
-
-		if t.options.TreeTimeAccounting {
-			// update the parent tree time if it is older than the nodes mtime
-			updateSyncTime := false
-
-			var tmTime time.Time
-			tmTime, err = n.GetTMTime(ctx)
-			switch {
-			case err != nil:
-				// missing attribute, or invalid format, overwrite
-				sublog.Debug().Err(err).
-					Msg("could not read tmtime attribute, overwriting")
-				updateSyncTime = true
-			case tmTime.Before(sTime):
-				sublog.Debug().
-					Time("tmtime", tmTime).
-					Time("stime", sTime).
-					Msg("parent tmtime is older than node mtime, updating")
-				updateSyncTime = true
-			default:
-				sublog.Debug().
-					Time("tmtime", tmTime).
-					Time("stime", sTime).
-					Dur("delta", sTime.Sub(tmTime)).
-					Msg("parent tmtime is younger than node mtime, not updating")
-			}
-
-			if updateSyncTime {
-				// update the tree time of the parent node
-				attrs.SetString(prefixes.TreeMTimeAttr, sTime.UTC().Format(time.RFC3339Nano))
-			}
-
-			attrs.SetString(prefixes.TmpEtagAttr, "")
-		}
-
-		// size accounting
-		if t.options.TreeSizeAccounting && sizeDiff != 0 {
-			var newSize uint64
-
-			// read treesize
-			treeSize, err := n.GetTreeSize(ctx)
-			switch {
-			case metadata.IsAttrUnset(err):
-				// fallback to calculating the treesize
-				sublog.Warn().Msg("treesize attribute unset, falling back to calculating the treesize")
-				newSize, err = t.calculateTreeSize(ctx, n.InternalPath())
-				if err != nil {
-					return err
-				}
-			case err != nil:
-				sublog.Error().Err(err).
-					Msg("Faild to propagate treesize change. Error when reading the treesize attribute from parent")
-				return err
-			case sizeDiff > 0:
-				newSize = treeSize + uint64(sizeDiff)
-			case uint64(-sizeDiff) > treeSize:
-				// The sizeDiff is larger than the current treesize. Which would result in
-				// a negative new treesize. Something must have gone wrong with the accounting.
-				// Reset the current treesize to 0.
-				sublog.Error().Uint64("treeSize", treeSize).Int64("sizeDiff", sizeDiff).
-					Msg("Error when updating treesize of parent node. Updated treesize < 0. Reestting to 0")
-				newSize = 0
-			default:
-				newSize = treeSize - uint64(-sizeDiff)
-			}
-
-			// update the tree size of the node
-			attrs.SetString(prefixes.TreesizeAttr, strconv.FormatUint(newSize, 10))
-			sublog.Debug().Uint64("newSize", newSize).Msg("updated treesize of parent node")
-		}
-
-		if err = n.SetXattrsWithContext(ctx, attrs, false); err != nil {
-			sublog.Error().Err(err).Msg("Failed to update extend attributes of parent node")
-			return err
-		}
-
-		// Release node lock early, ignore already closed error
-		_, subspan = tracer.Start(ctx, "f.Close")
-		cerr := f.Close()
-		subspan.End()
-		if cerr != nil && !errors.Is(cerr, os.ErrClosed) {
-			sublog.Error().Err(cerr).Msg("Failed to close parent node and release lock")
-			return cerr
-		}
-	}
-	if err != nil {
-		sublog.Error().Err(err).Msg("error propagating")
-		return
-	}
-	return
-}
-
-func (t *Tree) calculateTreeSize(ctx context.Context, childrenPath string) (uint64, error) {
-	ctx, span := tracer.Start(ctx, "calculateTreeSize")
-	defer span.End()
-	var size uint64
-
-	f, err := os.Open(childrenPath)
-	if err != nil {
-		appctx.GetLogger(ctx).Error().Err(err).Str("childrenPath", childrenPath).Msg("could not open dir")
-		return 0, err
-	}
-	defer f.Close()
-
-	names, err := f.Readdirnames(0)
-	if err != nil {
-		appctx.GetLogger(ctx).Error().Err(err).Str("childrenPath", childrenPath).Msg("could not read dirnames")
-		return 0, err
-	}
-	for i := range names {
-		cPath := filepath.Join(childrenPath, names[i])
-		resolvedPath, err := filepath.EvalSymlinks(cPath)
-		if err != nil {
-			appctx.GetLogger(ctx).Error().Err(err).Str("childpath", cPath).Msg("could not resolve child entry symlink")
-			continue // continue after an error
-		}
-
-		// raw read of the attributes for performance reasons
-		attribs, err := t.lookup.MetadataBackend().All(ctx, resolvedPath)
-		if err != nil {
-			appctx.GetLogger(ctx).Error().Err(err).Str("childpath", cPath).Msg("could not read attributes of child entry")
-			continue // continue after an error
-		}
-		sizeAttr := ""
-		if string(attribs[prefixes.TypeAttr]) == strconv.FormatUint(uint64(provider.ResourceType_RESOURCE_TYPE_FILE), 10) {
-			sizeAttr = string(attribs[prefixes.BlobsizeAttr])
-		} else {
-			sizeAttr = string(attribs[prefixes.TreesizeAttr])
-		}
-		csize, err := strconv.ParseInt(sizeAttr, 10, 64)
-		if err != nil {
-			return 0, errors.Wrapf(err, "invalid blobsize xattr format")
-		}
-		size += uint64(csize)
-	}
-	return size, err
+	return t.propagator.Propagate(ctx, n, sizeDiff)
 }
 
 // WriteBlob writes a blob to the blobstore
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 78976024f76..1ced96053b1 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -352,7 +352,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.15.1-0.20230731061316-db79e9b61738
+# github.com/cs3org/reva/v2 v2.15.1-0.20230731073021-26b2f3432d11
 ## explicit; go 1.20
 github.com/cs3org/reva/v2/cmd/revad/internal/grace
 github.com/cs3org/reva/v2/cmd/revad/runtime
@@ -662,6 +662,7 @@ github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node
 github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options
 github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/spaceidindex
 github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree
+github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/propagator
 github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload
 github.com/cs3org/reva/v2/pkg/storage/utils/downloader
 github.com/cs3org/reva/v2/pkg/storage/utils/eosfs


Test patch:
diff --git a/services/proxy/pkg/middleware/policies_test.go b/services/proxy/pkg/middleware/policies_test.go
new file mode 100644
index 00000000000..78af83c4993
--- /dev/null
+++ b/services/proxy/pkg/middleware/policies_test.go
@@ -0,0 +1,173 @@
+package middleware_test
+
+import (
+	"context"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	. "github.com/onsi/gomega"
+	"github.com/stretchr/testify/mock"
+	"go-micro.dev/v4/client"
+	"google.golang.org/grpc"
+
+	cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
+	"github.com/owncloud/ocis/v2/services/proxy/mocks"
+	"github.com/owncloud/ocis/v2/services/webdav/pkg/net"
+
+	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+	pMessage "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/policies/v0"
+	policiesPG "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/policies/v0"
+	"github.com/owncloud/ocis/v2/services/proxy/pkg/middleware"
+)
+
+func TestPolicies_NoQuery_PassThrough(t *testing.T) {
+	var g = NewWithT(t)
+
+	policiesMiddleware, _, _ := prepare("")
+
+	responseRecorder := httptest.NewRecorder()
+	policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodGet, "/policies", nil))
+
+	g.Expect(responseRecorder.Code).To(Equal(http.StatusOK))
+}
+
+func TestPolicies_ErrorsOnEvaluationError(t *testing.T) {
+	var g = NewWithT(t)
+
+	policiesMiddleware, policiesProviderService, _ := prepare("any")
+	policiesProviderService.On("Evaluate", mock.Anything, mock.Anything).Return(
+		nil,
+		errors.New("any"),
+	)
+
+	responseRecorder := httptest.NewRecorder()
+	policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodGet, "/policies", nil))
+
+	g.Expect(responseRecorder.Code).To(Equal(http.StatusInternalServerError))
+}
+
+func TestPolicies_ErrorsOnDeny(t *testing.T) {
+	var g = NewWithT(t)
+
+	policiesMiddleware, policiesProviderService, _ := prepare("any")
+	policiesProviderService.On("Evaluate", mock.Anything, mock.Anything).Return(
+		&policiesPG.EvaluateResponse{},
+		nil,
+	)
+
+	responseRecorder := httptest.NewRecorder()
+	policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodGet, "/policies", nil))
+
+	result := responseRecorder.Result()
+	defer func() {
+		g.Expect(result.Body.Close()).ToNot(HaveOccurred())
+	}()
+
+	data, err := io.ReadAll(result.Body)
+	g.Expect(err).ToNot(HaveOccurred())
+	g.Expect(data).To(ContainSubstring(middleware.DeniedMessage))
+	g.Expect(responseRecorder.Code).To(Equal(http.StatusForbidden))
+}
+
+func TestPolicies_EvaluationEnvironment_HTTPStage(t *testing.T) {
+	var g = NewWithT(t)
+
+	policiesMiddleware, policiesProviderService, _ := prepare("any")
+	policiesProviderService.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(
+		func(_ context.Context, in *policiesPG.EvaluateRequest, _ ...client.CallOption) (*policiesPG.EvaluateResponse, error) {
+			g.Expect(in.Environment.Stage).To(Equal(pMessage.Stage_STAGE_HTTP))
+
+			return &policiesPG.EvaluateResponse{Result: false}, nil
+		},
+	)
+
+	responseRecorder := httptest.NewRecorder()
+	policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodGet, "/policies", nil))
+}
+
+func TestPolicies_EvaluationEnvironment_Request(t *testing.T) {
+	var g = NewWithT(t)
+
+	policiesMiddleware, policiesProviderService, _ := prepare("any")
+	policiesProviderService.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(
+		func(_ context.Context, in *policiesPG.EvaluateRequest, _ ...client.CallOption) (*policiesPG.EvaluateResponse, error) {
+			g.Expect(in.Environment.Request.Method).To(Equal(http.MethodDelete))
+			g.Expect(in.Environment.Request.Path).To(Equal("/whatever"))
+
+			return &policiesPG.EvaluateResponse{Result: false}, nil
+		},
+	)
+
+	responseRecorder := httptest.NewRecorder()
+	policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodDelete, "/whatever", nil))
+}
+
+func TestPolicies_EvaluationEnvironment_Resource(t *testing.T) {
+	var g = NewWithT(t)
+
+	policiesMiddleware, policiesProviderService, _ := prepare("any")
+
+	// tus metadata
+	{
+		responseRecorder := httptest.NewRecorder()
+		request := httptest.NewRequest(http.MethodPost, "/remote.php/dav/spaces", nil)
+		request.Header.Set(net.HeaderUploadMetadata, fmt.Sprintf("filename %v", base64.StdEncoding.EncodeToString([]byte("tus-file-name.png"))))
+		policiesProviderService.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(
+			func(_ context.Context, in *policiesPG.EvaluateRequest, _ ...client.CallOption) (*policiesPG.EvaluateResponse, error) {
+				g.Expect(in.Environment.Resource.Name).To(Equal("tus-file-name.png"))
+
+				return &policiesPG.EvaluateResponse{Result: false}, nil
+			},
+		).Once()
+		policiesMiddleware.ServeHTTP(responseRecorder, request)
+	}
+
+	// url path
+	{
+		responseRecorder := httptest.NewRecorder()
+		policiesProviderService.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(
+			func(_ context.Context, in *policiesPG.EvaluateRequest, _ ...client.CallOption) (*policiesPG.EvaluateResponse, error) {
+				g.Expect(in.Environment.Resource.Name).To(Equal("simple-file-name.png"))
+
+				return &policiesPG.EvaluateResponse{Result: false}, nil
+			},
+		).Once()
+		policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodPut, "/remote.php/dav/spaces/simple-file-name.png", nil))
+	}
+}
+
+func prepare(q string) (http.Handler, *mocks.PoliciesProviderService, *cs3mocks.GatewayAPIClient) {
+
+	// mocked gatewaySelector
+	gatewayClient := &cs3mocks.GatewayAPIClient{}
+	gatewaySelector := pool.GetSelector[gateway.GatewayAPIClient](
+		"GatewaySelector",
+		"com.owncloud.api.gateway",
+		func(cc *grpc.ClientConn) gateway.GatewayAPIClient {
+			return gatewayClient
+		},
+	)
+	defer pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway")
+
+	// mocked policiesProviderService
+	policiesProviderService := &mocks.PoliciesProviderService{}
+
+	// spin up middleware
+	policiesMiddleware := middleware.Policies(
+		q,
+		middleware.WithRevaGatewaySelector(gatewaySelector),
+		middleware.PoliciesProviderService(policiesProviderService),
+	)(mockHandler{})
+
+	return policiesMiddleware, policiesProviderService, gatewayClient
+}
+
+type mockHandler struct{}
+
+func (m mockHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {}
diff --git a/tests/acceptance/features/apiSpaces/resharing.feature b/tests/acceptance/features/apiSpaces/resharing.feature
index 47dfc10ced8..879960b993f 100644
--- a/tests/acceptance/features/apiSpaces/resharing.feature
+++ b/tests/acceptance/features/apiSpaces/resharing.feature
@@ -54,10 +54,10 @@ Feature: Resharing
     And user "Carol" should be included in the response
     And user "Damian" should be included in the response
 
-
+  @skipOnRevaMaster
   Scenario: user can't share with more permissions than they have
     When user "Damian" shares folder "Shares/folder" with user "Ember" with permissions "31" using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the OCS status message should be "Cannot set the requested share permissions"
 
 
@@ -88,7 +88,7 @@ Feature: Resharing
       | Brian | 998  | should exist     |
       | Carol | 100  | should not exist |
 
-
+  @skipOnRevaMaster
   Scenario Outline: resharing folder with different permissions
     When user "<user>" shares folder "Shares/folder" with user "Ember" with permissions "<permissions>" using the sharing API
     Then the OCS status code should be "<code>"
@@ -97,10 +97,10 @@ Feature: Resharing
       | Brian  | 17          | 100  |
       | Carol  | 31          | 100  |
       | Damian | 17          | 100  |
-      | Damian | 27          | 404  |
-      | Damian | 31          | 404  |
-
+      | Damian | 27          | 403  |
+      | Damian | 31          | 403  |
 
+  @skipOnRevaMaster
   Scenario Outline: Resharing files with different permissions
     Given user "Alice" has uploaded file with content "Random data" to "/file.txt"
     And user "Alice" has shared file "/file.txt" with user "Brian" with permissions "<shareepermissions>"
@@ -110,7 +110,7 @@ Feature: Resharing
     Examples:
       | shareepermissions | granteepermissions | code |
       | 17                | 17                 | 100  |
-      | 17                | 19                 | 404  |
+      | 17                | 19                 | 403  |
       | 19                | 19                 | 100  |
 
 
diff --git a/tests/acceptance/features/apiSpacesShares/shareSpaces.feature b/tests/acceptance/features/apiSpacesShares/shareSpaces.feature
index 386754003fc..5db7e9ce27d 100644
--- a/tests/acceptance/features/apiSpacesShares/shareSpaces.feature
+++ b/tests/acceptance/features/apiSpacesShares/shareSpaces.feature
@@ -131,7 +131,7 @@ Feature: Share spaces
       | editor  |
       | viewer  |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user with editor or viewer role cannot share a space to another user
     Given user "Alice" has shared a space "share space" with settings:
       | shareWith | Brian  |
@@ -139,8 +139,8 @@ Feature: Share spaces
     When user "Brian" shares a space "share space" with settings:
       | shareWith | Bob        |
       | role      | <new_role> |
-    Then the HTTP status code should be "404"
-    And the OCS status code should be "404"
+    Then the HTTP status code should be "403"
+    And the OCS status code should be "403"
     And the OCS status message should be "No share permission"
     And the user "Bob" should not have a space called "share space"
     Examples:
@@ -172,7 +172,7 @@ Feature: Share spaces
       | manager | editor   |
       | manager | viewer   |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user without manager role cannot change the role of space members
     Given user "Alice" has shared a space "share space" with settings:
       | shareWith | Brian  |
@@ -183,8 +183,8 @@ Feature: Share spaces
     When user "Brian" updates the space "share space" with settings:
       | shareWith | Bob        |
       | role      | <new_role> |
-    Then the HTTP status code should be "404"
-    And the OCS status code should be "404"
+    Then the HTTP status code should be "403"
+    And the OCS status code should be "403"
     And the user "Alice" should have a space called "share space" granted to "Bob" with role "viewer"
     Examples:
       | role   | new_role |
diff --git a/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature b/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature
index a8ca3f0651e..42605ac171b 100644
--- a/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature
+++ b/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature
@@ -70,15 +70,15 @@ Feature: Share spaces via link
     And for user "Alice" the space "share space" should contain these entries:
       | lorem.txt |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user without manager role cannot share a space to public via link
     Given user "Alice" has shared a space "share space" with settings:
       | shareWith | Brian  |
       | role      | <role> |
     When user "Brian" creates a public link share of the space "share space" with settings:
       | permissions | 1 |
-    Then the HTTP status code should be "404"
-    And the OCS status code should be "404"
+    Then the HTTP status code should be "403"
+    And the OCS status code should be "403"
     And the OCS status message should be "No share permission"
     And for user "Alice" the space "share space" should not contain the last created public link
     Examples:
diff --git a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature
index 6484ff5abb8..5c697e13a94 100644
--- a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature
+++ b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpace.feature
@@ -65,7 +65,7 @@ Feature: Share a file or folder that is inside a space
       | file.txt | file   | viewer | 2042-03-25T23:59:59+0100 | 2042-03-25 |
       | file.txt | file   | editor |                          |            |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user participant of the project space without space manager role cannot share an entity to another user
     Given user "Alice" has shared a space "share sub-item" with settings:
       | shareWith | Brian       |
@@ -74,8 +74,8 @@ Feature: Share a file or folder that is inside a space
       | path      | <entity> |
       | shareWith | Bob      |
       | role      | editor   |
-    Then the HTTP status code should be "404"
-    And the OCS status code should be "404"
+    Then the HTTP status code should be "403"
+    And the OCS status code should be "403"
     And the OCS status message should be "No share permission"
     Examples:
       | entity   | spaceRole |
diff --git a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature
index 82b8ca511b3..eed669352bf 100644
--- a/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature
+++ b/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature
@@ -96,7 +96,7 @@ Feature: Share a file or folder that is inside a space via public link
       | folder          | /folder     | folder    | httpd/unix-directory |
       | folder/file.txt | /file.txt   | file      | text/plain           |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user participant of the project space without space manager role cannot share an entity inside project space via public link
     Given user "Alice" has shared a space "share sub-item" with settings:
       | shareWith | Brian       |
@@ -108,8 +108,8 @@ Feature: Share a file or folder that is inside a space via public link
       | password    | 123                      |
       | name        | public link              |
       | expireDate  | 2042-03-25T23:59:59+0100 |
-    Then the HTTP status code should be "404"
-    And the OCS status code should be "404"
+    Then the HTTP status code should be "403"
+    And the OCS status code should be "403"
     And the OCS status message should be "No share permission"
     Examples:
       | entity          | spaceRole |
diff --git a/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature
index 9f61b69da0a..e1eac3261cc 100644
--- a/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature
+++ b/tests/acceptance/features/coreApiSharePublicLink2/reShareAsPublicLinkToSharesNewDav.feature
@@ -10,7 +10,7 @@ Feature: reshare as public link
       | Alice    |
       | Brian    |
 
-
+  @skipOnRevaMaster
   Scenario Outline: creating a public link from a share with read permission only is not allowed
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/test"
@@ -19,12 +19,12 @@ Feature: reshare as public link
     When user "Brian" creates a public link share using the sharing API with settings
       | path         | /Shares/test |
       | publicUpload | false        |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
 
 
   Scenario Outline: creating a public link from a share with share+read only permissions is allowed
@@ -46,7 +46,7 @@ Feature: reshare as public link
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: creating an upload public link from a share with share+read only permissions is not allowed
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/test"
@@ -56,14 +56,14 @@ Feature: reshare as public link
       | path         | /Shares/test              |
       | permissions  | read,update,create,delete |
       | publicUpload | true                      |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
-
+      | 2               | 403              |
 
+  @skipOnRevaMaster
   Scenario Outline: creating a public link from a share with read+write permissions only is not allowed
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/test"
@@ -72,12 +72,12 @@ Feature: reshare as public link
     When user "Brian" creates a public link share using the sharing API with settings
       | path         | /Shares/test |
       | publicUpload | true         |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
 
 
   Scenario Outline: creating a public link from a share with share+read+write permissions is allowed
@@ -120,7 +120,7 @@ Feature: reshare as public link
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: creating an upload public link from a sub-folder of a share with share+read only permissions is not allowed
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/test"
@@ -131,14 +131,14 @@ Feature: reshare as public link
       | path         | /Shares/test/sub          |
       | permissions  | read,update,create,delete |
       | publicUpload | true                      |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
-
+      | 2               | 403              |
 
+  @skipOnRevaMaster
   Scenario Outline: increasing permissions of a public link of a share with share+read only permissions is not allowed
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/test"
@@ -151,15 +151,15 @@ Feature: reshare as public link
       | publicUpload | false        |
     When user "Brian" updates the last public link share using the sharing API with
       | permissions | read,update,create,delete |
-    Then the OCS status code should be "404" or "403"
-    And the HTTP status code should be "<http_status_code1>" or "<http_status_code2>"
+    Then the OCS status code should be "403"
+    And the HTTP status code should be "<http_status_code>"
     And uploading a file should not work using the new public WebDAV API
     Examples:
-      | ocs_api_version | http_status_code1 | http_status_code2 |
-      | 1               | 200               | 200               |
-      | 2               | 404               | 403               |
-
+      | ocs_api_version | http_status_code |
+      | 1               | 200              |
+      | 2               | 403              |
 
+  @skipOnRevaMaster
   Scenario Outline: increasing permissions of a public link from a sub-folder of a share with share+read only permissions is not allowed
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/test"
@@ -173,10 +173,10 @@ Feature: reshare as public link
     And uploading a file should not work using the new public WebDAV API
     When user "Brian" updates the last public link share using the sharing API with
       | permissions | read,update,create,delete |
-    Then the OCS status code should be "404" or "403"
-    And the HTTP status code should be "<http_status_code1>" or "<http_status_code2>"
+    Then the OCS status code should be "403"
+    And the HTTP status code should be "<http_status_code>"
     And uploading a file should not work using the new public WebDAV API
     Examples:
-      | ocs_api_version | http_status_code1 | http_status_code2 |
-      | 1               | 200               | 200               |
-      | 2               | 404               | 403               |
+      | ocs_api_version | http_status_code |
+      | 1               | 200              |
+      | 2               | 403              |
diff --git a/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature
index 942c8d06793..961d3faeb91 100644
--- a/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature
+++ b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature
@@ -246,7 +246,7 @@ Feature: update a public link share
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: adding public upload to a read only shared folder as recipient is not allowed using the public API
     Given using OCS API version "<ocs_api_version>"
     And user "Brian" has been created with default attributes and without skeleton files
@@ -258,14 +258,14 @@ Feature: update a public link share
       | publicUpload | false        |
     When user "Brian" updates the last public link share using the sharing API with
       | publicUpload | true |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And uploading a file should not work using the old public WebDAV API
     And uploading a file should not work using the new public WebDAV API
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
 
 
   Scenario Outline:adding public upload to a shared folder as recipient is allowed with permissions using the public API
@@ -288,7 +288,7 @@ Feature: update a public link share
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: adding public link with all permissions to a read only shared folder as recipient is not allowed using the public API
     Given using OCS API version "<ocs_api_version>"
     And user "Brian" has been created with default attributes and without skeleton files
@@ -300,14 +300,14 @@ Feature: update a public link share
       | permissions | read         |
     When user "Brian" updates the last public link share using the sharing API with
       | permissions | read,update,create,delete |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And uploading a file should not work using the old public WebDAV API
     And uploading a file should not work using the new public WebDAV API
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
 
 
   Scenario Outline: adding public link with all permissions to a read only shared folder as recipient is allowed with permissions using the public API
diff --git a/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature b/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature
index df88a9264fc..c84136819fd 100644
--- a/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature
+++ b/tests/acceptance/features/coreApiShareReshareToShares1/reShare.feature
@@ -11,14 +11,14 @@ Feature: sharing
       | Brian    |
       | Carol    |
 
-  @smokeTest
+  @smokeTest @skipOnRevaMaster
   Scenario Outline: user is not allowed to reshare file when reshare permission is not given
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt"
     And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions "read,update"
     And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
     When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions "read,update" using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" file "/Shares/textfile0.txt" should not exist
     And the sharing API should report to user "Carol" that no shares are in the pending state
@@ -26,16 +26,16 @@ Feature: sharing
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
-
+      | 2               | 403              |
 
+  @skipOnRevaMaster
   Scenario Outline: user is not allowed to reshare folder when reshare permission is not given
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/FOLDER"
     And user "Alice" has shared folder "/FOLDER" with user "Brian" with permissions "read,update"
     And user "Brian" has accepted share "/FOLDER" offered by user "Alice"
     When user "Brian" shares folder "/Shares/FOLDER" with user "Carol" with permissions "read,update" using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" folder "/Shares/FOLDER" should not exist
     And the sharing API should report to user "Carol" that no shares are in the pending state
@@ -43,7 +43,7 @@ Feature: sharing
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
 
   @smokeTest
   Scenario Outline: user is allowed to reshare file with the same permissions
@@ -109,14 +109,14 @@ Feature: sharing
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user is not allowed to reshare file and set more permissions bits
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile0.txt"
     And user "Alice" has shared file "/textfile0.txt" with user "Brian" with permissions 17
     And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
     When user "Brian" shares file "/Shares/textfile0.txt" with user "Carol" with permissions <reshare_permissions> using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" file "/Shares/textfile0.txt" should not exist
     And the sharing API should report to user "Carol" that no shares are in the pending state
@@ -125,18 +125,18 @@ Feature: sharing
       | ocs_api_version | http_status_code | reshare_permissions |
       # passing on more bits including reshare
       | 1               | 200              | 19                  |
-      | 2               | 404              | 19                  |
+      | 2               | 403              | 19                  |
       | 1               | 200              | 23                  |
-      | 2               | 404              | 23                  |
+      | 2               | 403              | 23                  |
       | 1               | 200              | 31                  |
-      | 2               | 404              | 31                  |
+      | 2               | 403              | 31                  |
       # passing on more bits but not reshare
       | 1               | 200              | 3                   |
-      | 2               | 404              | 3                   |
+      | 2               | 403              | 3                   |
       | 1               | 200              | 7                   |
-      | 2               | 404              | 7                   |
+      | 2               | 403              | 7                   |
       | 1               | 200              | 15                  |
-      | 2               | 404              | 15                  |
+      | 2               | 403              | 15                  |
 
 
   Scenario Outline: user is allowed to reshare file and set create (4) or delete (8) permissions bits, which get ignored
@@ -179,14 +179,14 @@ Feature: sharing
       | 1               | 100             | 17                   | 9                   | 1                   |
       | 2               | 200             | 17                   | 9                   | 1                   |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user is not allowed to reshare folder and set more permissions bits
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/PARENT"
     And user "Alice" has shared folder "/PARENT" with user "Brian" with permissions <received_permissions>
     And user "Brian" has accepted share "/PARENT" offered by user "Alice"
     When user "Brian" shares folder "/Shares/PARENT" with user "Carol" with permissions <reshare_permissions> using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" folder "/Shares/PARENT" should not exist
     And the sharing API should report to user "Carol" that no shares are in the pending state
@@ -195,39 +195,39 @@ Feature: sharing
       | ocs_api_version | http_status_code | received_permissions | reshare_permissions |
       # try to pass on more bits including reshare
       | 1               | 200              | 17                   | 19                  |
-      | 2               | 404              | 17                   | 19                  |
+      | 2               | 403              | 17                   | 19                  |
       | 1               | 200              | 17                   | 21                  |
-      | 2               | 404              | 17                   | 21                  |
+      | 2               | 403              | 17                   | 21                  |
       | 1               | 200              | 17                   | 23                  |
-      | 2               | 404              | 17                   | 23                  |
+      | 2               | 403              | 17                   | 23                  |
       | 1               | 200              | 17                   | 31                  |
-      | 2               | 404              | 17                   | 31                  |
+      | 2               | 403              | 17                   | 31                  |
       | 1               | 200              | 19                   | 23                  |
-      | 2               | 404              | 19                   | 23                  |
+      | 2               | 403              | 19                   | 23                  |
       | 1               | 200              | 19                   | 31                  |
-      | 2               | 404              | 19                   | 31                  |
+      | 2               | 403              | 19                   | 31                  |
       # try to pass on more bits but not reshare
       | 1               | 200              | 17                   | 3                   |
-      | 2               | 404              | 17                   | 3                   |
+      | 2               | 403              | 17                   | 3                   |
       | 1               | 200              | 17                   | 5                   |
-      | 2               | 404              | 17                   | 5                   |
+      | 2               | 403              | 17                   | 5                   |
       | 1               | 200              | 17                   | 7                   |
-      | 2               | 404              | 17                   | 7                   |
+      | 2               | 403              | 17                   | 7                   |
       | 1               | 200              | 17                   | 15                  |
-      | 2               | 404              | 17                   | 15                  |
+      | 2               | 403              | 17                   | 15                  |
       | 1               | 200              | 19                   | 7                   |
-      | 2               | 404              | 19                   | 7                   |
+      | 2               | 403              | 19                   | 7                   |
       | 1               | 200              | 19                   | 15                  |
-      | 2               | 404              | 19                   | 15                  |
-
+      | 2               | 403              | 19                   | 15                  |
 
+  @skipOnRevaMaster
   Scenario Outline: user is not allowed to reshare folder and add delete permission bit (8)
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has created folder "/PARENT"
     And user "Alice" has shared folder "/PARENT" with user "Brian" with permissions <received_permissions>
     And user "Brian" has accepted share "/PARENT" offered by user "Alice"
     When user "Brian" shares folder "/Shares/PARENT" with user "Carol" with permissions <reshare_permissions> using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" folder "/Shares/PARENT" should not exist
     And the sharing API should report to user "Carol" that no shares are in the pending state
@@ -236,18 +236,18 @@ Feature: sharing
       | ocs_api_version | http_status_code | received_permissions | reshare_permissions |
       # try to pass on extra delete (including reshare)
       | 1               | 200              | 17                   | 25                  |
-      | 2               | 404              | 17                   | 25                  |
+      | 2               | 403              | 17                   | 25                  |
       | 1               | 200              | 19                   | 27                  |
-      | 2               | 404              | 19                   | 27                  |
+      | 2               | 403              | 19                   | 27                  |
       | 1               | 200              | 23                   | 31                  |
-      | 2               | 404              | 23                   | 31                  |
+      | 2               | 403              | 23                   | 31                  |
       # try to pass on extra delete (but not reshare)
       | 1               | 200              | 17                   | 9                   |
-      | 2               | 404              | 17                   | 9                   |
+      | 2               | 403              | 17                   | 9                   |
       | 1               | 200              | 19                   | 11                  |
-      | 2               | 404              | 19                   | 11                  |
+      | 2               | 403              | 19                   | 11                  |
       | 1               | 200              | 23                   | 15                  |
-      | 2               | 404              | 23                   | 15                  |
+      | 2               | 403              | 23                   | 15                  |
 
 
   Scenario Outline: reshare a file with same name as a deleted file
diff --git a/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature b/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature
index 6a6e8550dc0..b2ee4c9a64a 100644
--- a/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature
+++ b/tests/acceptance/features/coreApiShareReshareToShares2/reShareSubfolder.feature
@@ -29,13 +29,13 @@ Feature: a subfolder of a received share can be reshared
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: user is not allowed to reshare a sub-folder with more permissions
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has shared folder "/TMP" with user "Brian" with permissions <received_permissions>
     And user "Brian" has accepted share "/TMP" offered by user "Alice"
     When user "Brian" shares folder "/Shares/TMP/SUB" with user "Carol" with permissions <reshare_permissions> using the sharing API
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" folder "/Shares/SUB" should not exist
     And the sharing API should report to user "Carol" that no shares are in the pending state
@@ -44,44 +44,44 @@ Feature: a subfolder of a received share can be reshared
       | ocs_api_version | http_status_code | received_permissions | reshare_permissions |
       # try to pass on more bits including reshare
       | 1               | 200              | 17                   | 19                  |
-      | 2               | 404              | 17                   | 19                  |
+      | 2               | 403              | 17                   | 19                  |
       | 1               | 200              | 17                   | 21                  |
-      | 2               | 404              | 17                   | 21                  |
+      | 2               | 403              | 17                   | 21                  |
       | 1               | 200              | 17                   | 23                  |
-      | 2               | 404              | 17                   | 23                  |
+      | 2               | 403              | 17                   | 23                  |
       | 1               | 200              | 17                   | 31                  |
-      | 2               | 404              | 17                   | 31                  |
+      | 2               | 403              | 17                   | 31                  |
       | 1               | 200              | 19                   | 23                  |
-      | 2               | 404              | 19                   | 23                  |
+      | 2               | 403              | 19                   | 23                  |
       | 1               | 200              | 19                   | 31                  |
-      | 2               | 404              | 19                   | 31                  |
+      | 2               | 403              | 19                   | 31                  |
       # try to pass on more bits but not reshare
       | 1               | 200              | 17                   | 3                   |
-      | 2               | 404              | 17                   | 3                   |
+      | 2               | 403              | 17                   | 3                   |
       | 1               | 200              | 17                   | 5                   |
-      | 2               | 404              | 17                   | 5                   |
+      | 2               | 403              | 17                   | 5                   |
       | 1               | 200              | 17                   | 7                   |
-      | 2               | 404              | 17                   | 7                   |
+      | 2               | 403              | 17                   | 7                   |
       | 1               | 200              | 17                   | 15                  |
-      | 2               | 404              | 17                   | 15                  |
+      | 2               | 403              | 17                   | 15                  |
       | 1               | 200              | 19                   | 7                   |
-      | 2               | 404              | 19                   | 7                   |
+      | 2               | 403              | 19                   | 7                   |
       | 1               | 200              | 19                   | 15                  |
-      | 2               | 404              | 19                   | 15                  |
+      | 2               | 403              | 19                   | 15                  |
       # try to pass on extra delete (including reshare)
       | 1               | 200              | 17                   | 25                  |
-      | 2               | 404              | 17                   | 25                  |
+      | 2               | 403              | 17                   | 25                  |
       | 1               | 200              | 19                   | 27                  |
-      | 2               | 404              | 19                   | 27                  |
+      | 2               | 403              | 19                   | 27                  |
       | 1               | 200              | 23                   | 31                  |
-      | 2               | 404              | 23                   | 31                  |
+      | 2               | 403              | 23                   | 31                  |
       # try to pass on extra delete (but not reshare)
       | 1               | 200              | 17                   | 9                   |
-      | 2               | 404              | 17                   | 9                   |
+      | 2               | 403              | 17                   | 9                   |
       | 1               | 200              | 19                   | 11                  |
-      | 2               | 404              | 19                   | 11                  |
+      | 2               | 403              | 19                   | 11                  |
       | 1               | 200              | 23                   | 15                  |
-      | 2               | 404              | 23                   | 15                  |
+      | 2               | 403              | 23                   | 15                  |
 
   @issue-2214
   Scenario Outline: user is allowed to update reshare of a sub-folder with less permissions
@@ -123,7 +123,7 @@ Feature: a subfolder of a received share can be reshared
       | 1               | 100             |
       | 2               | 200             |
 
-  @issue-2214
+  @issue-2214 @skipOnRevaMaster
   Scenario Outline: user is not allowed to update reshare of a sub-folder with more permissions
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,read"
@@ -132,7 +132,7 @@ Feature: a subfolder of a received share can be reshared
     And user "Carol" has accepted share "/SUB" offered by user "Brian"
     When user "Brian" updates the last share using the sharing API with
       | permissions | all |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And as "Carol" folder "/Shares/SUB" should exist
     But user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "/Shares/SUB/textfile.txt"
@@ -141,4 +141,4 @@ Feature: a subfolder of a received share can be reshared
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
diff --git a/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature b/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature
index 088eada24e9..867d12cba75 100644
--- a/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature
+++ b/tests/acceptance/features/coreApiShareReshareToShares3/reShareUpdate.feature
@@ -46,7 +46,7 @@ Feature: sharing
       | 1               | 100             |
       | 2               | 200             |
 
-
+  @skipOnRevaMaster
   Scenario Outline: do not allow update of reshare to exceed permissions
     Given using OCS API version "<ocs_api_version>"
     And user "Alice" has shared folder "/TMP" with user "Brian" with permissions "share,read"
@@ -55,13 +55,13 @@ Feature: sharing
     And user "Carol" has accepted share "/TMP" offered by user "Brian"
     When user "Brian" updates the last share using the sharing API with
       | permissions | all |
-    Then the OCS status code should be "404"
+    Then the OCS status code should be "403"
     And the HTTP status code should be "<http_status_code>"
     And user "Carol" should not be able to upload file "filesForUpload/textfile.txt" to "Shares/TMP/textfile.txt"
     Examples:
       | ocs_api_version | http_status_code |
       | 1               | 200              |
-      | 2               | 404              |
+      | 2               | 403              |
 
 
   Scenario Outline: update of user reshare by the original share owner can increase permissions up to the permissions of the top-level share