Repository: owncloud/ocis
Pull request #5423 (2023-01-18 16:14:50)

[docs-only] ADR for service authorization
please comment in this PR


Patch:
diff --git a/.drone.env b/.drone.env
index 146123a4365..e607cae2084 100644
--- a/.drone.env
+++ b/.drone.env
@@ -1,3 +1,3 @@
 # The test runner source for UI tests
-WEB_COMMITID=48032d756dd416636fcd9b6ffa9fd5700c594337
+WEB_COMMITID=087d56495474ef34b9f847bd30a23613ec0daacd
 WEB_BRANCH=master
diff --git a/.drone.star b/.drone.star
index 38e51c05712..6baa0f09754 100644
--- a/.drone.star
+++ b/.drone.star
@@ -142,10 +142,6 @@ config = {
         "skip": False,
         "earlyFail": True,
     },
-    "settingsUITests": {
-        "skip": False,
-        "earlyFail": True,
-    },
     "rocketchat": {
         "channel": "ocis-internal",
         "from_secret": "private_rocketchat",
@@ -364,9 +360,6 @@ def testPipelines(ctx):
     if "skip" not in config["e2eTests"] or not config["e2eTests"]["skip"]:
         pipelines += e2eTests(ctx)
 
-    if "skip" not in config["settingsUITests"] or not config["settingsUITests"]["skip"]:
-        pipelines.append(settingsUITests(ctx))
-
     return pipelines
 
 def testOcisModule(ctx, module):
@@ -805,7 +798,7 @@ def wopiValidatorTests(ctx, storage, accounts_hash_difficulty = 4):
                      },
                      {
                          "name": "wopiserver",
-                         "image": "cs3org/wopiserver:v9.3.0",
+                         "image": "cs3org/wopiserver:v9.3.3",
                          "detach": True,
                          "commands": [
                              "cp %s/tests/config/drone/wopiserver.conf /etc/wopi/wopiserver.conf" % (dirs["base"]),
@@ -889,7 +882,7 @@ def coreApiTests(ctx, part_number = 1, number_of_parts = 1, storage = "ocis", ac
                              "UPLOAD_DELETE_WAIT_TIME": "1" if storage == "owncloud" else 0,
                          },
                          "commands": [
-                             "make -C %s test-acceptance-core-api" % (dirs["base"]),
+                             "make -C %s test-acceptance-from-core-api" % (dirs["base"]),
                          ],
                      },
                  ] + failEarly(ctx, early_fail),
@@ -1151,74 +1144,6 @@ def publishTracingResult(ctx, suite):
         },
     }]
 
-def settingsUITests(ctx, storage = "ocis", accounts_hash_difficulty = 4):
-    early_fail = config["settingsUITests"]["earlyFail"] if "earlyFail" in config["settingsUITests"] else False
-
-    return {
-        "kind": "pipeline",
-        "type": "docker",
-        "name": "settingsUITests",
-        "platform": {
-            "os": "linux",
-            "arch": "amd64",
-        },
-        "steps": skipIfUnchanged(ctx, "acceptance-tests") +
-                 restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin") +
-                 ocisServer(storage, accounts_hash_difficulty) +
-                 waitForSeleniumService() +
-                 waitForMiddlewareService() +
-                 restoreWebCache() +
-                 restoreWebPnpmCache() +
-                 [
-                     {
-                         "name": "WebUIAcceptanceTests",
-                         "image": OC_CI_NODEJS % DEFAULT_NODEJS_VERSION,
-                         "environment": {
-                             "SERVER_HOST": "https://ocis-server:9200",
-                             "BACKEND_HOST": "https://ocis-server:9200",
-                             "RUN_ON_OCIS": "true",
-                             "OCIS_REVA_DATA_ROOT": "%s" % dirs["ocisRevaDataRoot"],
-                             "WEB_UI_CONFIG": "%s/%s" % (dirs["base"], dirs["ocisConfig"]),
-                             "TEST_TAGS": "not @skipOnOCIS and not @skip",
-                             "LOCAL_UPLOAD_DIR": "/uploads",
-                             "NODE_TLS_REJECT_UNAUTHORIZED": 0,
-                             "WEB_PATH": dirs["web"],
-                             "FEATURE_PATH": "%s/services/settings/ui/tests/acceptance/features" % dirs["base"],
-                             "MIDDLEWARE_HOST": "http://middleware:3000",
-                         },
-                         "commands": [
-                             # TODO: settings/package.json has all the acceptance test dependencies
-                             # they shouldn't be needed since we could also use them from web:/tests/acceptance/package.json
-                             "cd %s/services/settings" % dirs["base"],
-                             "pnpm config set store-dir ./.pnpm-store",
-                             "retry -t 3 'pnpm install'",
-                             "make test-acceptance-webui",
-                         ],
-                         "volumes": [{
-                             "name": "uploads",
-                             "path": "/uploads",
-                         }],
-                     },
-                 ] + failEarly(ctx, early_fail),
-        "services": [
-            {
-                "name": "redis",
-                "image": REDIS,
-            },
-        ] + selenium() + middlewareService(),
-        "volumes": [{
-            "name": "uploads",
-            "temp": {},
-        }],
-        "depends_on": getPipelineNames([buildOcisBinaryForTesting(ctx)] + buildWebCache(ctx)),
-        "trigger": {
-            "ref": [
-                "refs/heads/master",
-                "refs/pull/**",
-            ],
-        },
-    }
-
 def failEarly(ctx, early_fail):
     """failEarly sends posts a comment about the failed pipeline to the github pr and then kills all pipelines of the current build
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 78f64c73034..02a33149927 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,7 +23,9 @@ The following sections list the changes for unreleased.
 * Enhancement - Extended search: [#5221](https://github.com/owncloud/ocis/pull/5221)
 * Enhancement - Resource tags: [#5227](https://github.com/owncloud/ocis/pull/5227)
 * Enhancement - Expiration Notifications: [#5330](https://github.com/owncloud/ocis/pull/5330)
+* Enhancement - Better config for postprocessing service: [#5457](https://github.com/owncloud/ocis/pull/5457)
 * Enhancement - Add expiration to user and group shares: [#5389](https://github.com/owncloud/ocis/pull/5389)
+* Enhancement - Space Management permissions: [#5441](https://github.com/owncloud/ocis/pull/5441)
 
 # Changelog for [2.0.0] (2022-11-30)
 
diff --git a/Makefile b/Makefile
index d9e993d8ea1..61c4ef3820e 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,7 @@ help:
 	@echo -e "${GREEN}Testing with test suite natively installed:${RESET}\n"
 	@echo -e "${PURPLE}\tdocs: https://owncloud.dev/ocis/development/testing/#testing-with-test-suite-natively-installed${RESET}\n"
 	@echo -e "\tmake test-acceptance-api\t\t${BLUE}run API acceptance tests${RESET}"
-	@echo -e "\tmake test-acceptance-core-api\t\t${BLUE}run core API acceptance tests${RESET}"
+	@echo -e "\tmake test-acceptance-from-core-api\t\t${BLUE}run core API acceptance tests${RESET}"
 	@echo -e "\tmake test-paralleldeployment-api\t${BLUE}run API acceptance tests for parallel deployment${RESET}"
 	@echo -e "\tmake clean-tests\t\t\t${BLUE}delete API tests framework dependencies${RESET}"
 	@echo
@@ -111,8 +111,8 @@ CORE_BEHAT_YML=tests/acceptance/config/behat-core.yml
 test-acceptance-api: vendor-bin/behat/vendor
 	BEHAT_BIN=$(BEHAT_BIN) $(PWD)/tests/acceptance/run.sh --type api
 
-.PHONY: test-acceptance-core-api
-test-acceptance-core-api: vendor-bin/behat/vendor
+.PHONY: test-acceptance-from-core-api
+test-acceptance-from-core-api: vendor-bin/behat/vendor
 	BEHAT_BIN=$(BEHAT_BIN) BEHAT_YML=$(CORE_BEHAT_YML) $(PWD)/tests/acceptance/run.sh --type core-api
 
 .PHONY: test-paralleldeployment-api
diff --git a/changelog/unreleased/bump-reva.md b/changelog/unreleased/bump-reva.md
index 66429b33bd8..bd597ec5c5d 100644
--- a/changelog/unreleased/bump-reva.md
+++ b/changelog/unreleased/bump-reva.md
@@ -6,3 +6,4 @@ https://github.com/owncloud/ocis/pull/5243
 https://github.com/owncloud/ocis/pull/5285
 https://github.com/owncloud/ocis/pull/5310
 https://github.com/owncloud/ocis/pull/5404
+https://github.com/owncloud/ocis/pull/5460
diff --git a/changelog/unreleased/populate-expanded-properties.md b/changelog/unreleased/populate-expanded-properties.md
index b3ea06a4357..81c242e9544 100644
--- a/changelog/unreleased/populate-expanded-properties.md
+++ b/changelog/unreleased/populate-expanded-properties.md
@@ -3,4 +3,5 @@ Bugfix: Populate expanded properties
 We now return an empty array when an expanded relation has no entries. This makes consuming the responses a little easier.
 
 https://github.com/owncloud/ocis/pull/5421
-https://github.com/owncloud/ocis/issues/5419
\ No newline at end of file
+https://github.com/owncloud/ocis/issues/5419
+https://github.com/owncloud/ocis/pull/5426
\ No newline at end of file
diff --git a/changelog/unreleased/postprocessing-config.md b/changelog/unreleased/postprocessing-config.md
new file mode 100644
index 00000000000..3ff0f17bcb3
--- /dev/null
+++ b/changelog/unreleased/postprocessing-config.md
@@ -0,0 +1,5 @@
+Enhancement: Better config for postprocessing service
+
+The postporcessing service is now individually configurable. This is achieved by allowing a list of postprocessing steps that are processed in order of their appearance in the `POSTPROCESSING_STEPS` envvar.
+
+https://github.com/owncloud/ocis/pull/5457
diff --git a/changelog/unreleased/space-management-permissions.md b/changelog/unreleased/space-management-permissions.md
new file mode 100644
index 00000000000..24dfc0fcd2d
--- /dev/null
+++ b/changelog/unreleased/space-management-permissions.md
@@ -0,0 +1,5 @@
+Enhancement: Space Management permissions
+
+We added new space management permissions. `space-properties` will allow changing space properties (name, description, ...). `space-ability` will allow enabling and disabling spaces
+
+https://github.com/owncloud/ocis/pull/5441
diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml
index 192de75dc81..080e713c0b5 100644
--- a/deployments/examples/ocis_wopi/docker-compose.yml
+++ b/deployments/examples/ocis_wopi/docker-compose.yml
@@ -146,7 +146,7 @@ services:
     restart: always
 
   wopiserver:
-    image: cs3org/wopiserver:${WOPISERVER_DOCKER_TAG:-v9.3.2}
+    image: cs3org/wopiserver:${WOPISERVER_DOCKER_TAG:-v9.3.3}
     networks:
       ocis-net:
     entrypoint:
@@ -172,7 +172,7 @@ services:
     restart: always
 
   collabora:
-    image: collabora/code:22.05.9.2.1
+    image: collabora/code:22.05.9.3.1
     networks:
       ocis-net:
     environment:
diff --git a/docs/helpers/adoc-generator.go.tmpl b/docs/helpers/adoc-generator.go.tmpl
index 977363ad152..da0503d1a39 100644
--- a/docs/helpers/adoc-generator.go.tmpl
+++ b/docs/helpers/adoc-generator.go.tmpl
@@ -52,18 +52,26 @@ type templateData struct {
 
 func main() {
 	fmt.Println("Generating adoc documentation for environment variables:")
-	content, err := os.ReadFile("../../docs/templates/ADOC.tmpl")
+	adoc, err := os.ReadFile("../../docs/templates/ADOC.tmpl")
+	if err != nil {
+	    log.Fatal(err)
+	}
+	dpr, err := os.ReadFile("../../docs/templates/ADOC_deprecation.tmpl")
 	if err != nil {
 	    log.Fatal(err)
 	}
 	replacer := strings.NewReplacer(
 			"github.com/owncloud/ocis/v2/services/", "",
 			"/pkg/config/defaults", "",
-		)
-	var fields []ConfigField
-	var deprecations []DeprecationField
-	var targetFile *os.File
-	tpl := template.Must(template.New("").Parse(string(content)))
+	)
+	var (
+	    fields 	 []ConfigField
+	    deprecations []DeprecationField
+	    adocFile  	 *os.File
+	    dprFile 	 *os.File
+	)
+	adoctpl := template.Must(template.New("").Parse(string(adoc)))
+	dprtpl := template.Must(template.New("").Parse(string(dpr)))
 
 	m := map[string]interface{}{
 	{{- range $key, $value := .}}
@@ -103,19 +111,29 @@ func main() {
 
 	    if len(fields) > 0 || len(deprecations) > 0 {
 		fmt.Printf("... %s\n", pkg)
-		targetFile, err = os.Create(filepath.Join(targetFolder, service + "_configvars.adoc"))
-		if err != nil {
-			log.Fatalf("Failed to create target file: %s", err)
-		}
-		defer targetFile.Close()
-
 		td := templateData{
 			ExtensionName: service,
 			Fields:        fields,
 			Deprecations: deprecations,
 			HasDeprecations: hasDeprecations ,
 		}
-		if err := tpl.Execute(targetFile, td); err != nil {
+		adocFile, err = os.Create(filepath.Join(targetFolder, service + "_configvars.adoc"))
+		if err != nil {
+			log.Fatalf("Failed to create target file: %s", err)
+		}
+		defer adocFile.Close()
+
+		if err := adoctpl.Execute(adocFile, td); err != nil {
+			log.Fatalf("Failed to execute template: %s", err)
+		}
+
+		dprFile, err = os.Create(filepath.Join(targetFolder, service + "_deprecation.adoc"))
+		if err != nil {
+			log.Fatalf("Failed to create target file: %s", err)
+		}
+		defer dprFile.Close()
+
+		if err := dprtpl.Execute(dprFile, td); err != nil {
 			log.Fatalf("Failed to execute template: %s", err)
 		}
 	    }
diff --git a/docs/ocis/adr/0020-file-search-query-language.md b/docs/ocis/adr/0020-file-search-query-language.md
index 1317cdff3d9..e897c6b25c8 100644
--- a/docs/ocis/adr/0020-file-search-query-language.md
+++ b/docs/ocis/adr/0020-file-search-query-language.md
@@ -4,7 +4,7 @@ date: 2022-03-18T09:00:00+01:00
 weight: 20
 geekdocRepo: https://github.com/owncloud/ocis
 geekdocEditPath: edit/master/docs/ocis/adr
-geekdocFilePath: 0018-file-search-query-language.md
+geekdocFilePath: 0020-file-search-query-language.md
 ---
 
 * Status: proposed
diff --git a/docs/ocis/adr/0021-service-accounts.md b/docs/ocis/adr/0021-service-accounts.md
new file mode 100644
index 00000000000..507e33a1bac
--- /dev/null
+++ b/docs/ocis/adr/0021-service-accounts.md
@@ -0,0 +1,95 @@
+---
+title: "21. Service accounts"
+date: 2023-01-18T16:07:00+01:00
+weight: 21
+geekdocRepo: https://github.com/owncloud/ocis
+geekdocEditPath: edit/master/docs/ocis/adr
+geekdocFilePath: 0021-service-accounts.md
+---
+
+* Status: proposed
+* Deciders: @butonic, @C0rby
+* Date: 2023-01-18
+
+## Context and Problem Statement
+
+There are three levels of security checks in a microservice web application that uses OpenID Connect:
+1. **scope claims** limit the possible operations to what the user (or admin on behalf of the organization) consented to
+2. **service authorization** limit the possible operations to what specific services are allowed to do, on behalf of users or even without them
+3. **permission checks** limit the possible operations to the relationships between subject, permission and resource allow
+
+This ADR deals with a prerequisite for service authorization: service accounts.
+
+Some services need access to file content without a user being logged in. We currently pass the owner or manager
+of a space in events which allows the search service to impersonate that user to extract metadata from the changed resource.
+There are two problems with this: 
+1. The service could get all permissions of the user and gain write permission
+2. There is a race condition where the user in the event might no longer have read permission, causing the index to go stale
+
+The race condition will become more of an issue when we start working on a workflow engine.
+
+How can we grant services the least amount of permissions required for their purpose?
+
+## Decision Drivers
+
+* It should be possible to represent this as servicePrincipals in the libregraph API, similar to the [MS Graph servicePrincipal](https://learn.microsoft.com/en-us/graph/api/resources/serviceprincipal?view=graph-rest-1.0).
+* Services should check permissions using the oCIS permissions or reva auth service, we don't want to introduce a new mechanism for this
+
+## Considered Options
+
+* [Service Accounts](#service-accounts)
+* [Impersonate Space-Owners](#impersonate-space-owners)
+
+## Decision Outcome
+
+Chosen option: [Service Accounts](#service-accounts)
+
+### Consequences
+
+* Good, because it allows provisioning permissions for services
+* Good, because it uses existing CS3 concepts
+* Good, because it uses the existing permissions service
+* Good, because it can be mapped to libre graph permissions
+* Bad, because we have to make the reva auth manager aware of CS3 [`USER_TYPE_SERVICE`](https://cs3org.github.io/cs3apis/#cs3.identity.user.v1beta1.UserType)
+* Bad, because we have to provision and manage service accounts on init
+* Bad, because external APIs may need to filter out service accounts
+* Bad, because we need to persist service accounts in addition to normal user accounts
+
+## Pros and Cons of the Options
+
+### Service Accounts
+
+Make the reva auth manager and registry aware of CS3 users of type [`USER_TYPE_SERVICE`](https://cs3org.github.io/cs3apis/#cs3.identity.user.v1beta1.UserType). Then we can provision service accounts at oCIS initialization and use the permissions service to check permissions.
+When assigning permissions we use the permission constraints to define the scope of permissions, see [Permission Checks](#permission-checks) for more details.
+
+To authenticate service accounts the static reva auth registry needs to be configured with a new auth provider for type `service`. The actual provider can use a plain JSON file or JSONCS3 that is provisioned once with `ocis init`. TODO Furthermore, the user provider needs to be able to return users for service accounts.
+
+
+* Good, because we could replace machine auth with specific service accounts and no longer have to distribute a shared secret everywhere
+* Bad, because we don't know if a there are places in the code that try to look up a user with USER_TYPE_SERVICE at the cs3 users service ... they might not exist there ... or do we have to implement a userregistry, similar to the authregistry?
+* Bad, because we have to provision and manage service accounts on init
+* Bad, because we have to write code to manage service accounts or at least filter them out in the admin ui
+
+
+### Impersonate Space-Owners 
+
+We could implement a new auth manager that can authenticate space owners, a CS3 user type we introduced for project spaces which 'have no owner', only one or more managers.
+
+* Good, because it reuses the space owner user type
+* Bad, because the space owner always has write permisson
+* Bad, because we don't know if a there are places in the code that try to look up a user with USER_TYPE_SPACE_OWNER at the cs3 users service ... they might not exist there ... or do we have to implement a userregistry, similar to the authregistry?
+* Bad, because it feels like another hack and does not protect against compromized services that try to execute operations that the user did not consent to.
+
+## Links
+
+* [MS Graph servicePrincipal](https://learn.microsoft.com/en-us/graph/api/resources/serviceprincipal?view=graph-rest-1.0)
+* [reva auth managers](https://reva.link/docs/config/packages/auth/manager/) - lacks docs for `auth_machine`, to be found [in the code](https://github.com/cs3org/reva/blob/edge/pkg/auth/manager/machine/machine.go)
+
+## Permission checks
+When checking permissions we do not check for global permissions but for the concrete permission. Global permissions describe permissions that are used when assigning permissions, e.g. the index service account has the read permission constrained to tenant. The concrete permission check always contains a resource and a specific permission like `Resource.Read` or `Space.Delete`. That we currently check if a user has the `delete-all-spaces` permission is wrong. It should instead check if the user has the permission `Space.Delete` on a specific space. The permissions service can implement the check by taking the permission constraint into account.
+
+Another example would be a `Resource.Read` check for a specific resource. Normal users like the demo users Einstein and Marie would have the permission `Resource.ReadWrite` with the constraint ALL (which limits them to all files they own and that have been shared with them). The permissions service can return true. Service accounts like the indexer would have  `Resource.Read` with the constraint TENANT and thus be granted read access to all resources.
+
+In the storage drive implementation we can check the ACLs first (which would allow service accounts that are known to the underlying storage system, e.g. EOS to access the resource) and then make a call to the permissions service. At least for the Read Resource permission. Other permission checks can be introduced as needed.
+
+The permission names and constraints are different from the MS Graph API. Giving permission like [`Files.ReadWrite.All`](https://learn.microsoft.com/en-us/graph/permissions-reference#user-permissions) a different meaning, depending on the type of user (for normal users it means all files they have access to, for service accounts it means all files in the organization) is a source of confusion which only gets worse when there are two different UUIDs for this. 
\ No newline at end of file
diff --git a/docs/templates/ADOC.tmpl b/docs/templates/ADOC.tmpl
index 73d54b53f4a..e49ed19d8ab 100644
--- a/docs/templates/ADOC.tmpl
+++ b/docs/templates/ADOC.tmpl
@@ -6,6 +6,7 @@ ifeval::[{show-deprecation} == true]
 
 [[deprecation-note]]
 [caption=]
+.Deprecation notes for the {{ .ExtensionName }} service
 [width="100%",cols="~,~,~,~",options="header"]
 |===
 | Deprecation Info
diff --git a/docs/templates/ADOC_deprecation.tmpl b/docs/templates/ADOC_deprecation.tmpl
new file mode 100644
index 00000000000..fd340a8f0bf
--- /dev/null
+++ b/docs/templates/ADOC_deprecation.tmpl
@@ -0,0 +1,2 @@
+:show-deprecation: {{ .HasDeprecations }}
+
diff --git a/go.mod b/go.mod
index 1bdc9918e80..a7379f1be05 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/blevesearch/bleve/v2 v2.3.5
 	github.com/coreos/go-oidc/v3 v3.4.0
 	github.com/cs3org/go-cs3apis v0.0.0-20221012090518-ef2996678965
-	github.com/cs3org/reva/v2 v2.12.1-0.20230113095557-1a7bb8c77ed0
+	github.com/cs3org/reva/v2 v2.12.1-0.20230126045302-72336e6ececa
 	github.com/disintegration/imaging v1.6.2
 	github.com/ggwhite/go-masker v1.0.9
 	github.com/go-chi/chi/v5 v5.0.7
@@ -55,7 +55,7 @@ require (
 	github.com/onsi/ginkgo/v2 v2.7.0
 	github.com/onsi/gomega v1.24.1
 	github.com/orcaman/concurrent-map v1.0.0
-	github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d
+	github.com/owncloud/libre-graph-api-go v1.0.2-0.20230119095249-5688a1b749dc
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.14.0
 	github.com/rs/zerolog v1.28.0
diff --git a/go.sum b/go.sum
index 9430715e763..36fda795ec5 100644
--- a/go.sum
+++ b/go.sum
@@ -344,8 +344,10 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p
 github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A=
 github.com/crewjam/saml v0.4.9 h1:X2jDv4dv3IvfT9t+RhADavzNFAcq3fVxzTCIH3G605U=
 github.com/crewjam/saml v0.4.9/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4=
-github.com/cs3org/reva/v2 v2.12.1-0.20230113095557-1a7bb8c77ed0 h1:OOFyGTaCOKo3uXFNNtUAQVSNYNDKlZ21EqdDLMHUzQQ=
-github.com/cs3org/reva/v2 v2.12.1-0.20230113095557-1a7bb8c77ed0/go.mod h1:u73Df9JAZsDj43GIjQIb3DO1PLJuPutZXkRqQH0oGXA=
+github.com/cs3org/reva/v2 v2.12.1-0.20230125142028-2d125a5354d0 h1:FCKt9i38oNgs6oQYpdWaIj7KfJykCNer22HwTUi+h3Y=
+github.com/cs3org/reva/v2 v2.12.1-0.20230125142028-2d125a5354d0/go.mod h1:u73Df9JAZsDj43GIjQIb3DO1PLJuPutZXkRqQH0oGXA=
+github.com/cs3org/reva/v2 v2.12.1-0.20230126045302-72336e6ececa h1:adJ1cFJ2QA+ZaloXEsEAd5jO++qFmEUavv8J2TyvSYY=
+github.com/cs3org/reva/v2 v2.12.1-0.20230126045302-72336e6ececa/go.mod h1:u73Df9JAZsDj43GIjQIb3DO1PLJuPutZXkRqQH0oGXA=
 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=
@@ -1058,8 +1060,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk
 github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
 github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
 github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
-github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d h1:aqVf2yJEdSgFQd3k5fnwtYxjTwC/UREAKTZIzeupwHg=
-github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d/go.mod h1:iKdVH6nYpI8RBeK9sjeLfzrPByST6r9d+NG2IJHoJmU=
+github.com/owncloud/libre-graph-api-go v1.0.2-0.20230119095249-5688a1b749dc h1:FS5FC8kLv1RSEOKkZJ0Dxr1QJtv0Q18FaPXSKUsVnAc=
+github.com/owncloud/libre-graph-api-go v1.0.2-0.20230119095249-5688a1b749dc/go.mod h1:iKdVH6nYpI8RBeK9sjeLfzrPByST6r9d+NG2IJHoJmU=
 github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
 github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
diff --git a/services/graph/pkg/identity/backend.go b/services/graph/pkg/identity/backend.go
index 5b8d5f5f444..440909f3b7a 100644
--- a/services/graph/pkg/identity/backend.go
+++ b/services/graph/pkg/identity/backend.go
@@ -70,6 +70,8 @@ type EducationBackend interface {
 	DeleteEducationClass(ctx context.Context, nameOrID string) error
 	// GetEducationClassMembers returns the EducationUser members for an EducationClass
 	GetEducationClassMembers(ctx context.Context, nameOrID string) ([]*libregraph.EducationUser, error)
+	// UpdateEducationClass updates properties of the supplied class in the identity backend.
+	UpdateEducationClass(ctx context.Context, id string, class libregraph.EducationClass) (*libregraph.EducationClass, error)
 
 	// CreateEducationUser creates a given education user in the identity backend.
 	CreateEducationUser(ctx context.Context, user libregraph.EducationUser) (*libregraph.EducationUser, error)
diff --git a/services/graph/pkg/identity/cs3.go b/services/graph/pkg/identity/cs3.go
index 6aa8043792c..2a3335d3ce6 100644
--- a/services/graph/pkg/identity/cs3.go
+++ b/services/graph/pkg/identity/cs3.go
@@ -207,10 +207,8 @@ func createGroupModelFromCS3(g *cs3group.Group) *libregraph.Group {
 		g.Id = &cs3group.GroupId{}
 	}
 	return &libregraph.Group{
-		Id:                       &g.Id.OpaqueId,
-		OnPremisesDomainName:     &g.Id.Idp,
-		OnPremisesSamAccountName: &g.GroupName,
-		DisplayName:              &g.DisplayName,
+		Id:          &g.Id.OpaqueId,
+		DisplayName: &g.GroupName,
 		// TODO when to fetch and expand memberof, usernames or ids?
 	}
 }
diff --git a/services/graph/pkg/identity/err_education.go b/services/graph/pkg/identity/err_education.go
index 6e81f7c9c58..b18080d51f2 100644
--- a/services/graph/pkg/identity/err_education.go
+++ b/services/graph/pkg/identity/err_education.go
@@ -75,6 +75,11 @@ func (i *ErrEducationBackend) GetEducationClassMembers(ctx context.Context, name
 	return nil, errNotImplemented
 }
 
+// UpdateEducationClass implments the EducationBackend interface
+func (i *ErrEducationBackend) UpdateEducationClass(ctx context.Context, id string, class libregraph.EducationClass) (*libregraph.EducationClass, error) {
+	return nil, errNotImplemented
+}
+
 // CreateEducationUser creates a given education user in the identity backend.
 func (i *ErrEducationBackend) CreateEducationUser(ctx context.Context, user libregraph.EducationUser) (*libregraph.EducationUser, error) {
 	return nil, errNotImplemented
diff --git a/services/graph/pkg/identity/ldap_education_class.go b/services/graph/pkg/identity/ldap_education_class.go
index 93ba9b13764..f19af096350 100644
--- a/services/graph/pkg/identity/ldap_education_class.go
+++ b/services/graph/pkg/identity/ldap_education_class.go
@@ -2,6 +2,7 @@ package identity
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/url"
 
@@ -139,6 +140,98 @@ func (i *LDAP) DeleteEducationClass(ctx context.Context, id string) error {
 	return nil
 }
 
+// UpdateEducationClass implements the EducationBackend interface for the LDAP backend.
+// Only the displayName and externalID are supported to change at this point.
+func (i *LDAP) UpdateEducationClass(ctx context.Context, id string, class libregraph.EducationClass) (*libregraph.EducationClass, error) {
+	logger := i.logger.SubloggerWithRequestID(ctx)
+	logger.Debug().Str("backend", "ldap").Msg("UpdateEducationClass")
+	if !i.writeEnabled {
+		return nil, ErrReadOnly
+	}
+
+	g, err := i.getLDAPGroupByID(id, false)
+	if err != nil {
+		return nil, err
+	}
+
+	var updateNeeded bool
+
+	if class.GetId() != "" {
+		if g.GetEqualFoldAttributeValue(i.groupAttributeMap.id) != class.GetId() {
+			return nil, errorcode.New(errorcode.NotAllowed, "changing the GroupID is not allowed")
+		}
+	}
+
+	if class.GetDescription() != "" {
+		return nil, errorcode.New(errorcode.NotSupported, "changing the description is currently not supported")
+	}
+
+	if len(class.GetMembers()) != 0 {
+		return nil, errorcode.New(errorcode.NotSupported, "changing the members is currently not supported")
+	}
+
+	if class.GetClassification() != "" {
+		return nil, errorcode.New(errorcode.NotSupported, "changing the classification is currently not supported")
+	}
+
+	dn := g.DN
+
+	if eID := class.GetExternalId(); eID != "" {
+		if g.GetEqualFoldAttributeValue(i.educationConfig.classAttributeMap.externalID) != eID {
+			dn, err = i.updateClassExternalID(ctx, dn, eID)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	mr := ldap.ModifyRequest{DN: dn}
+
+	if dName := class.GetDisplayName(); dName != "" {
+		if g.GetEqualFoldAttributeValue(i.groupAttributeMap.name) != dName {
+			mr.Replace(i.groupAttributeMap.name, []string{dName})
+			updateNeeded = true
+		}
+	}
+
+	if updateNeeded {
+		if err := i.conn.Modify(&mr); err != nil {
+			return nil, err
+		}
+	}
+
+	g, err = i.getEducationClassByDN(dn)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.createEducationClassModelFromLDAP(g), nil
+}
+
+func (i *LDAP) updateClassExternalID(ctx context.Context, dn, externalID string) (string, error) {
+	logger := i.logger.SubloggerWithRequestID(ctx)
+	newDN := fmt.Sprintf("ocEducationExternalId=%s", externalID)
+
+	mrdn := ldap.NewModifyDNRequest(dn, newDN, true, "")
+	i.logger.Debug().Str("Backend", "ldap").
+		Str("dn", mrdn.DN).
+		Str("newrdn", mrdn.NewRDN).
+		Msg("updating class external ID")
+
+	if err := i.conn.ModifyDN(mrdn); err != nil {
+		var lerr *ldap.Error
+		logger.Debug().Err(err).Msg("error updating class external ID")
+		if errors.As(err, &lerr) {
+			if lerr.ResultCode == ldap.LDAPResultEntryAlreadyExists {
+				err = errorcode.New(errorcode.NameAlreadyExists, lerr.Error())
+			}
+		}
+		return "", err
+	}
+
+	return fmt.Sprintf("%s,%s", newDN, i.groupBaseDN), nil
+}
+
 // GetEducationClassMembers implements the EducationBackend interface for the LDAP backend.
 func (i *LDAP) GetEducationClassMembers(ctx context.Context, id string) ([]*libregraph.EducationUser, error) {
 	logger := i.logger.SubloggerWithRequestID(ctx)
diff --git a/services/graph/pkg/identity/ldap_education_school.go b/services/graph/pkg/identity/ldap_education_school.go
index 77e1252ab61..6be681fc05c 100644
--- a/services/graph/pkg/identity/ldap_education_school.go
+++ b/services/graph/pkg/identity/ldap_education_school.go
@@ -348,11 +348,11 @@ func (i *LDAP) GetEducationSchools(ctx context.Context, queryParam url.Values) (
 }
 
 // GetEducationSchoolUsers implements the EducationBackend interface for the LDAP backend.
-func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, id string) ([]*libregraph.EducationUser, error) {
+func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, schoolNumberOrID string) ([]*libregraph.EducationUser, error) {
 	logger := i.logger.SubloggerWithRequestID(ctx)
 	logger.Debug().Str("backend", "ldap").Msg("GetEducationSchoolUsers")
 
-	schoolEntry, err := i.getSchoolByNumberOrID(id)
+	schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID)
 	if err != nil {
 		return nil, err
 	}
@@ -360,8 +360,9 @@ func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, id string) ([]*libre
 	if schoolEntry == nil {
 		return nil, ErrNotFound
 	}
-	id = ldap.EscapeFilter(id)
-	idFilter := fmt.Sprintf("(%s=%s)", i.educationConfig.memberOfSchoolAttribute, id)
+	schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id)
+	schoolID = ldap.EscapeFilter(schoolID)
+	idFilter := fmt.Sprintf("(%s=%s)", i.educationConfig.memberOfSchoolAttribute, schoolID)
 	userFilter := fmt.Sprintf("(&%s(objectClass=%s)%s)", i.userFilter, i.educationConfig.userObjectClass, idFilter)
 
 	searchRequest := ldap.NewSearchRequest(
@@ -398,11 +399,11 @@ func (i *LDAP) GetEducationSchoolUsers(ctx context.Context, id string) ([]*libre
 }
 
 // AddUsersToEducationSchool adds new members (reference by a slice of IDs) to supplied school in the identity backend.
-func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolID string, memberIDs []string) error {
+func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolNumberOrID string, memberIDs []string) error {
 	logger := i.logger.SubloggerWithRequestID(ctx)
 	logger.Debug().Str("backend", "ldap").Msg("AddUsersToEducationSchool")
 
-	schoolEntry, err := i.getSchoolByNumberOrID(schoolID)
+	schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID)
 	if err != nil {
 		return err
 	}
@@ -411,6 +412,8 @@ func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolID string, m
 		return ErrNotFound
 	}
 
+	schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id)
+
 	userEntries := make([]*ldap.Entry, 0, len(memberIDs))
 	for _, memberID := range memberIDs {
 		user, err := i.getEducationUserByNameOrID(memberID)
@@ -443,11 +446,11 @@ func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolID string, m
 }
 
 // RemoveUserFromEducationSchool removes a single member (by ID) from a school
-func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolID string, memberID string) error {
+func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolNumberOrID string, memberID string) error {
 	logger := i.logger.SubloggerWithRequestID(ctx)
 	logger.Debug().Str("backend", "ldap").Msg("RemoveUserFromEducationSchool")
 
-	schoolEntry, err := i.getSchoolByNumberOrID(schoolID)
+	schoolEntry, err := i.getSchoolByNumberOrID(schoolNumberOrID)
 	if err != nil {
 		return err
 	}
@@ -455,6 +458,8 @@ func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolID strin
 	if schoolEntry == nil {
 		return ErrNotFound
 	}
+
+	schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id)
 	user, err := i.getEducationUserByNameOrID(memberID)
 	if err != nil {
 		i.logger.Warn().Str("userid", memberID).Msg("User does not exist")
diff --git a/services/graph/pkg/identity/mocks/education_backend.go b/services/graph/pkg/identity/mocks/education_backend.go
index 2c2b4868b72..cb827811682 100644
--- a/services/graph/pkg/identity/mocks/education_backend.go
+++ b/services/graph/pkg/identity/mocks/education_backend.go
@@ -340,6 +340,29 @@ func (_m *EducationBackend) RemoveUserFromEducationSchool(ctx context.Context, s
 	return r0
 }
 
+// UpdateEducationClass provides a mock function with given fields: ctx, id, class
+func (_m *EducationBackend) UpdateEducationClass(ctx context.Context, id string, class libregraph.EducationClass) (*libregraph.EducationClass, error) {
+	ret := _m.Called(ctx, id, class)
+
+	var r0 *libregraph.EducationClass
+	if rf, ok := ret.Get(0).(func(context.Context, string, libregraph.EducationClass) *libregraph.EducationClass); ok {
+		r0 = rf(ctx, id, class)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*libregraph.EducationClass)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, libregraph.EducationClass) error); ok {
+		r1 = rf(ctx, id, class)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // UpdateEducationSchool provides a mock function with given fields: ctx, numberOrID, school
 func (_m *EducationBackend) UpdateEducationSchool(ctx context.Context, numberOrID string, school libregraph.EducationSchool) (*libregraph.EducationSchool, error) {
 	ret := _m.Called(ctx, numberOrID, school)
diff --git a/services/graph/pkg/service/v0/educationclasses.go b/services/graph/pkg/service/v0/educationclasses.go
index d50a1768eec..330a8ef2700 100644
--- a/services/graph/pkg/service/v0/educationclasses.go
+++ b/services/graph/pkg/service/v0/educationclasses.go
@@ -10,6 +10,8 @@ import (
 	"strings"
 
 	"github.com/CiscoM31/godata"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/events"
 	libregraph "github.com/owncloud/libre-graph-api-go"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
 
@@ -120,6 +122,25 @@ func (g Graph) PatchEducationClass(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	var features []events.GroupFeature
+	if displayName, ok := changes.GetDisplayNameOk(); ok {
+		features = append(features, events.GroupFeature{Name: "displayname", Value: *displayName})
+	}
+
+	if externalID, ok := changes.GetExternalIdOk(); ok {
+		features = append(features, events.GroupFeature{Name: "externalid", Value: *externalID})
+	}
+
+	_, err = g.identityEducationBackend.UpdateEducationClass(r.Context(), classID, *changes)
+	if err != nil {
+		logger.Error().
+			Err(err).
+			Str("classID", classID).
+			Msg("could not update class")
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+		return
+	}
+
 	if memberRefs, ok := changes.GetMembersodataBindOk(); ok {
 		// The spec defines a limit of 20 members maxium per Request
 		if len(memberRefs) > g.config.API.GroupMembersPatchLimit {
@@ -166,6 +187,20 @@ func (g Graph) PatchEducationClass(w http.ResponseWriter, r *http.Request) {
 		}
 		return
 	}
+
+	if len(features) > 0 {
+		e := events.GroupFeatureChanged{
+			GroupID:  classID,
+			Features: features,
+		}
+
+		if currentUser, ok := revactx.ContextGetUser(r.Context()); ok {
+			e.Executant = currentUser.GetId()
+		}
+		g.publishEvent(e)
+
+	}
+
 	render.Status(r, http.StatusNoContent) // TODO StatusNoContent when prefer=minimal is used, otherwise OK and the resource in the body
 	render.NoContent(w, r)
 }
diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go
index 8ce2d93a243..bfc287b4fc4 100644
--- a/services/graph/pkg/service/v0/service.go
+++ b/services/graph/pkg/service/v0/service.go
@@ -270,6 +270,11 @@ func NewService(opts ...Option) (Graph, error) {
 		})
 	})
 
+	_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
+
 	return svc, nil
 }
 
diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go
index c0bddc77752..09ea4512bbf 100644
--- a/services/graph/pkg/service/v0/users.go
+++ b/services/graph/pkg/service/v0/users.go
@@ -188,10 +188,6 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) {
 			errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("%v is not a valid email address", *u.Mail))
 			return
 		}
-	} else {
-		logger.Debug().Interface("user", u).Msg("could not create user: missing required Attribute: 'mail'")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'mail'")
-		return
 	}
 
 	// Disallow user-supplied IDs. It's supposed to be readonly. We're either
@@ -273,7 +269,20 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
 	}
 	sel := strings.Split(r.URL.Query().Get("$select"), ",")
 	exp := strings.Split(r.URL.Query().Get("$expand"), ",")
-	if slices.Contains(sel, "drive") || slices.Contains(sel, "drives") || slices.Contains(exp, "drive") || slices.Contains(exp, "drives") {
+	listDrives := slices.Contains(sel, "drives") || slices.Contains(exp, "drives")
+	listDrive := slices.Contains(sel, "drive") || slices.Contains(exp, "drive")
+
+	// do we need to list all or only the personal drive
+	filters := []*storageprovider.ListStorageSpacesRequest_Filter{}
+	if listDrives || listDrive {
+		filters = append(filters, listStorageSpacesUserFilter(user.GetId()))
+		if !listDrives {
+			// TODO filter by owner when decomposedfs supports the OWNER filter
+			filters = append(filters, listStorageSpacesTypeFilter("personal"))
+		}
+	}
+
+	if len(filters) > 0 {
 		wdu, err := g.getWebDavBaseURL()
 		if err != nil {
 			// log error, wrong configuration
@@ -285,14 +294,13 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
 			render.Status(r, http.StatusInternalServerError)
 			return
 		}
-		logger.Debug().Str("id", user.GetId()).Msg("calling list storage spaces with user id filter")
-		f := listStorageSpacesUserFilter(user.GetId())
+		logger.Debug().Str("id", user.GetId()).Msg("calling list storage spaces with filter")
 		// use the unrestricted flag to get all possible spaces
 		// users with the canListAllSpaces permission should see all spaces
 		opaque := utils.AppendPlainToOpaque(nil, "unrestricted", "T")
 		lspr, err := g.gatewayClient.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{
 			Opaque:  opaque,
-			Filters: []*storageprovider.ListStorageSpacesRequest_Filter{f},
+			Filters: filters,
 		})
 		if err != nil {
 			// transport error, needs to be fixed by admin
@@ -302,13 +310,18 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 		if lspr.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
-			logger.Debug().Str("grpc", lspr.GetStatus().GetMessage()).Msg("could not get drive for user")
+			logger.Debug().Str("grpc", lspr.GetStatus().GetMessage()).Msg("could not list drives for user")
 			// in case of NOT_OK, we can just return the user object with empty drives
 			render.Status(r, status.HTTPStatusFromCode(http.StatusOK))
 			render.JSON(w, r, user)
 			return
 		}
-		drives := []libregraph.Drive{}
+		if listDrives {
+			user.Drives = make([]libregraph.Drive, 0, len(lspr.GetStorageSpaces()))
+		}
+		if listDrive {
+			user.Drive = &libregraph.Drive{}
+		}
 		for _, sp := range lspr.GetStorageSpaces() {
 			d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp)
 			if err != nil {
@@ -320,16 +333,18 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
 				logger.Debug().Err(err).Interface("id", sp.Id).Msg("error calling get quota on drive")
 			}
 			d.Quota = &quota
-			if slices.Contains(sel, "drive") || slices.Contains(exp, "drive") {
-				if *d.DriveType == "personal" {
+			if d.GetDriveType() == "personal" && sp.GetOwner().GetId().GetOpaqueId() == user.GetId() {
+				if listDrive {
 					user.Drive = d
 				}
 			} else {
-				drives = append(drives, *d)
-				user.Drives = drives
+				if listDrives {
+					user.Drives = append(user.Drives, *d)
+				}
 			}
 		}
 	}
+
 	// expand appRoleAssignments if requested
 	if slices.Contains(exp, "appRoleAssignments") {
 		user.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), user.GetId())
diff --git a/services/idp/pkg/service/v0/service.go b/services/idp/pkg/service/v0/service.go
index 82b8d41277c..ca60923eff7 100644
--- a/services/idp/pkg/service/v0/service.go
+++ b/services/idp/pkg/service/v0/service.go
@@ -238,6 +238,11 @@ func (idp *IDP) initMux(ctx context.Context, r []server.WithRoutes, h http.Handl
 	idp.mux.Get("/signin/v1/identifier/index.html", idp.Index())
 
 	idp.mux.Mount("/", gm)
+
+	_ = chi.Walk(idp.mux, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
 }
 
 // ServeHTTP implements the Service interface.
diff --git a/services/ocs/pkg/command/server.go b/services/ocs/pkg/command/server.go
index 6b6ab26a463..7c3b6c989b4 100644
--- a/services/ocs/pkg/command/server.go
+++ b/services/ocs/pkg/command/server.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
-	ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
 	"github.com/owncloud/ocis/v2/ocis-pkg/version"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/config/parser"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/logging"
@@ -34,10 +33,6 @@ func Server(cfg *config.Config) *cli.Command {
 			if err != nil {
 				return err
 			}
-			err = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...)
-			if err != nil {
-				return err
-			}
 
 			var (
 				gr          = run.Group{}
diff --git a/services/ocs/pkg/config/config.go b/services/ocs/pkg/config/config.go
index e4130405447..1de8f60aa3e 100644
--- a/services/ocs/pkg/config/config.go
+++ b/services/ocs/pkg/config/config.go
@@ -19,21 +19,7 @@ type Config struct {
 
 	HTTP HTTP `yaml:"http"`
 
-	TokenManager  *TokenManager         `yaml:"token_manager"`
-	Reva          *shared.Reva          `yaml:"reva"`
-	GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
-
-	IdentityManagement IdentityManagement `yaml:"identity_management"`
-
-	AccountBackend    string `yaml:"-"` // we only support cs3 backend, no need to have this configurable
-	MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;OCS_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."`
+	TokenManager *TokenManager `yaml:"token_manager"`
 
 	Context context.Context `yaml:"-"`
 }
-
-// IdentityManagement keeps track of the OIDC address. This is because Reva requisite of uniqueness for users
-// is based in the combination of IDP hostname + UserID. For more information see:
-// https://github.com/cs3org/reva/blob/4fd0229f13fae5bc9684556a82dbbd0eced65ef9/pkg/storage/utils/decomposedfs/node/node.go#L856-L865
-type IdentityManagement struct {
-	Address string `yaml:"address" env:"OCIS_URL;OCIS_OIDC_ISSUER;OCS_IDM_ADDRESS" desc:"URL of the OIDC issuer. It defaults to URL of the builtin IDP."`
-}
diff --git a/services/ocs/pkg/config/defaults/defaultconfig.go b/services/ocs/pkg/config/defaults/defaultconfig.go
index 30dc68871b0..9b04fb99f8b 100644
--- a/services/ocs/pkg/config/defaults/defaultconfig.go
+++ b/services/ocs/pkg/config/defaults/defaultconfig.go
@@ -3,7 +3,6 @@ package defaults
 import (
 	"strings"
 
-	"github.com/owncloud/ocis/v2/ocis-pkg/shared"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/config"
 )
 
@@ -36,11 +35,6 @@ func DefaultConfig() *config.Config {
 		Service: config.Service{
 			Name: "ocs",
 		},
-		AccountBackend: "cs3",
-		Reva:           shared.DefaultRevaConfig(),
-		IdentityManagement: config.IdentityManagement{
-			Address: "https://localhost:9200",
-		},
 	}
 }
 
@@ -78,15 +72,6 @@ func EnsureDefaults(cfg *config.Config) {
 		cfg.CacheStore = &config.CacheStore{}
 	}
 
-	if cfg.Reva == nil && cfg.Commons != nil && cfg.Commons.Reva != nil {
-		cfg.Reva = &shared.Reva{
-			Address: cfg.Commons.Reva.Address,
-			TLS:     cfg.Commons.Reva.TLS,
-		}
-	} else if cfg.Reva == nil {
-		cfg.Reva = &shared.Reva{}
-	}
-
 	if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil {
 		cfg.TokenManager = &config.TokenManager{
 			JWTSecret: cfg.Commons.TokenManager.JWTSecret,
@@ -95,17 +80,6 @@ func EnsureDefaults(cfg *config.Config) {
 		cfg.TokenManager = &config.TokenManager{}
 	}
 
-	if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" {
-		cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey
-	}
-
-	if cfg.GRPCClientTLS == nil {
-		cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
-		if cfg.Commons != nil && cfg.Commons.GRPCClientTLS != nil {
-			cfg.GRPCClientTLS.Mode = cfg.Commons.GRPCClientTLS.Mode
-			cfg.GRPCClientTLS.CACert = cfg.Commons.GRPCClientTLS.CACert
-		}
-	}
 	if cfg.Commons != nil {
 		cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS
 	}
diff --git a/services/ocs/pkg/config/parser/parse.go b/services/ocs/pkg/config/parser/parse.go
index ecb6b0e184b..bff72f8e7da 100644
--- a/services/ocs/pkg/config/parser/parse.go
+++ b/services/ocs/pkg/config/parser/parse.go
@@ -39,9 +39,5 @@ func Validate(cfg *config.Config) error {
 		return shared.MissingJWTTokenError(cfg.Service.Name)
 	}
 
-	if cfg.MachineAuthAPIKey == "" {
-		return shared.MissingMachineAuthApiKeyError(cfg.Service.Name)
-	}
-
 	return nil
 }
diff --git a/services/ocs/pkg/middleware/options.go b/services/ocs/pkg/middleware/options.go
deleted file mode 100644
index c3f58e51f3a..00000000000
--- a/services/ocs/pkg/middleware/options.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package middleware
-
-import (
-	"github.com/owncloud/ocis/v2/ocis-pkg/log"
-	"github.com/owncloud/ocis/v2/ocis-pkg/roles"
-)
-
-// Option defines a single option function.
-type Option func(o *Options)
-
-// Options defines the available options for this package.
-type Options struct {
-	// Logger to use for logging, must be set
-	Logger log.Logger
-	// RoleManager for looking up permissions
-	RoleManager *roles.Manager
-}
-
-// newOptions initializes the available default options.
-func newOptions(opts ...Option) Options {
-	opt := Options{}
-
-	for _, o := range opts {
-		o(&opt)
-	}
-
-	return opt
-}
-
-// Logger provides a function to set the logger option.
-func Logger(l log.Logger) Option {
-	return func(o *Options) {
-		o.Logger = l
-	}
-}
-
-// RoleManager provides a function to set the RoleManager option.
-func RoleManager(val *roles.Manager) Option {
-	return func(o *Options) {
-		o.RoleManager = val
-	}
-}
diff --git a/services/ocs/pkg/middleware/requireadmin.go b/services/ocs/pkg/middleware/requireadmin.go
deleted file mode 100644
index 87f58eebe09..00000000000
--- a/services/ocs/pkg/middleware/requireadmin.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package middleware
-
-import (
-	"net/http"
-
-	"github.com/go-chi/render"
-	"github.com/owncloud/ocis/v2/ocis-pkg/roles"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-	settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0"
-)
-
-// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions
-func RequireAdmin(opts ...Option) func(next http.Handler) http.Handler {
-	opt := newOptions(opts...)
-
-	mustRender := func(w http.ResponseWriter, r *http.Request, renderer render.Renderer) {
-		if err := render.Render(w, r, renderer); err != nil {
-			opt.Logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL)
-		}
-	}
-
-	return func(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
-			// get roles from context
-			roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context())
-			if !ok {
-				mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
-				return
-			}
-
-			// check if permission is present in roles of the authenticated account
-			if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil {
-				next.ServeHTTP(w, r)
-				return
-			}
-
-			mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
-		})
-	}
-}
diff --git a/services/ocs/pkg/middleware/requireselforadmin.go b/services/ocs/pkg/middleware/requireselforadmin.go
deleted file mode 100644
index f88863db5e5..00000000000
--- a/services/ocs/pkg/middleware/requireselforadmin.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package middleware
-
-import (
-	"net/http"
-	"net/url"
-
-	revactx "github.com/cs3org/reva/v2/pkg/ctx"
-	"github.com/go-chi/chi/v5"
-	"github.com/go-chi/render"
-	"github.com/owncloud/ocis/v2/ocis-pkg/roles"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-	settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0"
-)
-
-// RequireSelfOrAdmin middleware is used to require the requesting user to be an admin or the requested user himself
-func RequireSelfOrAdmin(opts ...Option) func(next http.Handler) http.Handler {
-	opt := newOptions(opts...)
-
-	mustRender := func(w http.ResponseWriter, r *http.Request, renderer render.Renderer) {
-		if err := render.Render(w, r, renderer); err != nil {
-			opt.Logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL)
-		}
-	}
-
-	return func(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
-			u, ok := revactx.ContextGetUser(r.Context())
-			if !ok {
-				mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
-				return
-			}
-			if u.Id == nil || u.Id.OpaqueId == "" {
-				mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id"))
-				return
-			}
-			// get roles from context
-			roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context())
-			if !ok {
-				opt.Logger.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service")
-				var err error
-				roleIDs, err = opt.RoleManager.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId)
-				if err != nil {
-					opt.Logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user")
-					mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
-					return
-				}
-				if len(roleIDs) == 0 {
-					roleIDs = append(roleIDs, settings.BundleUUIDRoleUser, settings.SelfManagementPermissionID)
-					// if roles are empty, assume we haven't seen the user before and assign a default user role. At least until
-					// proper roles are provided. See https://github.com/owncloud/ocis/v2/issues/1825 for more context.
-					//return user, nil
-				}
-			}
-
-			// check if account management permission is present in roles of the authenticated account
-			if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil {
-				next.ServeHTTP(w, r)
-				return
-			}
-
-			// check if self management permission is present in roles of the authenticated account
-			if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, settings.SelfManagementPermissionID) != nil {
-				userid := chi.URLParam(r, "userid")
-				var err error
-				if userid, err = url.PathUnescape(userid); err != nil {
-					mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "malformed username"))
-				}
-
-				if userid == "" || userid == u.Id.OpaqueId || userid == u.Username {
-					next.ServeHTTP(w, r)
-					return
-				}
-			}
-
-			mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
-
-		})
-	}
-}
diff --git a/services/ocs/pkg/middleware/requireuser.go b/services/ocs/pkg/middleware/requireuser.go
deleted file mode 100644
index 2b17f178b04..00000000000
--- a/services/ocs/pkg/middleware/requireuser.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package middleware
-
-import (
-	"net/http"
-
-	revactx "github.com/cs3org/reva/v2/pkg/ctx"
-	"github.com/go-chi/render"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-)
-
-// RequireUser middleware is used to require a user in context
-func RequireUser(opts ...Option) func(next http.Handler) http.Handler {
-	opt := newOptions(opts...)
-
-	mustRender := func(w http.ResponseWriter, r *http.Request, renderer render.Renderer) {
-		if err := render.Render(w, r, renderer); err != nil {
-			opt.Logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL)
-		}
-	}
-
-	return func(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
-			u, ok := revactx.ContextGetUser(r.Context())
-			if !ok {
-				mustRender(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
-				return
-			}
-			if u.Id == nil || u.Id.OpaqueId == "" {
-				mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id"))
-				return
-			}
-
-			next.ServeHTTP(w, r)
-
-		})
-	}
-}
diff --git a/services/ocs/pkg/service/v0/config.go b/services/ocs/pkg/service/v0/config.go
deleted file mode 100644
index 9ff28bad191..00000000000
--- a/services/ocs/pkg/service/v0/config.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package svc
-
-import (
-	"net/http"
-
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-)
-
-// GetConfig renders the ocs config endpoint
-func (o Ocs) GetConfig(w http.ResponseWriter, r *http.Request) {
-	o.mustRender(w, r, response.DataRender(&data.ConfigData{
-		Version: "1.7",  // TODO get from env
-		Website: "ocis", // TODO get from env
-		Host:    "",     // TODO get from FRONTEND config
-		Contact: "",     // TODO get from env
-		SSL:     "true", // TODO get from env
-	}))
-}
diff --git a/services/ocs/pkg/service/v0/data/config.go b/services/ocs/pkg/service/v0/data/config.go
deleted file mode 100644
index 0978b73c5f5..00000000000
--- a/services/ocs/pkg/service/v0/data/config.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package data
-
-// ConfigData holds basic config
-type ConfigData struct {
-	Version string `json:"version" xml:"version"`
-	Website string `json:"website" xml:"website"`
-	Host    string `json:"host" xml:"host"`
-	Contact string `json:"contact" xml:"contact"`
-	SSL     string `json:"ssl" xml:"ssl"`
-}
diff --git a/services/ocs/pkg/service/v0/data/group.go b/services/ocs/pkg/service/v0/data/group.go
deleted file mode 100644
index 1eab5d8bd39..00000000000
--- a/services/ocs/pkg/service/v0/data/group.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package data
-
-// Groups holds group ids for the groups listing
-type Groups struct {
-	Groups []string `json:"groups" xml:"groups>element"`
-}
diff --git a/services/ocs/pkg/service/v0/data/user.go b/services/ocs/pkg/service/v0/data/user.go
index 8b210afb570..fbb3e0d8d52 100644
--- a/services/ocs/pkg/service/v0/data/user.go
+++ b/services/ocs/pkg/service/v0/data/user.go
@@ -1,31 +1,5 @@
 package data
 
-// Users holds user ids for the user listing
-type Users struct {
-	Users []string `json:"users" xml:"users>element"`
-}
-
-// User holds the payload for a GetUser response
-type User struct {
-	Enabled           string `json:"enabled" xml:"enabled"`
-	UserID            string `json:"id" xml:"id"` // UserID is mapped to the preferred_name attribute in accounts
-	DisplayName       string `json:"display-name" xml:"display-name"`
-	LegacyDisplayName string `json:"displayname" xml:"displayname"`
-	Email             string `json:"email" xml:"email"`
-	Quota             *Quota `json:"quota" xml:"quota"`
-	UIDNumber         int64  `json:"uidnumber" xml:"uidnumber"`
-	GIDNumber         int64  `json:"gidnumber" xml:"gidnumber"`
-}
-
-// Quota holds quota information
-type Quota struct {
-	Free       int64   `json:"free" xml:"free"`
-	Used       int64   `json:"used" xml:"used"`
-	Total      int64   `json:"total" xml:"total"`
-	Relative   float32 `json:"relative" xml:"relative"`
-	Definition string  `json:"definition" xml:"definition"`
-}
-
 // SigningKey holds the Payload for a GetSigningKey response
 type SigningKey struct {
 	User       string `json:"user" xml:"user"`
diff --git a/services/ocs/pkg/service/v0/groups.go b/services/ocs/pkg/service/v0/groups.go
deleted file mode 100644
index 0f136422610..00000000000
--- a/services/ocs/pkg/service/v0/groups.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package svc
-
-import (
-	"net/http"
-
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
-	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-)
-
-const (
-	_backendCS3 = "cs3"
-)
-
-// ListUserGroups lists a users groups
-func (o Ocs) ListUserGroups(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		// TODO
-		o.mustRender(w, r, response.DataRender(&data.Groups{}))
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// AddToGroup adds a user to a group
-func (o Ocs) AddToGroup(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		// TODO
-		o.cs3WriteNotSupported(w, r)
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// RemoveFromGroup removes a user from a group
-func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		// TODO
-		o.cs3WriteNotSupported(w, r)
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// ListGroups lists all groups
-func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		// TODO
-		o.mustRender(w, r, response.DataRender(&data.Groups{}))
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// AddGroup adds a group
-// oC10 implementation: https://github.com/owncloud/core/blob/762780a23c9eadda4fb5fa8db99eba66a5100b6e/apps/provisioning_api/lib/Groups.php#L126-L154
-func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		o.cs3WriteNotSupported(w, r)
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// DeleteGroup deletes a group
-func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		o.cs3WriteNotSupported(w, r)
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// GetGroupMembers lists all members of a group
-func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case _backendCS3:
-		// TODO
-		o.mustRender(w, r, response.DataRender(&data.Users{}))
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
diff --git a/services/ocs/pkg/service/v0/instrument.go b/services/ocs/pkg/service/v0/instrument.go
index 15c42b348f9..8c48325201e 100644
--- a/services/ocs/pkg/service/v0/instrument.go
+++ b/services/ocs/pkg/service/v0/instrument.go
@@ -23,8 +23,3 @@ type instrument struct {
 func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	i.next.ServeHTTP(w, r)
 }
-
-// GetConfig implements the Service interface.
-func (i instrument) GetConfig(w http.ResponseWriter, r *http.Request) {
-	i.next.GetConfig(w, r)
-}
diff --git a/services/ocs/pkg/service/v0/logging.go b/services/ocs/pkg/service/v0/logging.go
index bb22e46fccd..c4e7c8382eb 100644
--- a/services/ocs/pkg/service/v0/logging.go
+++ b/services/ocs/pkg/service/v0/logging.go
@@ -23,8 +23,3 @@ type logging struct {
 func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	l.next.ServeHTTP(w, r)
 }
-
-// GetConfig implements the Service interface.
-func (l logging) GetConfig(w http.ResponseWriter, r *http.Request) {
-	l.next.GetConfig(w, r)
-}
diff --git a/services/ocs/pkg/service/v0/option.go b/services/ocs/pkg/service/v0/option.go
index bfefbe61f09..40ccc9e1c76 100644
--- a/services/ocs/pkg/service/v0/option.go
+++ b/services/ocs/pkg/service/v0/option.go
@@ -4,8 +4,6 @@ import (
 	"net/http"
 
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
-	"github.com/owncloud/ocis/v2/ocis-pkg/roles"
-	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/config"
 )
 
@@ -14,11 +12,9 @@ type Option func(o *Options)
 
 // Options defines the available options for this package.
 type Options struct {
-	Logger      log.Logger
-	Config      *config.Config
-	Middleware  []func(http.Handler) http.Handler
-	RoleService settingssvc.RoleService
-	RoleManager *roles.Manager
+	Logger     log.Logger
+	Config     *config.Config
+	Middleware []func(http.Handler) http.Handler
 }
 
 // newOptions initializes the available default options.
@@ -52,17 +48,3 @@ func Middleware(val ...func(http.Handler) http.Handler) Option {
 		o.Middleware = val
 	}
 }
-
-// RoleService provides a function to set the RoleService option.
-func RoleService(val settingssvc.RoleService) Option {
-	return func(o *Options) {
-		o.RoleService = val
-	}
-}
-
-// RoleManager provides a function to set the RoleManager option.
-func RoleManager(val *roles.Manager) Option {
-	return func(o *Options) {
-		o.RoleManager = val
-	}
-}
diff --git a/services/ocs/pkg/service/v0/service.go b/services/ocs/pkg/service/v0/service.go
index 60b7e2b08b3..5e333415a9f 100644
--- a/services/ocs/pkg/service/v0/service.go
+++ b/services/ocs/pkg/service/v0/service.go
@@ -3,10 +3,6 @@ package svc
 import (
 	"net/http"
 
-	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
-	"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
-	"github.com/owncloud/ocis/v2/ocis-pkg/store"
-
 	"github.com/go-chi/chi/v5"
 	"github.com/go-chi/chi/v5/middleware"
 	"github.com/go-chi/render"
@@ -14,19 +10,15 @@ import (
 	"github.com/owncloud/ocis/v2/ocis-pkg/account"
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	opkgm "github.com/owncloud/ocis/v2/ocis-pkg/middleware"
-	"github.com/owncloud/ocis/v2/ocis-pkg/roles"
-	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/config"
 	ocsm "github.com/owncloud/ocis/v2/services/ocs/pkg/middleware"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-	"github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend"
 )
 
 // Service defines the service handlers.
 type Service interface {
 	ServeHTTP(http.ResponseWriter, *http.Request)
-	GetConfig(http.ResponseWriter, *http.Request)
 }
 
 // NewService returns a service implementation for Service.
@@ -36,40 +28,12 @@ func NewService(opts ...Option) Service {
 	m := chi.NewMux()
 	m.Use(options.Middleware...)
 
-	roleService := options.RoleService
-	if roleService == nil {
-		roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient())
-	}
-	roleManager := options.RoleManager
-	if roleManager == nil {
-		storeOptions := store.OcisStoreOptions{
-			Type:    options.Config.CacheStore.Type,
-			Address: options.Config.CacheStore.Address,
-			Size:    options.Config.CacheStore.Size,
-		}
-		m := roles.NewManager(
-			roles.StoreOptions(storeOptions),
-			roles.Logger(options.Logger),
-			roles.RoleService(roleService),
-		)
-		roleManager = &m
-	}
-
 	svc := Ocs{
-		config:      options.Config,
-		mux:         m,
-		RoleManager: roleManager,
-		logger:      options.Logger,
-	}
-
-	if svc.config.AccountBackend == "" {
-		svc.config.AccountBackend = "cs3"
+		config: options.Config,
+		mux:    m,
+		logger: options.Logger,
 	}
 
-	requireUser := ocsm.RequireUser(
-		ocsm.Logger(options.Logger),
-	)
-
 	m.Route(options.Config.HTTP.Root, func(r chi.Router) {
 		r.NotFound(svc.NotFound)
 		r.Use(middleware.StripSlashes)
@@ -80,31 +44,23 @@ func NewService(opts ...Option) Service {
 		r.Use(ocsm.OCSFormatCtx) // updates request Accept header according to format=(json|xml) query parameter
 		r.Route("/v{version:(1|2)}.php", func(r chi.Router) {
 			r.Use(response.VersionCtx) // stores version in context
-			r.Route("/apps/files_sharing/api/v1", func(r chi.Router) {})
-			r.Route("/apps/notifications/api/v1", func(r chi.Router) {})
-			r.Route("/cloud", func(r chi.Router) {
-				r.Route("/capabilities", func(r chi.Router) {})
-				// TODO /apps
-				r.Route("/user", func(r chi.Router) {
-					r.Get("/signing-key", svc.GetSigningKey)
-				})
-			})
-			r.Route("/config", func(r chi.Router) {
-				r.With(requireUser).Get("/", svc.GetConfig)
-			})
+			r.Get("/cloud/user/signing-key", svc.GetSigningKey)
 		})
 	})
 
+	_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
+
 	return svc
 }
 
 // Ocs defines implements the business logic for Service.
 type Ocs struct {
-	config      *config.Config
-	logger      log.Logger
-	RoleService settingssvc.RoleService
-	RoleManager *roles.Manager
-	mux         *chi.Mux
+	config *config.Config
+	logger log.Logger
+	mux    *chi.Mux
 }
 
 // ServeHTTP implements the Service interface.
@@ -117,19 +73,6 @@ func (o Ocs) NotFound(w http.ResponseWriter, r *http.Request) {
 	o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "not found"))
 }
 
-func (o Ocs) getCS3Backend() backend.UserBackend {
-	revaClient, err := pool.GetGatewayServiceClient(o.config.Reva.Address, o.config.Reva.GetRevaOptions()...)
-	if err != nil {
-		o.logger.Fatal().Msgf("could not get reva client at address %s", o.config.Reva.Address)
-	}
-	return backend.NewCS3UserBackend(nil, revaClient, o.config.MachineAuthAPIKey, "", nil, o.logger)
-}
-
-// NotImplementedStub returns a not implemented error
-func (o Ocs) NotImplementedStub(w http.ResponseWriter, r *http.Request) {
-	o.mustRender(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "Not implemented"))
-}
-
 func (o Ocs) mustRender(w http.ResponseWriter, r *http.Request, renderer render.Renderer) {
 	if err := render.Render(w, r, renderer); err != nil {
 		o.logger.Err(err).Msgf("failed to write response for ocs request %s on %s", r.Method, r.URL)
diff --git a/services/ocs/pkg/service/v0/tracing.go b/services/ocs/pkg/service/v0/tracing.go
index 6c807d772db..4a2c04f51fa 100644
--- a/services/ocs/pkg/service/v0/tracing.go
+++ b/services/ocs/pkg/service/v0/tracing.go
@@ -21,8 +21,3 @@ type tracing struct {
 func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	middleware.TraceContext(t.next).ServeHTTP(w, r)
 }
-
-// GetConfig implements the Service interface.
-func (t tracing) GetConfig(w http.ResponseWriter, r *http.Request) {
-	t.next.GetConfig(w, r)
-}
diff --git a/services/ocs/pkg/service/v0/users.go b/services/ocs/pkg/service/v0/users.go
index df788d91034..f0bb5c742d3 100644
--- a/services/ocs/pkg/service/v0/users.go
+++ b/services/ocs/pkg/service/v0/users.go
@@ -1,155 +1,20 @@
 package svc
 
 import (
-	"context"
 	"crypto/rand"
 	"encoding/hex"
 	"net/http"
-	"net/url"
 
 	storemsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/store/v0"
 	storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0"
 
-	cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
 	revactx "github.com/cs3org/reva/v2/pkg/ctx"
-	"github.com/go-chi/chi/v5"
 	"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/data"
 	"github.com/owncloud/ocis/v2/services/ocs/pkg/service/v0/response"
-	ocstracing "github.com/owncloud/ocis/v2/services/ocs/pkg/tracing"
 	merrors "go-micro.dev/v4/errors"
 )
 
-// GetSelf returns the currently logged in user
-func (o Ocs) GetSelf(w http.ResponseWriter, r *http.Request) {
-	u, ok := revactx.ContextGetUser(r.Context())
-	if !ok || u.Id == nil || u.Id.OpaqueId == "" {
-		o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id"))
-		return
-	}
-	d := &data.User{
-		UserID:            u.Username,
-		DisplayName:       u.DisplayName,
-		LegacyDisplayName: u.DisplayName,
-		Email:             u.Mail,
-		UIDNumber:         u.UidNumber,
-		GIDNumber:         u.GidNumber,
-	}
-	o.mustRender(w, r, response.DataRender(d))
-}
-
-// GetUser returns the user with the given userid
-func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
-	userid := chi.URLParam(r, "userid")
-	userid, err := url.PathUnescape(userid)
-	if err != nil {
-		o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
-	}
-
-	var user *cs3.User
-	switch {
-	case userid == "":
-		o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context"))
-	case o.config.AccountBackend == "cs3":
-		user, err = o.fetchAccountFromCS3Backend(r.Context(), userid)
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-
-	if err != nil {
-		merr := merrors.FromError(err)
-		if merr.Code == http.StatusNotFound {
-			o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageUserNotFound))
-		} else {
-			o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
-		}
-		o.logger.Error().Err(merr).Str("userid", userid).Msg("could not get account for user")
-		return
-	}
-
-	o.logger.Debug().Interface("user", user).Msg("got user")
-
-	d := &data.User{
-		UserID:            user.Username,
-		DisplayName:       user.DisplayName,
-		LegacyDisplayName: user.DisplayName,
-		Email:             user.Mail,
-		UIDNumber:         user.UidNumber,
-		GIDNumber:         user.GidNumber,
-		Enabled:           "true", // TODO include in response only when admin?
-		// TODO query storage registry for free space? of home storage, maybe...
-		Quota: &data.Quota{
-			Free:       2840756224000,
-			Used:       5059416668,
-			Total:      2845815640668,
-			Relative:   0.18,
-			Definition: "default",
-		},
-	}
-
-	_, span := ocstracing.TraceProvider.
-		Tracer("ocs").
-		Start(r.Context(), "GetUser")
-	defer span.End()
-
-	o.mustRender(w, r, response.DataRender(d))
-}
-
-// AddUser creates a new user account
-func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case "cs3":
-		o.cs3WriteNotSupported(w, r)
-		return
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// EditUser creates a new user account
-func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case "cs3":
-		o.cs3WriteNotSupported(w, r)
-		return
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// DeleteUser deletes a user
-func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case "cs3":
-		o.cs3WriteNotSupported(w, r)
-		return
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// EnableUser enables a user
-func (o Ocs) EnableUser(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case "cs3":
-		o.cs3WriteNotSupported(w, r)
-		return
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-// DisableUser disables a user
-func (o Ocs) DisableUser(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case "cs3":
-		o.cs3WriteNotSupported(w, r)
-		return
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
 // GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist
 // The signing key is part of the user settings and is used by the proxy to authenticate requests
 // Currently, the username is used as the OC-Credential
@@ -222,29 +87,3 @@ func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) {
 		SigningKey: signingKey,
 	}))
 }
-
-// ListUsers lists the users
-func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) {
-	switch o.config.AccountBackend {
-	case "cs3":
-		// TODO
-		o.cs3WriteNotSupported(w, r)
-		return
-	default:
-		o.logger.Fatal().Msgf("Invalid accounts backend type '%s'", o.config.AccountBackend)
-	}
-}
-
-func (o Ocs) fetchAccountFromCS3Backend(ctx context.Context, name string) (*cs3.User, error) {
-	backend := o.getCS3Backend()
-	u, _, err := backend.GetUserByClaims(ctx, "username", name, false)
-	if err != nil {
-		return nil, err
-	}
-	return u, nil
-}
-
-func (o Ocs) cs3WriteNotSupported(w http.ResponseWriter, r *http.Request) {
-	o.logger.Warn().Msg("the CS3 backend does not support adding or updating users")
-	o.NotImplementedStub(w, r)
-}
diff --git a/services/postprocessing/README.md b/services/postprocessing/README.md
index 102dee52bae..2e8329cfb0b 100644
--- a/services/postprocessing/README.md
+++ b/services/postprocessing/README.md
@@ -20,12 +20,25 @@ When postprocessing has been enabled, configuring any postprocessing step will r
 
 ## Postprocessing Steps
 
-As of now, `ocis` allows two different postprocessing steps to be enabled via an environment variable.
+The postporcessing service is individually configurable. This is achieved by allowing a list of postprocessing steps that are processed in order of their appearance in the `POSTPROCESSING_STEPS` envvar. This envvar expects a comma separated list of steps that will be executed. Currently known steps to the system are `virusscan` and `delay`. Custom steps can be added but need an existing target for processing.
 
 ### Virus Scanning
 
-To enable virus scanning as a postprocessing step after uploading a file, the environment variable  `POSTPROCESSING_VIRUSSCAN` needs to be set to ` true`. As a result, each uploaded file gets virus scanned as part of the postprocessing steps. Note that the `antivirus` service is required to be enabled and configured for this to work.
+To enable virus scanning as a postprocessing step after uploading a file, the environment variable `POSTPROCESSING_STEPS` needs to contain the word `virusscan` at one location in the list of steps. As a result, each uploaded file gets virus scanned as part of the postprocessing steps. Note that the `antivirus` service is required to be enabled and configured for this to work.
 
 ### Delay
 
-Though this is for development purposes only and NOT RECOMMENDED on production systems, setting the environment variable `POSTPROCESSING_DELAY` to a duration not equal to zero will add a delay step with the configured amount of time. ocis will continue postprocessing the file after the configured delay.
+Though this is for development purposes only and NOT RECOMMENDED on production systems, setting the environment variable `POSTPROCESSING_DELAY` to a duration not equal to zero will add a delay step with the configured amount of time. ocis will continue postprocessing the file after the configured delay. Use the enviroment variable `POSTPROCESSING_STEPS` and the keyword `delay` if you have multiple postprocessing steps and want to define their order. If `POSTPROCESSING_DELAY` is set but the keyword `delay` is not contained in `POSTPROCESSING_STEPS`, it will be processed as last postprocessing step without being listed there. In this case, a log entry will be written on service startup to notify the admin about that situation. That log entry can be avoided by adding the keyword `delay` to `POSTPROCESSING_STEPS`.
+
+### Custom Postprocessing Steps
+By using the envvar `POSTPROCESSING_STEPS`, custom postprocessing steps can be added. Any word can be used as step name but be careful not to conflict with exising keywords like `virusscan` and `delay`. In addition, if a keyword is misspelled or the corresponding service does either not exist or does not follow the necessary event communication, the postprocessing service will wait forever getting the required response to proceed and does not continue any other processing.
+
+#### Prerequisites
+For using custom postprocessing steps you need a custom service listening to the configured event system (see `General Prerequisites`)
+
+#### Workflow
+When setting a custom postprocessing step (eg. `"customstep"`) the postprocessing service will eventually sent an event during postprocessing. The event will be of type `StartPostprocessingStep` with its field `StepToStart` set to `"customstep"`. When the custom service receives this event it can savely execute its actions, postprocessing service will wait until it has finished its work. The event contains further information (filename, executing user, size, ...) and also required tokens and urls to download the file in case byte inspection is necessary.
+
+Once the custom service has finished its work, it should sent an event of type `PostprocessingFinished` via the configured events system. This event needs to contain a `FinishedStep` field set to `"customstep"`. It also must contain the outcome of the step, which can be one of "delete" (abort postprocessing, delete the file), "abort" (abort postprocessing, keep the file) and "continue" (continue postprocessing, this is the success case).
+
+See https://github.com/cs3org/reva/blob/edge/pkg/events/postprocessing.go for up-to-date information of reserved step names and event definitons.
diff --git a/services/postprocessing/pkg/config/config.go b/services/postprocessing/pkg/config/config.go
index c7bb9d69d11..b1fac6a2ea5 100644
--- a/services/postprocessing/pkg/config/config.go
+++ b/services/postprocessing/pkg/config/config.go
@@ -20,11 +20,12 @@ type Config struct {
 	Context context.Context `yaml:"-"`
 }
 
-// Postprocessing definces the config options for the postprocessing service.
+// Postprocessing defines the config options for the postprocessing service.
 type Postprocessing struct {
 	Events          Events        `yaml:"events"`
-	Virusscan       bool          `yaml:"virusscan" env:"POSTPROCESSING_VIRUSSCAN" desc:"After uploading a file but before making it available for download, virus scanning the file can be enabled. Needs as prerequisite the antivirus service to be enabled and configured."`
-	Delayprocessing time.Duration `yaml:"delayprocessing" env:"POSTPROCESSING_DELAY" desc:"After uploading a file but before making it available for download, a delay step can be added. Intended for developing purposes only. The duration can be set as number followed by a unit identifier like s, m or h."`
+	Steps           []string      `yaml:"steps" env:"POSTPROCESSING_STEPS" desc:"A comma separated list of postprocessing steps, processed in order of their appearance. Currently supported values by the system are: 'virusscan' and 'delay'. Custom steps are allowed. See the documentation for instructions."`
+	Virusscan       bool          `yaml:"virusscan" env:"POSTPROCESSING_VIRUSSCAN" desc:"After uploading a file but before making it available for download, virus scanning the file can be enabled. Needs as prerequisite the antivirus service to be enabled and configured." deprecationVersion:"master" removalVersion:"master" deprecationInfo:"POSTPROCESSING_VIRUSSCAN is not longer necessary and is replaced by POSTPROCESSING_STEPS which also holds information about the order of steps" deprecationReplacement:"POSTPROCESSING_STEPS"`
+	Delayprocessing time.Duration `yaml:"delayprocessing" env:"POSTPROCESSING_DELAY" desc:"After uploading a file but before making it available for download, a delay step can be added. Intended for developing purposes only. The duration can be set as number followed by a unit identifier like s, m or h. If a duration is set but the keyword 'delay' is not explicitely added to 'POSTPROCESSING_STEPS', the delay step will be processed as last step. In such a case, a log entry will be written on service startup to remind the admin about that situation."`
 }
 
 // Events combines the configuration options for the event bus.
diff --git a/services/postprocessing/pkg/config/parser/parse.go b/services/postprocessing/pkg/config/parser/parse.go
index 03441f9415f..8dce4f46222 100644
--- a/services/postprocessing/pkg/config/parser/parse.go
+++ b/services/postprocessing/pkg/config/parser/parse.go
@@ -2,7 +2,10 @@ package parser
 
 import (
 	"errors"
+	"fmt"
+	"strings"
 
+	"github.com/cs3org/reva/v2/pkg/events"
 	ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
 	"github.com/owncloud/ocis/v2/services/postprocessing/pkg/config"
 	"github.com/owncloud/ocis/v2/services/postprocessing/pkg/config/defaults"
@@ -32,6 +35,33 @@ func ParseConfig(cfg *config.Config) error {
 	return Validate(cfg)
 }
 
+// Validate validates the config
 func Validate(cfg *config.Config) error {
+	if cfg.Postprocessing.Virusscan {
+		if !contains(cfg.Postprocessing.Steps, events.PPStepAntivirus) {
+			cfg.Postprocessing.Steps = append(cfg.Postprocessing.Steps, string(events.PPStepAntivirus))
+			fmt.Printf("ATTENTION: POSTPROCESSING_VIRUSSCAN is deprecated. Use `POSTPROCESSING_STEPS=%v` in the future\n", strings.Join(cfg.Postprocessing.Steps, ","))
+		}
+	}
+
+	if cfg.Postprocessing.Delayprocessing != 0 {
+		if !contains(cfg.Postprocessing.Steps, events.PPStepDelay) {
+			if len(cfg.Postprocessing.Steps) > 0 {
+				s := strings.Join(append(cfg.Postprocessing.Steps, string(events.PPStepDelay)), ",")
+				fmt.Printf("Added delay step to the list of postprocessing steps. NOTE: Use envvar `POSTPROCESSING_STEPS=%s` to suppress this message and choose the order of postprocessing steps.\n", s)
+			}
+
+			cfg.Postprocessing.Steps = append(cfg.Postprocessing.Steps, string(events.PPStepDelay))
+		}
+	}
 	return nil
 }
+
+func contains(all []string, candidate events.Postprocessingstep) bool {
+	for _, s := range all {
+		if s == string(candidate) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/services/postprocessing/pkg/postprocessing/postprocessing.go b/services/postprocessing/pkg/postprocessing/postprocessing.go
index 7875285a9dc..297cdc978dc 100644
--- a/services/postprocessing/pkg/postprocessing/postprocessing.go
+++ b/services/postprocessing/pkg/postprocessing/postprocessing.go
@@ -6,7 +6,6 @@ import (
 	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
 	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	"github.com/cs3org/reva/v2/pkg/events"
-	"github.com/owncloud/ocis/v2/services/postprocessing/pkg/config"
 )
 
 // Postprocessing handles postprocessing of a file
@@ -18,22 +17,22 @@ type Postprocessing struct {
 	filename   string
 	filesize   uint64
 	resourceID *provider.ResourceId
-	c          config.Postprocessing
 	steps      []events.Postprocessingstep
+	delay      time.Duration
 }
 
 // New returns a new postprocessing instance
-func New(uploadID string, uploadURL string, user *user.User, filename string, filesize uint64, resourceID *provider.ResourceId, c config.Postprocessing) *Postprocessing {
+func New(uploadID string, uploadURL string, user *user.User, filename string, filesize uint64, resourceID *provider.ResourceId, steps []events.Postprocessingstep, delay time.Duration) *Postprocessing {
 	return &Postprocessing{
 		id:         uploadID,
 		url:        uploadURL,
 		u:          user,
 		m:          make(map[events.Postprocessingstep]interface{}),
-		c:          c,
 		filename:   filename,
 		filesize:   filesize,
 		resourceID: resourceID,
-		steps:      getSteps(c),
+		steps:      steps,
+		delay:      delay,
 	}
 }
 
@@ -48,13 +47,13 @@ func (pp *Postprocessing) Init(ev events.BytesReceived) interface{} {
 	return pp.nextStep(pp.steps[0])
 }
 
-// Virusscan is the virusscanning step of the postprocessing
-func (pp *Postprocessing) Virusscan(ev events.VirusscanFinished) interface{} {
-	pp.m[events.PPStepAntivirus] = ev
+// NextStep returns the next postprocessing step
+func (pp *Postprocessing) NextStep(ev events.PostprocessingStepFinished) interface{} {
+	pp.m[ev.FinishedStep] = ev
 
 	switch ev.Outcome {
 	case events.PPOutcomeContinue:
-		return pp.next(events.PPStepAntivirus)
+		return pp.next(ev.FinishedStep)
 	default:
 		return pp.finished(ev.Outcome)
 
@@ -64,7 +63,7 @@ func (pp *Postprocessing) Virusscan(ev events.VirusscanFinished) interface{} {
 // Delay will sleep the configured time then continue
 func (pp *Postprocessing) Delay(ev events.StartPostprocessingStep) interface{} {
 	pp.m[events.PPStepDelay] = ev
-	time.Sleep(pp.c.Delayprocessing)
+	time.Sleep(pp.delay)
 	return pp.next(events.PPStepDelay)
 }
 
@@ -99,20 +98,3 @@ func (pp *Postprocessing) finished(outcome events.PostprocessingOutcome) events.
 		Outcome:       outcome,
 	}
 }
-
-func getSteps(c config.Postprocessing) []events.Postprocessingstep {
-	// NOTE: first version only contains very basic configuration options
-	// But we aim for a system where postprocessing steps and their order can be configured per space
-	// ideally by the spaceadmin itself
-	// We need to iterate over configuring PP service when we see fit
-	var steps []events.Postprocessingstep
-	if c.Delayprocessing != 0 {
-		steps = append(steps, events.PPStepDelay)
-	}
-
-	if c.Virusscan {
-		steps = append(steps, events.PPStepAntivirus)
-	}
-
-	return steps
-}
diff --git a/services/postprocessing/pkg/service/service.go b/services/postprocessing/pkg/service/service.go
index e442cb76a26..7100a642227 100644
--- a/services/postprocessing/pkg/service/service.go
+++ b/services/postprocessing/pkg/service/service.go
@@ -12,6 +12,7 @@ type PostprocessingService struct {
 	log    log.Logger
 	events <-chan interface{}
 	pub    events.Publisher
+	steps  []events.Postprocessingstep
 	c      config.Postprocessing
 }
 
@@ -31,6 +32,7 @@ func NewPostprocessingService(stream events.Stream, logger log.Logger, c config.
 		log:    logger,
 		events: evs,
 		pub:    stream,
+		steps:  getSteps(c),
 		c:      c,
 	}, nil
 }
@@ -42,16 +44,16 @@ func (pps *PostprocessingService) Run() error {
 		var next interface{}
 		switch ev := e.(type) {
 		case events.BytesReceived:
-			pp := postprocessing.New(ev.UploadID, ev.URL, ev.ExecutingUser, ev.Filename, ev.Filesize, ev.ResourceID, pps.c)
+			pp := postprocessing.New(ev.UploadID, ev.URL, ev.ExecutingUser, ev.Filename, ev.Filesize, ev.ResourceID, pps.steps, pps.c.Delayprocessing)
 			current[ev.UploadID] = pp
 			next = pp.Init(ev)
-		case events.VirusscanFinished:
+		case events.PostprocessingStepFinished:
 			pp := current[ev.UploadID]
 			if pp == nil {
 				// no current upload - this was an on demand scan
 				continue
 			}
-			next = pp.Virusscan(ev)
+			next = pp.NextStep(ev)
 		case events.StartPostprocessingStep:
 			if ev.StepToStart != events.PPStepDelay {
 				continue
@@ -73,3 +75,15 @@ func (pps *PostprocessingService) Run() error {
 	}
 	return nil
 }
+
+func getSteps(c config.Postprocessing) []events.Postprocessingstep {
+	// NOTE: improved version only allows configuring order of postprocessing steps
+	// But we aim for a system where postprocessing steps can be configured per space, ideally by the spaceadmin itself
+	// We need to iterate over configuring PP service when we see fit
+	var steps []events.Postprocessingstep
+	for _, s := range c.Steps {
+		steps = append(steps, events.Postprocessingstep(s))
+	}
+
+	return steps
+}
diff --git a/services/search/pkg/content/mocks/extractor.go b/services/search/pkg/content/mocks/extractor.go
index d1ab8d08188..d3f705014b5 100644
--- a/services/search/pkg/content/mocks/extractor.go
+++ b/services/search/pkg/content/mocks/extractor.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.15.0. DO NOT EDIT.
+// Code generated by mockery v2.14.1. DO NOT EDIT.
 
 package mocks
 
diff --git a/services/search/pkg/content/mocks/retriever.go b/services/search/pkg/content/mocks/retriever.go
index 9b50186c64b..bc380c7e690 100644
--- a/services/search/pkg/content/mocks/retriever.go
+++ b/services/search/pkg/content/mocks/retriever.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.15.0. DO NOT EDIT.
+// Code generated by mockery v2.14.1. DO NOT EDIT.
 
 package mocks
 
diff --git a/services/search/pkg/engine/mocks/engine.go b/services/search/pkg/engine/mocks/engine.go
index 61037adc514..273fd2e93be 100644
--- a/services/search/pkg/engine/mocks/engine.go
+++ b/services/search/pkg/engine/mocks/engine.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.15.0. DO NOT EDIT.
+// Code generated by mockery v2.14.1. DO NOT EDIT.
 
 package mocks
 
diff --git a/services/search/pkg/search/mocks/searcher.go b/services/search/pkg/search/mocks/searcher.go
index 2f94ad0ad1b..557e9a0e5aa 100644
--- a/services/search/pkg/search/mocks/searcher.go
+++ b/services/search/pkg/search/mocks/searcher.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.15.0. DO NOT EDIT.
+// Code generated by mockery v2.14.1. DO NOT EDIT.
 
 package mocks
 
diff --git a/services/settings/pkg/server/http/server.go b/services/settings/pkg/server/http/server.go
index fd92344ad17..2a7813036ac 100644
--- a/services/settings/pkg/server/http/server.go
+++ b/services/settings/pkg/server/http/server.go
@@ -2,13 +2,14 @@ package http
 
 import (
 	"fmt"
+	"net/http"
 
 	"github.com/go-chi/chi/v5"
 	chimiddleware "github.com/go-chi/chi/v5/middleware"
 	"github.com/owncloud/ocis/v2/ocis-pkg/account"
 	"github.com/owncloud/ocis/v2/ocis-pkg/cors"
 	"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
-	"github.com/owncloud/ocis/v2/ocis-pkg/service/http"
+	ohttp "github.com/owncloud/ocis/v2/ocis-pkg/service/http"
 	"github.com/owncloud/ocis/v2/ocis-pkg/version"
 	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	"github.com/owncloud/ocis/v2/services/settings/pkg/assets"
@@ -17,24 +18,24 @@ import (
 )
 
 // Server initializes the http service and server.
-func Server(opts ...Option) (http.Service, error) {
+func Server(opts ...Option) (ohttp.Service, error) {
 	options := newOptions(opts...)
 
-	service, err := http.NewService(
-		http.TLSConfig(options.Config.HTTP.TLS),
-		http.Logger(options.Logger),
-		http.Name(options.Name),
-		http.Version(version.GetString()),
-		http.Address(options.Config.HTTP.Addr),
-		http.Namespace(options.Config.HTTP.Namespace),
-		http.Context(options.Context),
-		http.Flags(options.Flags...),
+	service, err := ohttp.NewService(
+		ohttp.TLSConfig(options.Config.HTTP.TLS),
+		ohttp.Logger(options.Logger),
+		ohttp.Name(options.Name),
+		ohttp.Version(version.GetString()),
+		ohttp.Address(options.Config.HTTP.Addr),
+		ohttp.Namespace(options.Config.HTTP.Namespace),
+		ohttp.Context(options.Context),
+		ohttp.Flags(options.Flags...),
 	)
 	if err != nil {
 		options.Logger.Error().
 			Err(err).
 			Msg("Error initializing http service")
-		return http.Service{}, fmt.Errorf("could not initialize http service: %w", err)
+		return ohttp.Service{}, fmt.Errorf("could not initialize http service: %w", err)
 	}
 
 	handle := svc.NewService(options.Config, options.Logger)
@@ -88,6 +89,11 @@ func Server(opts ...Option) (http.Service, error) {
 		settingssvc.RegisterPermissionServiceWeb(r, handle)
 	})
 
+	_ = chi.Walk(mux, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
+
 	micro.RegisterHandler(service.Server(), mux)
 
 	return service, nil
diff --git a/services/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go
index 906ce680024..52cc0b66963 100644
--- a/services/settings/pkg/store/defaults/defaults.go
+++ b/services/settings/pkg/store/defaults/defaults.go
@@ -58,6 +58,16 @@ const (
 	// DeleteAllSpacesPermissionName is the hardcoded setting name for the delete all space permission
 	DeleteAllSpacesPermissionName string = "delete-all-spaces"
 
+	// ManageSpacePropertiesPermissionID is the hardcoded setting UUID for the manage space properties permission
+	ManageSpacePropertiesPermissionID string = "b44b4054-31a2-42b8-bb71-968b15cfbd4f"
+	// ManageSpacePropertiesPermissionName is the hardcoded setting name for the manage space properties permission
+	ManageSpacePropertiesPermissionName string = "Drive.ReadWrite"
+
+	// SpaceAbilityPermissionID is the hardcoded setting UUID for the space ability permission
+	SpaceAbilityPermissionID string = "cf3faa8c-50d9-4f84-9650-ff9faf21aa9d"
+	// SpaceAbilityPermissionName is the hardcoded setting name for the space ability permission
+	SpaceAbilityPermissionName string = "Drive.ReadWriteEnabled"
+
 	settingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f"
 
 	// AccountManagementPermissionID is the hardcoded setting UUID for the account management permission
@@ -265,6 +275,36 @@ func generateBundleSpaceAdminRole() *settingsmsg.Bundle {
 			Type: settingsmsg.Resource_TYPE_SYSTEM,
 		},
 		Settings: []*settingsmsg.Setting{
+			{
+				Id:          ManageSpacePropertiesPermissionID,
+				Name:        ManageSpacePropertiesPermissionName,
+				DisplayName: "Manage space properties",
+				Description: "This permission allows to manage space properties such as name and description.",
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_SYSTEM,
+				},
+				Value: &settingsmsg.Setting_PermissionValue{
+					PermissionValue: &settingsmsg.Permission{
+						Operation:  settingsmsg.Permission_OPERATION_READWRITE,
+						Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+					},
+				},
+			},
+			{
+				Id:          SpaceAbilityPermissionID,
+				Name:        SpaceAbilityPermissionName,
+				DisplayName: "Space ability",
+				Description: "This permission allows to enable and disable spaces.",
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_SYSTEM,
+				},
+				Value: &settingsmsg.Setting_PermissionValue{
+					PermissionValue: &settingsmsg.Permission{
+						Operation:  settingsmsg.Permission_OPERATION_READWRITE,
+						Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+					},
+				},
+			},
 			{
 				Id:          SetSpaceQuotaPermissionID,
 				Name:        SetSpaceQuotaPermissionName,
diff --git a/services/thumbnails/pkg/service/http/v0/service.go b/services/thumbnails/pkg/service/http/v0/service.go
index 428986a3a19..1fe63be6e85 100644
--- a/services/thumbnails/pkg/service/http/v0/service.go
+++ b/services/thumbnails/pkg/service/http/v0/service.go
@@ -55,6 +55,11 @@ func NewService(opts ...Option) Service {
 		r.Get("/data", svc.GetThumbnail)
 	})
 
+	_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
+
 	return svc
 }
 
diff --git a/services/web/Makefile b/services/web/Makefile
index 486bae4a79f..3c4ca0ba152 100644
--- a/services/web/Makefile
+++ b/services/web/Makefile
@@ -1,6 +1,6 @@
 SHELL := bash
 NAME := web
-WEB_ASSETS_VERSION = v7.0.0-rc.7
+WEB_ASSETS_VERSION = v7.0.0-rc.10
 
 include ../../.make/recursion.mk
 
diff --git a/services/web/pkg/config/defaults/defaultconfig.go b/services/web/pkg/config/defaults/defaultconfig.go
index f827e68094e..f32c1f34dc4 100644
--- a/services/web/pkg/config/defaults/defaultconfig.go
+++ b/services/web/pkg/config/defaults/defaultconfig.go
@@ -49,10 +49,6 @@ func DefaultConfig() *config.Config {
 				},
 				Apps: []string{"files", "search", "text-editor", "pdf-viewer", "external", "admin-settings"},
 				ExternalApps: []config.ExternalApp{
-					{
-						ID:   "settings",
-						Path: "/settings.js",
-					},
 					{
 						ID:   "preview",
 						Path: "web-app-preview",
diff --git a/services/web/pkg/service/v0/service.go b/services/web/pkg/service/v0/service.go
index a00e28bcdd9..aeb1c3083df 100644
--- a/services/web/pkg/service/v0/service.go
+++ b/services/web/pkg/service/v0/service.go
@@ -45,6 +45,11 @@ func NewService(opts ...Option) Service {
 		r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL))
 	})
 
+	_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
+
 	return svc
 }
 
diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go
index 2557b619c72..963abbd6638 100644
--- a/services/webdav/pkg/service/v0/service.go
+++ b/services/webdav/pkg/service/v0/service.go
@@ -134,6 +134,11 @@ func NewService(opts ...Option) (Service, error) {
 		}))
 	})
 
+	_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
+		options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
+		return nil
+	})
+
 	return svc, nil
 }
 


Test patch:
diff --git a/docs/ocis/development/testing.md b/docs/ocis/development/testing.md
index b3fa042e9c9..fafe6365e34 100644
--- a/docs/ocis/development/testing.md
+++ b/docs/ocis/development/testing.md
@@ -89,15 +89,12 @@ make -C tests/acceptance/docker clean
 
 ## Testing with test suite natively installed
 
-We are using the ownCloud 10 acceptance test suite against oCIS.
+We have two sets of tests:
+- `test-acceptance-from-core-api` set was transferred from [core](https://github.com/owncloud/core) repository 
+The suite name of all tests transferred from the core starts with "core"
 
-### Getting the tests
+- `test-acceptance-api` set was created for ocis. Mainly for testing spaces features
 
-All you need to do to get the acceptance tests is check out the core repo:
-
-```bash
-git clone https://github.com/owncloud/core.git
-```
 
 ### Run ocis
 
@@ -106,47 +103,54 @@ Create an up-to-date ocis binary by [building oCIS]({{< ref "build" >}})
 To start ocis:
 
 ```bash
-IDM_ADMIN_PASSWORD=admin ocis/bin/ocis init --insecure true
-OCIS_INSECURE=true PROXY_ENABLE_BASIC_AUTH=true ocis/bin/ocis server
+IDM_ADMIN_PASSWORD=admin \
+ocis/bin/ocis init --insecure true
+
+OCIS_INSECURE=true PROXY_ENABLE_BASIC_AUTH=true \
+ocis/bin/ocis server
 ```
 
 `PROXY_ENABLE_BASIC_AUTH` will allow the acceptance tests to make requests against the provisioning api (and other endpoints) using basic auth.
 
-### Run the acceptance tests
-
-First we will need to clone the testing app in owncloud which contains the skeleton files required for running the tests.
-In the ownCloud 10 core clone the testing app with the following command:
+### Run the test-acceptance-from-core-api tests
 
 ```bash
-git clone https://github.com/owncloud/testing apps/testing
+make test-acceptance-from-core-api \
+TEST_SERVER_URL=https://localhost:9200 \
+TEST_WITH_GRAPH_API=true \
+TEST_OCIS=true \
 ```
+Note: This command only works for suites that start with core
 
-Then run the api acceptance tests with the following command from the root of the ownCloud 10 core repository:
+### Run the test-acceptance-api tests
 
 ```bash
 make test-acceptance-api \
 TEST_SERVER_URL=https://localhost:9200 \
 TEST_WITH_GRAPH_API=true \
-PATH_TO_OCIS=/var/www/ocis \
-PATH_TO_CORE=. \
 TEST_OCIS=true \
-STORAGE_DRIVER=OCIS \
-SKELETON_DIR=apps/testing/data/apiSkeleton \
-BEHAT_FILTER_TAGS='~@notToImplementOnOCIS&&~@toImplementOnOCIS'
 ```
 
-Make sure to adjust the settings `TEST_SERVER_URL` and `PATH_TO_OCIS` according to your environment.
+Make sure to adjust the settings `TEST_SERVER_URL` according to your environment.
+
+To run a single feature add `BEHAT_FEATURE=<feature file>` 
+
+example: `BEHAT_SUITE=tests/acceptance/features/apiGraph/createUser.feature`
 
-This will run all tests that are relevant to oCIS.
+To run a single test add `BEHAT_FEATURE=<file.feature:(line number)>`
 
-To run a single feature add `BEHAT_FEATURE=<feature file>`
+example: `BEHAT_SUITE=tests/acceptance/features/apiGraph/createUser.feature:12`
+
+To run a single suite add `BEHAT_SUITE=<test suite>`
+
+example: `BEHAT_SUITE=apiGraph`
 
 To run tests with a different storage driver set `STORAGE_DRIVER` to the correct value. It can be set to `OCIS` or `OWNCLOUD` and uses `OWNCLOUD` as the default value.
 
 ### use existing tests for BDD
 
-As a lot of scenarios are written for oC10, we can use those tests for Behaviour driven development in ocis.
-Every scenario that does not work in oCIS with "ocis" storage, is listed in `tests/acceptance/expected-failures-on-OCIS-storage.md` with a link to the related issue.
+As a lot of scenarios from `test-acceptance-from-core-api` are written for oC10, we can use those tests for Behaviour driven development in ocis.
+Every scenario that does not work in oCIS with "ocis" storage, is listed in `tests/acceptance/expected-failures-API-on-OCIS-storage.md` with a link to the related issue.
 
 Those scenarios are run in the ordinary acceptance test pipeline in CI. The scenarios that fail are checked against the
 expected failures. If there are any differences then the CI pipeline fails.
@@ -155,30 +159,24 @@ The tests are not currently run in CI with the OWNCLOUD or EOS storage drivers,
 
 If you want to work on a specific issue
 
-1. adjust the core commit id to the latest commit in core so that CI will run the latest test code and scenarios from core.
-    For that change `CORE_COMMITID` in `.drone.env`:
-
-        # The test runner source for API tests
-        CORE_COMMITID=38c91e5cf5fc4ffdc0536ba1d147a2a618ef83b5
-        CORE_BRANCH=master
-
-2. locally run each of the tests marked with that issue in the expected failures file.
+1. locally run each of the tests marked with that issue in the expected failures file.
 
     E.g.:
 
     ```bash
-    make test-acceptance-api \
+    make test-acceptance-from-core-api \
     TEST_SERVER_URL=https://localhost:9200 \
     TEST_OCIS=true \
+    TEST_WITH_GRAPH_API=true \
     STORAGE_DRIVER=OCIS \
-    BEHAT_FEATURE='tests/acceptance/features/apiComments/comments.feature:123'
+    BEHAT_FEATURE='tests/acceptance/features/coreApiVersions/fileVersions.feature:147'
     ```
 
-3. the tests will fail, try to understand how and why they are failing
-4. fix the code
-5. go back to 2. and repeat till the tests are passing.
-6. remove those tests from the expected failures file
-7. make a PR that has the fixed code, and the relevant lines removed from the expected failures file.
+2. the tests will fail, try to understand how and why they are failing
+3. fix the code
+4. go back to 1. and repeat till the tests are passing.
+5. remove those tests from the expected failures file
+6. make a PR that has the fixed code, and the relevant lines removed from the expected failures file.
 
 ## Running tests for parallel deployment
 
diff --git a/services/graph/pkg/identity/ldap_education_class_test.go b/services/graph/pkg/identity/ldap_education_class_test.go
index f7271302153..7b5e215dc88 100644
--- a/services/graph/pkg/identity/ldap_education_class_test.go
+++ b/services/graph/pkg/identity/ldap_education_class_test.go
@@ -20,6 +20,7 @@ var classEntry = ldap.NewEntry("ocEducationExternalId=Math0123",
 		"ocEducationClassType":  {"course"},
 		"entryUUID":             {"abcd-defg"},
 	})
+
 var classEntryWithMember = ldap.NewEntry("ocEducationExternalId=Math0123",
 	map[string][]string{
 		"cn":                    {"Math"},
@@ -300,3 +301,245 @@ func TestGetEducationClassMembers(t *testing.T) {
 		}
 	}
 }
+
+func TestLDAP_UpdateEducationClass(t *testing.T) {
+	externalIDs := []string{"Math3210"}
+	changeString := "xxxx-xxxx"
+	type args struct {
+		id    string
+		class libregraph.EducationClass
+	}
+	type modifyData struct {
+		arg *ldap.ModifyRequest
+		ret error
+	}
+	type modifyDNData struct {
+		arg *ldap.ModifyDNRequest
+		ret error
+	}
+	type searchData struct {
+		res *ldap.SearchResult
+		err error
+	}
+	tests := []struct {
+		name         string
+		args         args
+		modifyDNData modifyDNData
+		modifyData   modifyData
+		searchData   searchData
+		assertion    func(assert.TestingT, error, ...interface{}) bool
+	}{
+		{
+			name: "Change name",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					DisplayName: "Math-2",
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Nil(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{
+					DN: "ocEducationExternalId=Math0123",
+					Changes: []ldap.Change{
+						{
+							Operation: ldap.ReplaceAttribute,
+							Modification: ldap.PartialAttribute{
+								Type: "cn",
+								Vals: []string{"Math-2"},
+							},
+						},
+					},
+				},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+		{
+			name: "Change external ID",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					ExternalId: &externalIDs[0],
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Nil(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{
+					DN:           "ocEducationExternalId=Math0123",
+					NewRDN:       "ocEducationExternalId=Math3210",
+					DeleteOldRDN: true,
+					NewSuperior:  "",
+				},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+		{
+			name: "Change both name and external ID",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					DisplayName: "Math-2",
+					ExternalId:  &externalIDs[0],
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Nil(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{
+					DN: "ocEducationExternalId=Math3210,ou=groups,dc=test",
+					Changes: []ldap.Change{
+						{
+							Operation: ldap.ReplaceAttribute,
+							Modification: ldap.PartialAttribute{
+								Type: "cn",
+								Vals: []string{"Math-2"},
+							},
+						},
+					},
+				},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{
+					DN:           "ocEducationExternalId=Math0123",
+					NewRDN:       "ocEducationExternalId=Math3210",
+					DeleteOldRDN: true,
+					NewSuperior:  "",
+				},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+		{
+			name: "Check error: attempt at changing ID",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					Id: &changeString,
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Error(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+		{
+			name: "Check error: attempt at changing description",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					Description: &changeString,
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Error(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+		{
+			name: "Check error: attempt at changing classification",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					Classification: changeString,
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Error(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+		{
+			name: "Check error: attempt at changing members",
+			args: args{
+				id: "abcd-defg",
+				class: libregraph.EducationClass{
+					Members: []libregraph.User{*libregraph.NewUser()},
+				},
+			},
+			assertion: func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.Error(tt, err) },
+			modifyData: modifyData{
+				arg: &ldap.ModifyRequest{},
+			},
+			modifyDNData: modifyDNData{
+				arg: &ldap.ModifyDNRequest{},
+				ret: nil,
+			},
+			searchData: searchData{
+				res: &ldap.SearchResult{
+					Entries: []*ldap.Entry{classEntry},
+				},
+				err: nil,
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			lm := &mocks.Client{}
+			b, err := getMockedBackend(lm, eduConfig, &logger)
+			if err != nil {
+				panic(err)
+			}
+
+			lm.On("Modify", tt.modifyData.arg).Return(tt.modifyData.ret)
+			lm.On("ModifyDN", tt.modifyDNData.arg).Return(tt.modifyDNData.ret)
+			lm.On("Search", mock.Anything).Return(tt.searchData.res, tt.searchData.err)
+
+			ctx := context.Background()
+
+			_, err = b.UpdateEducationClass(ctx, tt.args.id, tt.args.class)
+			tt.assertion(t, err)
+		})
+	}
+}
diff --git a/services/graph/pkg/identity/ldap_education_school_test.go b/services/graph/pkg/identity/ldap_education_school_test.go
index 12c25894e4d..49cc1c3dd8b 100644
--- a/services/graph/pkg/identity/ldap_education_school_test.go
+++ b/services/graph/pkg/identity/ldap_education_school_test.go
@@ -303,6 +303,14 @@ var schoolByIDSearch1 *ldap.SearchRequest = &ldap.SearchRequest{
 	Attributes: []string{"ou", "owncloudUUID", "ocEducationSchoolNumber"},
 	Controls:   []ldap.Control(nil),
 }
+var schoolByNumberSearch *ldap.SearchRequest = &ldap.SearchRequest{
+	BaseDN:     "",
+	Scope:      2,
+	SizeLimit:  1,
+	Filter:     filterSchoolSearchByNumberExisting,
+	Attributes: []string{"ou", "owncloudUUID", "ocEducationSchoolNumber"},
+	Controls:   []ldap.Control(nil),
+}
 var userByIDSearch1 *ldap.SearchRequest = &ldap.SearchRequest{
 	BaseDN:     "ou=people,dc=test",
 	Scope:      2,
@@ -320,12 +328,39 @@ var userByIDSearch2 *ldap.SearchRequest = &ldap.SearchRequest{
 	Controls:   []ldap.Control(nil),
 }
 
+var userToSchoolModRequest *ldap.ModifyRequest = &ldap.ModifyRequest{
+	DN: "uid=user,ou=people,dc=test",
+	Changes: []ldap.Change{
+		{
+			Operation: ldap.AddAttribute,
+			Modification: ldap.PartialAttribute{
+				Type: "ocMemberOfSchool",
+				Vals: []string{"abcd-defg"},
+			},
+		},
+	},
+}
+
+var userFromSchoolModRequest *ldap.ModifyRequest = &ldap.ModifyRequest{
+	DN: "uid=user,ou=people,dc=test",
+	Changes: []ldap.Change{
+		{
+			Operation: ldap.DeleteAttribute,
+			Modification: ldap.PartialAttribute{
+				Type: "ocMemberOfSchool",
+				Vals: []string{"abcd-defg"},
+			},
+		},
+	},
+}
+
 func TestAddUsersToEducationSchool(t *testing.T) {
 	lm := &mocks.Client{}
-	lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry, schoolEntry1}}, nil)
+	lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry}}, nil)
+	lm.On("Search", schoolByNumberSearch).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry}}, nil)
 	lm.On("Search", userByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntry}}, nil)
 	lm.On("Search", userByIDSearch2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{}}, nil)
-	lm.On("Modify", mock.Anything).Return(nil)
+	lm.On("Modify", userToSchoolModRequest).Return(nil)
 	b, err := getMockedBackend(lm, eduConfig, &logger)
 	assert.Nil(t, err)
 	err = b.AddUsersToEducationSchool(context.Background(), "abcd-defg", []string{"does-not-exist"})
@@ -337,14 +372,19 @@ func TestAddUsersToEducationSchool(t *testing.T) {
 	err = b.AddUsersToEducationSchool(context.Background(), "abcd-defg", []string{"abcd-defg"})
 	lm.AssertNumberOfCalls(t, "Search", 7)
 	assert.Nil(t, err)
+	// try to add by school number (instead or id)
+	err = b.AddUsersToEducationSchool(context.Background(), "0123", []string{"abcd-defg"})
+	lm.AssertNumberOfCalls(t, "Search", 9)
+	assert.Nil(t, err)
 }
 
 func TestRemoveMemberFromEducationSchool(t *testing.T) {
 	lm := &mocks.Client{}
-	lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry, schoolEntry1}}, nil)
+	lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry}}, nil)
+	lm.On("Search", schoolByNumberSearch).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry}}, nil)
 	lm.On("Search", userByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntryWithSchool}}, nil)
 	lm.On("Search", userByIDSearch2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{}}, nil)
-	lm.On("Modify", mock.Anything).Return(nil)
+	lm.On("Modify", userFromSchoolModRequest).Return(nil)
 	b, err := getMockedBackend(lm, eduConfig, &logger)
 	assert.Nil(t, err)
 	err = b.RemoveUserFromEducationSchool(context.Background(), "abcd-defg", "does-not-exist")
@@ -354,6 +394,10 @@ func TestRemoveMemberFromEducationSchool(t *testing.T) {
 	err = b.RemoveUserFromEducationSchool(context.Background(), "abcd-defg", "abcd-defg")
 	lm.AssertNumberOfCalls(t, "Search", 4)
 	lm.AssertNumberOfCalls(t, "Modify", 1)
+	// try to remove by school number (instead or id)
+	err = b.RemoveUserFromEducationSchool(context.Background(), "0123", "abcd-defg")
+	lm.AssertNumberOfCalls(t, "Search", 6)
+	lm.AssertNumberOfCalls(t, "Modify", 2)
 	assert.Nil(t, err)
 }
 
@@ -368,10 +412,14 @@ var usersBySchoolIDSearch *ldap.SearchRequest = &ldap.SearchRequest{
 
 func TestGetEducationSchoolUsers(t *testing.T) {
 	lm := &mocks.Client{}
-	lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry, schoolEntry1}}, nil)
+	lm.On("Search", schoolByIDSearch1).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry}}, nil)
+	lm.On("Search", schoolByNumberSearch).Return(&ldap.SearchResult{Entries: []*ldap.Entry{schoolEntry}}, nil)
 	lm.On("Search", usersBySchoolIDSearch).Return(&ldap.SearchResult{Entries: []*ldap.Entry{eduUserEntryWithSchool}}, nil)
 	b, _ := getMockedBackend(lm, eduConfig, &logger)
 	users, err := b.GetEducationSchoolUsers(context.Background(), "abcd-defg")
 	assert.Nil(t, err)
 	assert.Equal(t, 1, len(users))
+	users, err = b.GetEducationSchoolUsers(context.Background(), "0123")
+	assert.Nil(t, err)
+	assert.Equal(t, 1, len(users))
 }
diff --git a/services/graph/pkg/service/v0/educationclasses_test.go b/services/graph/pkg/service/v0/educationclasses_test.go
index d1f2a9d0b39..ba90311bd25 100644
--- a/services/graph/pkg/service/v0/educationclasses_test.go
+++ b/services/graph/pkg/service/v0/educationclasses_test.go
@@ -284,13 +284,16 @@ var _ = Describe("EducationClass", func() {
 		Context("with an existing group", func() {
 			BeforeEach(func() {
 				identityEducationBackend.On("GetEducationClass", mock.Anything, mock.Anything, mock.Anything).Return(newClass, nil)
+				identityEducationBackend.On("UpdateEducationClass", mock.Anything, mock.Anything, mock.Anything).Return(newClass, nil)
 			})
 
 			It("fails when the number of users is exceeded - spec says 20 max", func() {
 				updatedClass := libregraph.NewEducationClassWithDefaults()
 				updatedClass.SetDisplayName("class updated")
-				updatedClass.SetMembersodataBind([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18",
-					"19", "20", "21"})
+				updatedClass.SetMembersodataBind([]string{
+					"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18",
+					"19", "20", "21",
+				})
 				updatedClassJson, err := json.Marshal(updatedClass)
 				Expect(err).ToNot(HaveOccurred())
 
@@ -323,6 +326,7 @@ var _ = Describe("EducationClass", func() {
 					service.WithGatewayClient(gatewayClient),
 					service.EventsPublisher(&eventsPublisher),
 					service.WithIdentityBackend(identityBackend),
+					service.WithIdentityEducationBackend(identityEducationBackend),
 				)
 
 				r := httptest.NewRequest(http.MethodPatch, "/graph/v1.0/education/classes", bytes.NewBuffer(updatedClassJson))
@@ -408,7 +412,7 @@ var _ = Describe("EducationClass", func() {
 
 			Expect(rr.Code).To(Equal(http.StatusNoContent))
 			identityEducationBackend.AssertNumberOfCalls(GinkgoT(), "DeleteEducationClass", 1)
-			//eventsPublisher.AssertNumberOfCalls(GinkgoT(), "Publish", 1)
+			// eventsPublisher.AssertNumberOfCalls(GinkgoT(), "Publish", 1)
 		})
 	})
 
diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go
index d516782534b..9f21aa1a197 100644
--- a/services/graph/pkg/service/v0/users_test.go
+++ b/services/graph/pkg/service/v0/users_test.go
@@ -372,6 +372,7 @@ var _ = Describe("Users", func() {
 					},
 					{
 						Id:        &provider.StorageSpaceId{OpaqueId: "personal"},
+						Owner:     &userv1beta1.User{Id: &userv1beta1.UserId{OpaqueId: "user1"}},
 						Root:      &provider.ResourceId{SpaceId: "personal", OpaqueId: "personal"},
 						SpaceType: "personal",
 					},
@@ -512,9 +513,6 @@ var _ = Describe("Users", func() {
 		})
 
 		It("handles bad Mails", func() {
-			user.Mail = nil
-			assertHandleBadAttributes(user)
-
 			user.SetMail("not-a-mail-address")
 			assertHandleBadAttributes(user)
 		})
diff --git a/services/settings/ui/tests/acceptance/features/settings.feature b/services/settings/ui/tests/acceptance/features/settings.feature
deleted file mode 100644
index 37c081987eb..00000000000
--- a/services/settings/ui/tests/acceptance/features/settings.feature
+++ /dev/null
@@ -1,46 +0,0 @@
-Feature: Set user specific settings
-	As a user
-	I want to set user specific settings
-	So that I can customize my OCIS experience to my liking
-
-	Background:
-		Given these users have been created with default attributes and without skeleton files in the server:
-			| username |
-			| user1    |
-			| user2    |
-		And user "user1" has created folder "simple-folder" in the server
-
-	Scenario: Check the default settings
-		Given user "user1" has logged in using the webUI
-		And the user browses to the settings page
-		Then the setting "Language" should have value "English"
-		When the user browses to the files page
-		Then the files menu should be listed in language "English"
-
-	Scenario: changing the language (reactive and with page reload)
-		Given user "user1" has logged in using the webUI
-		And the user browses to the settings page
-		When the user changes the language to "Deutsch"
-		Then the setting "Language" should have value "Deutsch"
-		When the user browses to the files page
-		Then the files menu should be listed in language "Deutsch"
-		And the account menu should be listed in language "Deutsch"
-		And the files header should be displayed in language "Deutsch"
-		When the user reloads the current page of the webUI
-		Then the files menu should be listed in language "Deutsch"
-		And the account menu should be listed in language "Deutsch"
-		And the files header should be displayed in language "Deutsch"
-		When the user browses to the settings page
-		And the user changes the language to "English"
-		And the user browses to the files page
-		Then the files menu should be listed in language "English"
-
-	Scenario: changing the language only affects one user
-		Given user "user2" has logged in using the webUI
-		And the user browses to the settings page
-		When the user changes the language to "Español"
-		Then the setting "Language" should have value "Español"
-		When the user browses to the files page
-		Then the files menu should be listed in language "Español"
-		When the user re-logs in as "user1" using the webUI
-		Then the files menu should be listed in language "English"
diff --git a/services/settings/ui/tests/acceptance/helpers/language.js b/services/settings/ui/tests/acceptance/helpers/language.js
deleted file mode 100644
index d720300fda0..00000000000
--- a/services/settings/ui/tests/acceptance/helpers/language.js
+++ /dev/null
@@ -1,112 +0,0 @@
-const filesMenu = {
-  English: [
-    'Personal',
-    'Shares',
-    'Spaces',
-    'Deleted files'
-  ],
-  Deutsch: [
-    'Persönlich',
-    'Geteilt',
-    'Spaces',
-    'Gelöschte Dateien'
-  ],
-  Español: [
-    'Personal',
-    'Shares',
-    'Spaces',
-    'Archivos borrados'
-  ],
-  Français: [
-    'Personal',
-    'Shares',
-    'Spaces',
-    'Fichiers supprimés'
-  ]
-}
-
-const accountMenu = {
-  English: [
-    'U\nuser1\nuser1@example.org',
-    'Settings',
-    'Log out',
-    'Personal storage\n0 B used'
-  ],
-  Deutsch: [
-    'U\nuser1\nuser1@example.org',
-    'Einstellungen',
-    'Abmelden',
-    'Persönlicher Speicherplatz\n0 B verwendet'
-  ],
-  Español: [
-    'U\nuser1\nuser1@example.org',
-    'Configuración',
-    'Salir',
-    'Personal storage\n0 B used'
-  ],
-  Français: [
-    'U\nuser1\nuser1@example.org',
-    'Settings',
-    'Se déconnecter',
-    'Personal storage\n0 B used'
-  ]
-}
-
-const filesListHeaderMenu = {
-  English: [
-    'Name',
-    'Shares',
-    'Size',
-    'Tags',
-    'Modified',
-    'Actions'
-  ],
-  Deutsch: [
-    'Name',
-    'Geteilt',
-    'Größe',
-    'Schlagwörter',
-    'Bearbeitet',
-    'Aktionen'
-  ],
-  Español: [
-    'Nombre',
-    'Shares',
-    'Tamaño',
-    'Tags',
-    'Modificado',
-    'Acciones'
-  ],
-  Français: [
-    'Nom',
-    'Shares',
-    'Taille',
-    'Étiquettes',
-    'Modifié',
-    'Actions'
-  ]
-}
-
-exports.getFilesMenuForLanguage = function (language) {
-  const menuList = filesMenu[language]
-  if (menuList === undefined) {
-    throw new Error(`Menu for language ${language} is not available`)
-  }
-  return menuList
-}
-
-exports.getUserMenuForLanguage = function (language) {
-  const menuList = accountMenu[language]
-  if (menuList === undefined) {
-    throw new Error(`Menu for language ${language} is not available`)
-  }
-  return menuList
-}
-
-exports.getFilesHeaderMenuForLanguage = function (language) {
-  const menuList = filesListHeaderMenu[language]
-  if (menuList === undefined) {
-    throw new Error(`Menu for language ${language} is not available`)
-  }
-  return menuList
-}
diff --git a/services/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js b/services/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js
deleted file mode 100644
index b2ca955c398..00000000000
--- a/services/settings/ui/tests/acceptance/pageobjects/filesPageSettingsContext.js
+++ /dev/null
@@ -1,82 +0,0 @@
-module.exports = {
-  commands: {
-    getMenuList: async function () {
-      const menu = []
-      await this.isVisible('@openNavigationBtn', (res) => {
-        if (res.value) {
-          this.click('@openNavigationBtn')
-        }
-      })
-      await this.waitForElementVisible('@fileSidebarNavItem')
-      await this.api
-        .elements('@fileSidebarNavItem', result => {
-          result.value.map(item => {
-            this.api.elementIdText(item.ELEMENT, res => {
-              menu.push(res.value)
-            })
-            return undefined
-          })
-        })
-      return menu
-    },
-    getUserMenu: async function () {
-      const menu = []
-      await this
-        .waitForElementVisible('@userMenuBtn')
-        .click('@userMenuBtn')
-        .waitForElementVisible('@userMenuContainer')
-      await this.api
-        .elements('@userMenuItem', result => {
-          result.value.map(item => {
-            this.api.elementIdText(item.ELEMENT, res => {
-              menu.push(res.value)
-            })
-            return undefined
-          })
-        })
-      await this
-        .click('@userMenuBtn')
-        .waitForElementNotPresent('@userMenuContainer')
-      return menu
-    },
-    getFileHeaderItems: async function () {
-      const menu = []
-      await this.waitForElementVisible('@fileTableHeaderItems')
-      await this.api
-        .elements('@fileTableHeaderItems', result => {
-          result.value.map(item => {
-            this.api.elementIdText(item.ELEMENT, res => {
-              menu.push(res.value)
-            })
-            return undefined
-          })
-        })
-      return menu
-    }
-  },
-
-  elements: {
-    pageHeader: {
-      selector: '.oc-page-title'
-    },
-    fileSidebarNavItem: {
-      selector: '.oc-sidebar-nav-item'
-    },
-    openNavigationBtn: {
-      selector: '.oc-app-navigation-toggle'
-    },
-    userMenuBtn: {
-      selector: '#_userMenuButton'
-    },
-    userMenuItem: {
-      selector: '#account-info-container li'
-    },
-    userMenuContainer: {
-      selector: '#account-info-container'
-    },
-    fileTableHeaderItems: {
-      selector: '//*[@id="files-space-table"]//th[not(.//div)]',
-      locateStrategy: 'xpath'
-    }
-  }
-}
diff --git a/services/settings/ui/tests/acceptance/pageobjects/settingsPage.js b/services/settings/ui/tests/acceptance/pageobjects/settingsPage.js
deleted file mode 100644
index 8331053325b..00000000000
--- a/services/settings/ui/tests/acceptance/pageobjects/settingsPage.js
+++ /dev/null
@@ -1,82 +0,0 @@
-const { client } = require('nightwatch-api')
-
-module.exports = {
-  url: function () {
-    return this.api.launchUrl + '/settings'
-  },
-
-  commands: {
-    navigateAndWaitTillLoaded: async function () {
-      const url = this.url()
-      await await this.navigate(url)
-      while (true) {
-        let found = false
-        await this.waitForElementVisible('@pageHeader', 2000, 500, false)
-        await this.api
-          .elements('@pageHeader', result => {
-            if (result.value.length) {
-              found = true
-            }
-          })
-        if (found) {
-          break
-        }
-        await client.refresh()
-      }
-      return this.waitForElementVisible('@pageHeader')
-    },
-
-    getSettingsValue: async function (key) {
-      let output
-      let elemfound = true
-
-      switch (key) {
-        case 'Language':
-          // Language value is set to empty at beginning
-          // In that case just return false
-          await this.api.element('@languageValue', result => {
-            if (result.status < 0) {
-              elemfound = false
-            }
-          })
-          if (!elemfound) {
-            output = false
-            break
-          }
-          await this.waitForElementVisible('@languageValue')
-            .getText('@languageValue', (result) => {
-              output = result.value
-            })
-          break
-        default:
-          throw new Error('failed to find the setting')
-      }
-      return output
-    },
-    changeSettings: async function (key, value) {
-      switch (key) {
-        case 'Language':
-          await this
-            .waitForElementVisible('@languageInput')
-            .setValue('@languageInput', value + '\n')
-          break
-        default:
-          throw new Error('failed to find the setting')
-      }
-    }
-  },
-
-  elements: {
-    pageHeader: {
-      selector: '.oc-page-title'
-    },
-    languageValue: {
-      selector: "//label[.='Language']/..//span[@class='vs__selected']",
-      locateStrategy: 'xpath'
-    },
-    languageInput: {
-      selector: "//label[.='Language']/..//input",
-      locateStrategy: 'xpath'
-    }
-  }
-}
diff --git a/services/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js b/services/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js
deleted file mode 100644
index aba0804b486..00000000000
--- a/services/settings/ui/tests/acceptance/stepDefinitions/settingsContext.js
+++ /dev/null
@@ -1,40 +0,0 @@
-const assert = require('assert')
-const { client } = require('nightwatch-api')
-const { Given, When, Then } = require('@cucumber/cucumber')
-const languageHelper = require('../helpers/language')
-
-Given('the user browses to the settings page', function () {
-  return client.page.settingsPage().navigateAndWaitTillLoaded()
-})
-
-Then('the setting {string} should have value {string}', async function (setting, result) {
-  const actual = await client.page.settingsPage().getSettingsValue(setting)
-  assert.strictEqual(actual, result, 'The setting value doesnt matches to ' + result)
-})
-
-Then('the setting {string} should not have any value', async function (setting) {
-  const actual = await client.page.settingsPage().getSettingsValue(setting)
-  assert.strictEqual(actual, false, 'The setting value was expected not to be present but was')
-})
-
-When('the user changes the language to {string}', async function (value) {
-  await client.page.settingsPage().changeSettings('Language', value)
-})
-
-Then('the files menu should be listed in language {string}', async function (language) {
-  const menu = await client.page.filesPageSettingsContext().getMenuList()
-  const expected = languageHelper.getFilesMenuForLanguage(language)
-  assert.deepStrictEqual(menu, expected, 'the menu list were not same')
-})
-
-Then('the account menu should be listed in language {string}', async function (language) {
-  const menu = await client.page.filesPageSettingsContext().getUserMenu()
-  const expected = languageHelper.getUserMenuForLanguage(language)
-  assert.deepStrictEqual(menu, expected, 'the menu list were not same')
-})
-
-Then('the files header should be displayed in language {string}', async function (language) {
-  const items = await client.page.filesPageSettingsContext().getFileHeaderItems()
-  const expected = languageHelper.getFilesHeaderMenuForLanguage(language)
-  assert.deepStrictEqual(items, expected, 'the menu list were not same')
-})
diff --git a/services/settings/ui/tests/run-acceptance-test.sh b/services/settings/ui/tests/run-acceptance-test.sh
deleted file mode 100755
index ec99f83df09..00000000000
--- a/services/settings/ui/tests/run-acceptance-test.sh
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/bin/bash
-
-if [ -z "$WEB_PATH" ]
-then
-	echo "WEB_PATH env variable is not set, cannot find files for tests infrastructure"
-	exit 1
-fi
-
-if [ -z "$WEB_UI_CONFIG" ]
-then
-	echo "WEB_UI_CONFIG env variable is not set, cannot find web config file"
-	exit 1
-fi
-
-if [ -z "$1" ]
-then
-	echo "Features path not given, exiting test run"
-	exit 1
-fi
-
-trap clean_up SIGHUP SIGINT SIGTERM
-
-if [ -z "$TEST_INFRA_DIRECTORY" ]
-then
-	cleanup=true
-	testFolder=$(mktemp -d -p .)
-	printf "creating folder $testFolder for Test infrastructure setup\n\n"
-	export TEST_INFRA_DIRECTORY=$(realpath $testFolder)
-fi
-
-clean_up() {
-	if $cleanup
-	then
-		if [ -d "$testFolder" ]; then
-			printf "\n\n\n\nDeleting folder $testFolder Test infrastructure setup..."
-			rm -rf "$testFolder"
-		fi
-	fi
-}
-
-trap clean_up SIGHUP SIGINT SIGTERM EXIT
-
-cp -r $(ls -d "$WEB_PATH"/tests/acceptance/* | grep -v 'node_modules') "$testFolder"
-
-export SERVER_HOST=${SERVER_HOST:-https://localhost:9200}
-export BACKEND_HOST=${BACKEND_HOST:-https://localhost:9200}
-export TEST_TAGS=${TEST_TAGS:-"not @skip"}
-
-pnpm run acceptance-tests "$1"
-
-status=$?
-exit $status
diff --git a/tests/TestHelpers/GraphHelper.php b/tests/TestHelpers/GraphHelper.php
index 0dccb47832c..80673a4800d 100644
--- a/tests/TestHelpers/GraphHelper.php
+++ b/tests/TestHelpers/GraphHelper.php
@@ -589,7 +589,9 @@ class GraphHelper {
 		$payload['onPremisesSamAccountName'] = $userName;
 		$payload['passwordProfile'] = ['password' => $password];
 		$payload['displayName'] = $displayName ?? $userName;
-		$payload['mail'] = $email ?? $userName . '@example.com';
+		if (!empty($email)) {
+			$payload['mail'] = $email ?? $userName . '@example.com';
+		}
 		return \json_encode($payload);
 	}
 
diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
index a5f191d9552..98181dba5c8 100644
--- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
@@ -35,15 +35,19 @@ _ocdav: double-check the webdav property parsing when custom namespaces are used
 
 #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914)
 
-- [coreApiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L14)
-- [coreApiVersions/fileVersionAuthor.feature:37](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L37)
-- [coreApiVersions/fileVersionAuthor.feature:58](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L58)
-- [coreApiVersions/fileVersionAuthor.feature:78](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L78)
-- [coreApiVersions/fileVersionAuthor.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L104)
-- [coreApiVersions/fileVersionAuthor.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L129)
-- [coreApiVersions/fileVersionAuthor.feature:154](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L154)
-- [coreApiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L180)
+- [coreApiVersions/fileVersionAuthor.feature:13](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L13)
+- [coreApiVersions/fileVersionAuthor.feature:44](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L44)
+- [coreApiVersions/fileVersionAuthor.feature:71](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L71)
+- [coreApiVersions/fileVersionAuthor.feature:97](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L97)
+- [coreApiVersions/fileVersionAuthor.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L130)
+- [coreApiVersions/fileVersionAuthor.feature:157](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L157)
+- [coreApiVersions/fileVersionAuthor.feature:188](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L188)
 - [coreApiVersions/fileVersionAuthor.feature:223](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L223)
+- [coreApiVersions/fileVersionAuthor.feature:275](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L275)
+- [coreApiVersions/fileVersionAuthor.feature:324](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L324)
+- [coreApiVersions/fileVersionAuthor.feature:345](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L345)
+- [coreApiVersions/fileVersionAuthor.feature:365](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L365)
+- [coreApiVersions/fileVersionAuthor.feature:390](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature#L390)
 
 ### Sync
 
@@ -307,8 +311,6 @@ cannot share a folder with create permission
 - [coreApiShareOperationsToShares1/changingFilesShare.feature:131](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L131)
 - [coreApiShareOperationsToShares1/changingFilesShare.feature:132](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/changingFilesShare.feature#L132)
 - [coreApiShareManagementBasicToShares/createShareToSharesFolder.feature:538](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature#L538)
-- [coreApiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature#L220)
-- [coreApiVersions/fileVersionsSharingToShares.feature:221](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature#L221)
 - [coreApiWebdavMove2/moveShareOnOcis.feature:30](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L30)
 - [coreApiWebdavMove2/moveShareOnOcis.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L32)
 - [coreApiWebdavMove2/moveShareOnOcis.feature:98](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavMove2/moveShareOnOcis.feature#L98)
@@ -465,7 +467,7 @@ cannot share a folder with create permission
 
 #### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760)
 
-- [coreApiVersions/fileVersionsSharingToShares.feature:283](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature#L283)
+- [coreApiVersions/fileVersions.feature:487](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiVersions/fileVersions.feature#L487)
 
 #### [Share lists deleted user as 'user'](https://github.com/owncloud/ocis/issues/903)
 
diff --git a/tests/acceptance/features/apiContract/sharesReport.feature b/tests/acceptance/features/apiContract/sharesReport.feature
new file mode 100644
index 00000000000..d1a82fd0c0c
--- /dev/null
+++ b/tests/acceptance/features/apiContract/sharesReport.feature
@@ -0,0 +1,52 @@
+@api
+Feature: REPORT request to Shares space
+  Check that the REPORT response contains all relevant details for Shares
+
+  Background:
+    Given these users have been created with default attributes and without skeleton files:
+      | username |
+      | Alice    |
+      | Brian    |
+    And user "Alice" has created folder "/folderMain"
+    And user "Alice" has created folder "/folderMain/SubFolder1"
+    And user "Alice" has created folder "/folderMain/SubFolder1/subFOLDER2"
+    And user "Alice" has shared entry "/folderMain" with user "Brian" with permissions "17"
+    And user "Brian" has accepted share "/folderMain" offered by user "Alice"
+
+
+  Scenario Outline: Check the REPORT response of the found folder
+    Given using <dav_version> DAV path
+    When user "Brian" searches for "SubFolder1" using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the "REPORT" response to user "Brian" should contain a mountpoint "folderMain" with these key and value pairs:
+      | key              | value                |
+      | oc:fileid        | UUIDof:SubFolder1    |
+      | oc:file-parent   | UUIDof:folderMain    |
+      | oc:shareroot     | /folderMain          |
+      | oc:name          | SubFolder1           |
+      | d:getcontenttype | httpd/unix-directory |
+      | oc:permissions   | SR                   |
+    Examples:
+      | dav_version |
+      | old         |
+      | new         |
+
+
+  Scenario Outline: Check the REPORT response of the found file
+    Given using <dav_version> DAV path
+    And user "Alice" has uploaded file with content "Not all those who wander are lost." to "/folderMain/SubFolder1/subFOLDER2/frodo.txt"
+    When user "Brian" searches for "frodo.txt" using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the "REPORT" response to user "Brian" should contain a mountpoint "folderMain" with these key and value pairs:
+      | key                | value                                  |
+      | oc:fileid          | UUIDof:SubFolder1/subFOLDER2/frodo.txt |
+      | oc:file-parent     | UUIDof:SubFolder1/subFOLDER2           |
+      | oc:shareroot       | /folderMain                            |
+      | oc:name            | frodo.txt                              |
+      | d:getcontenttype   | text/plain                             |
+      | oc:permissions     | SR                                     |
+      | d:getcontentlength | 34                                     |
+    Examples:
+      | dav_version |
+      | old         |
+      | new         |
diff --git a/tests/acceptance/features/apiContract/report.feature b/tests/acceptance/features/apiContract/spacesReport.feature
similarity index 100%
rename from tests/acceptance/features/apiContract/report.feature
rename to tests/acceptance/features/apiContract/spacesReport.feature
diff --git a/tests/acceptance/features/apiGraph/createUser.feature b/tests/acceptance/features/apiGraph/createUser.feature
index 1378a4be7de..cd62336c0fb 100644
--- a/tests/acceptance/features/apiGraph/createUser.feature
+++ b/tests/acceptance/features/apiGraph/createUser.feature
@@ -24,7 +24,7 @@ Feature: create user
       | withoutPassSameEmail         | without pass     | alice@example.org   |                              | 200  | should     |
       | name                         | pass with space  | example@example.org | my pass                      | 200  | should     |
       | nameWithCharacters(*:!;_+-&) | user             | new@example.org     | 123                          | 400  | should not |
-      | withoutEmail                 | without email    |                     | 123                          | 400  | should not |
+      | withoutEmail                 | without email    |                     | 123                          | 200  | should     |
       | Alice                        | same userName    | new@example.org     | 123                          | 400  | should     |
       | name with space              | name with space  | example@example.org | 123                          | 400  | should not |
 
diff --git a/tests/acceptance/features/apiSpaces/tag.feature b/tests/acceptance/features/apiSpaces/tag.feature
index a628935f122..c452072a819 100644
--- a/tests/acceptance/features/apiSpaces/tag.feature
+++ b/tests/acceptance/features/apiSpaces/tag.feature
@@ -78,3 +78,166 @@ Feature: Tag
       | important                     |
       | fileTag                       |
       | tag with symbol @^$#^%$@%!_+) |
+
+
+  Scenario Outline: Member of the space tries to create tag
+    Given user "Alice" has shared a space "use-tag" to user "Brian" with role "<role>"
+    When user "Brian" creates the following tags for folder "folderMain/insideTheFolder.txt" of space "use-tag":
+      | tag level#1                    |
+      | tag with symbols @^$#^%$@%!_+) |
+    Then the HTTP status code should be "<code>"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response <shouldOrNot> contain following tags:
+      | tag level#1                    |
+      | tag with symbols @^$#^%$@%!_+) |
+    Examples:
+      | role    | code | shouldOrNot |
+      | viewer  | 403  | should not  |
+      | editor  | 200  | should      |
+      | manager | 200  | should      |
+
+
+  Scenario: The recipient has a created tags if share is accepted
+    Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag":
+      | folderTag |
+      | marketing |
+    And user "Alice" has shared the following entity "folderMain" inside of space "use-tag" with user "Brian" with role "viewer"
+    When user "Brian" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should not contain following tags:
+      | folderTag |
+      | marketing |
+    When user "Brian" accepts share "/folderMain" offered by user "Alice" using the sharing API
+    And user "Brian" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should contain following tags:
+      | folderTag |
+      | marketing |
+
+
+  Scenario Outline: The recipient of the shared resource tries to create a tag
+    Given user "Alice" has shared the following entity "folderMain" inside of space "use-tag" with user "Brian" with role "<role>"
+    And user "Brian" has accepted share "/folderMain" offered by user "Alice"
+    When user "Brian" creates the following tags for <resource> "<resourceName>" of space "Shares":
+      | tag in a shared resource |
+      | second tag               |
+    Then the HTTP status code should be "<code>"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response <shouldOrNot> contain following tags:
+      | tag in a shared resource |
+      | second tag               |
+    Examples:
+      | role    | resource | resourceName                   | code | shouldOrNot |
+      | viewer  | file     | folderMain/insideTheFolder.txt | 403  | should not  |
+      | editor  | file     | folderMain/insideTheFolder.txt | 200  | should      |
+      | manager | file     | folderMain/insideTheFolder.txt | 200  | should      |
+      | viewer  | folder   | folderMain                     | 403  | should not  |
+      | editor  | folder   | folderMain                     | 200  | should      |
+      | manager | folder   | folderMain                     | 200  | should      |
+
+
+  Scenario Outline: The recipient of the shared resource tries to remove a tag
+    Given user "Alice" has shared the following entity "folderMain" inside of space "use-tag" with user "Brian" with role "<role>"
+    And user "Alice" has created the following tags for <resource> "<resourceName>" of the space "use-tag":
+      | tag in a shared resource |
+      | second tag               |
+    And user "Brian" has accepted share "/folderMain" offered by user "Alice"
+    When user "Brian" removes the following tags for <resource> "<resourceName>" of space "Shares":
+      | tag in a shared resource |
+      | second tag               |
+    Then the HTTP status code should be "<code>"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response <shouldOrNot> contain following tags:
+      | tag in a shared resource |
+      | second tag               |
+    Examples:
+      | role    | resource | resourceName                   | code | shouldOrNot |
+      | viewer  | file     | folderMain/insideTheFolder.txt | 403  | should      |
+      | editor  | file     | folderMain/insideTheFolder.txt | 200  | should not  |
+      | manager | file     | folderMain/insideTheFolder.txt | 200  | should not  |
+      | viewer  | folder   | folderMain                     | 403  | should      |
+      | editor  | folder   | folderMain                     | 200  | should not  |
+      | manager | folder   | folderMain                     | 200  | should not  |
+
+
+  Scenario: User removes folder tags
+    Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag":
+      | folderTag   |
+      | marketing   |
+      | development |
+    When user "Alice" removes the following tags for folder "folderMain" of space "use-tag":
+      | folderTag |
+      | marketing |
+    And user "Alice" sends PROPFIND request from the space "use-tag" to the resource "folderMain" using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the "PROPFIND" response should contain a space "use-tag" with these key and value pairs:
+      | key     | value       |
+      | oc:tags | development |
+
+
+  Scenario: User lists tags after deleting some folder tags
+    Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag":
+      | folderTag   |
+      | marketing   |
+      | development |
+    When user "Alice" removes the following tags for folder "folderMain" of space "use-tag":
+      | folderTag |
+      | marketing |
+    Then the HTTP status code should be "200"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should contain following tags:
+      | development |
+    And the response should not contain following tags:
+      | folderTag |
+      | marketing |
+
+
+  Scenario: User lists the tags after deleting a folder
+    Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag":
+      | folderTag |
+      | marketing |
+    When user "Alice" removes the folder "folderMain" from space "use-tag"
+    Then the HTTP status code should be "204"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should not contain following tags:
+      | folderTag |
+      | marketing |
+
+
+  Scenario: User lists the tags after deleting a space
+    Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag":
+      | folderTag |
+      | marketing |
+    When user "Alice" disables a space "use-tag"
+    Then the HTTP status code should be "204"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should contain following tags:
+      | folderTag |
+      | marketing |
+    When user "Alice" deletes a space "use-tag"
+    Then the HTTP status code should be "204"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should not contain following tags:
+      | folderTag |
+      | marketing |
+
+
+  Scenario: User lists the tags after restoring a deleted folder
+    Given user "Alice" has created the following tags for folder "folderMain" of the space "use-tag":
+      | folderTag |
+      | marketing |
+    And user "Alice" has removed the folder "folderMain" from space "use-tag"
+    When user "Alice" restores the folder "folderMain" from the trash of the space "use-tag" to "/folderMain"
+    Then the HTTP status code should be "201"
+    When user "Alice" lists all available tags via the GraphApi
+    Then the HTTP status code should be "200"
+    And the response should contain following tags:
+      | folderTag |
+      | marketing |
diff --git a/tests/acceptance/features/bootstrap/TagContext.php b/tests/acceptance/features/bootstrap/TagContext.php
index 60dd67e2bdd..f8610b01771 100644
--- a/tests/acceptance/features/bootstrap/TagContext.php
+++ b/tests/acceptance/features/bootstrap/TagContext.php
@@ -101,7 +101,7 @@ class TagContext implements Context {
 	}
 
 	/**
-	 * @Given /^user "([^"]*)" has created the following tags a (folder|file)\s?"([^"]*)" of the space "([^"]*)":$/
+	 * @Given /^user "([^"]*)" has created the following tags for (folder|file)\s?"([^"]*)" of the space "([^"]*)":$/
 	 *
 	 * @param string $user
 	 * @param string $fileOrFolder   (file|folder)
@@ -126,6 +126,8 @@ class TagContext implements Context {
 	 * @throws Exception
 	 */
 	public function theUserGetsAllAvailableTags(string $user):void {
+		// after creating or deleting tags, in some cases tags do not appear or disappear immediately, so we use waiting
+		sleep(1);
 		$this->featureContext->setResponse(
 			GraphHelper::getTags(
 				$this->featureContext->getBaseUrl(),
@@ -136,18 +138,58 @@ class TagContext implements Context {
 	}
 
 	/**
-	 * @Then the response should contain following tag(s):
+	 * @Then /^the response should (not|)\s?contain following tag(s):$/
 	 *
+	 * @param string    $shouldOrNot   (not|)
 	 * @param TableNode $table
 	 *
 	 * @return void
 	 * @throws Exception
 	 */
-	public function theFollowingTagsShouldExistForUser(TableNode $table):void {
+	public function theFollowingTagsShouldExistForUser(string $shouldOrNot, TableNode $table):void {
 		$rows = $table->getRows();
 		foreach ($rows as $row) {
 			$responseArray = $this->featureContext->getJsonDecodedResponse($this->featureContext->getResponse())['value'];
-			Assert::assertTrue(\in_array($row[0], $responseArray), "the response does not contain the tag $row[0]");
+			if ($shouldOrNot === "not") {
+				Assert::assertFalse(\in_array($row[0], $responseArray), "the response should not contain the tag $row[0]");
+			} else {
+				Assert::assertTrue(\in_array($row[0], $responseArray), "the response does not contain the tag $row[0]");
+			}
 		}
 	}
+
+	/**
+	 * @When /^user "([^"]*)" removes the following tags for (folder|file)\s?"([^"]*)" of space "([^"]*)":$/
+	 *
+	 * @param string $user
+	 * @param string $fileOrFolder   (file|folder)
+	 * @param string $resource
+	 * @param string $space
+	 * @param TableNode $table
+	 *
+	 * @return void
+	 * @throws Exception
+	 */
+	public function userRemovesTagsFromResourceOfTheSpace(string $user, string $fileOrFolder, string $resource, string $space, TableNode $table):void {
+		$tagNameArray = [];
+		foreach ($table->getRows() as $value) {
+			array_push($tagNameArray, $value[0]);
+		}
+
+		if ($fileOrFolder === 'folder') {
+			$resourceId = $this->spacesContext->getFolderId($user, $space, $resource);
+		} else {
+			$resourceId = $this->spacesContext->getFileId($user, $space, $resource);
+		}
+
+		$response = GraphHelper::deleteTags(
+			$this->featureContext->getBaseUrl(),
+			$this->featureContext->getStepLineRef(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$resourceId,
+			$tagNameArray
+		);
+		$this->featureContext->setResponse($response);
+	}
 }
diff --git a/tests/acceptance/features/bootstrap/WebDav.php b/tests/acceptance/features/bootstrap/WebDav.php
index d966840cd86..af9a0b1212c 100644
--- a/tests/acceptance/features/bootstrap/WebDav.php
+++ b/tests/acceptance/features/bootstrap/WebDav.php
@@ -5497,13 +5497,23 @@ trait WebDav {
 		}
 
 		// the username should be in oc:meta-version-edited-by
-		$xmlPart = $resXml->xpath("//oc:meta-version-edited-by//text()");
-		if (!isset($xmlPart[$index - 1])) {
+		$xmlPart = $resXml->xpath("//oc:meta-version-edited-by");
+		$authors = [];
+		foreach ($xmlPart as $idx => $author) {
+			// The first element is the root path element which is not a version
+			// So skipping it
+			if ($idx !== 0) {
+				$authors[] = $author->__toString();
+			}
+		}
+		// reverse the array to get the latest version first
+		\array_reverse($authors);
+		if (!isset($authors[$index - 1])) {
 			Assert::fail(
 				'could not find version with index "' . $index . '" for oc:meta-version-edited-by property in response to user "' . $this->responseUser . '"'
 			);
 		}
-		$actualUser = $xmlPart[$index - 1][0];
+		$actualUser = $authors[$index - 1];
 		Assert::assertEquals(
 			$expectedUsername,
 			$actualUser,
@@ -5511,17 +5521,27 @@ trait WebDav {
 		);
 
 		// the user's display name should be in oc:meta-version-edited-by-name
-		$xmlPart = $resXml->xpath("//oc:meta-version-edited-by-name//text()");
-		if (!isset($xmlPart[$index - 1])) {
+		$xmlPart = $resXml->xpath("//oc:meta-version-edited-by-name");
+		$displaynames = [];
+		foreach ($xmlPart as $idx => $displayname) {
+			// The first element is the root path element which is not a version
+			// So skipping it
+			if ($idx !== 0) {
+				$displaynames[] = $displayname->__toString();
+			}
+		}
+		// reverse the array to get the latest version first
+		\array_reverse($displaynames);
+		if (!isset($displaynames[$index - 1])) {
 			Assert::fail(
 				'could not find version with index "' . $index . '" for oc:meta-version-edited-by-name property in response to user "' . $this->responseUser . '"'
 			);
 		}
-		$actualUserDisplayName = $xmlPart[$index - 1][0];
+		$actualUserDisplayName = $displaynames[$index - 1];
 		Assert::assertEquals(
 			$expectedUserDisplayName,
 			$actualUserDisplayName,
-			"Expected display name of version with index $index in response to user '$this->responseUser' was '$expectedUsername', but got '$actualUser'"
+			"Expected display name of version with index $index in response to user '$this->responseUser' was '$expectedUserDisplayName', but got '$actualUserDisplayName'"
 		);
 	}
 }
diff --git a/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature
index f1dc0b51f57..848da56477d 100644
--- a/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature
+++ b/tests/acceptance/features/coreApiSharePublicLink3/updatePublicLinkShare.feature
@@ -610,3 +610,28 @@ Feature: update a public link share
       | dav-path | ocs_api_version | ocs_status_code |
       | spaces   | 1               | 100             |
       | spaces   | 2               | 200             |
+
+
+  Scenario Outline: Updating the role of a public link to internal
+    Given using OCS API version "<ocs_api_version>"
+    And using <dav-path> DAV path
+    And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/textfile.txt"
+    And user "Alice" has created a public link share with settings
+      | path        | /textfile.txt |
+      | permissions | read          |
+    When user "Alice" updates the last public link share using the sharing API with
+      | permissions | 0 |
+    Then the OCS status code should be "<ocs_status_code>"
+    And the HTTP status code should be "200"
+    Examples:
+      | dav-path | ocs_api_version | ocs_status_code |
+      | old      | 1               | 100             |
+      | old      | 2               | 200             |
+      | new      | 1               | 100             |
+      | new      | 2               | 200             |
+
+    @personalSpace
+    Examples:
+      | dav-path | ocs_api_version | ocs_status_code |
+      | spaces   | 1               | 100             |
+      | spaces   | 2               | 200             |
diff --git a/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature b/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature
index dd04dbbaecc..d04ffcbf743 100644
--- a/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature
+++ b/tests/acceptance/features/coreApiVersions/fileVersionAuthor.feature
@@ -1,5 +1,4 @@
-@api @files_versions-app-required @issue-ocis-reva-275
-
+@api @issue-2914
 Feature: file versions remember the author of each version
 
   Background:
@@ -10,30 +9,38 @@ Feature: file versions remember the author of each version
     And user "Carol" has been created with default attributes and without skeleton files
     And the administrator has enabled the file version storage feature
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes from multiple users
     Given user "David" has been created with default attributes and without skeleton files
     And user "Alice" has created folder "/test"
     And user "Alice" has shared folder "/test" with user "Brian" with permissions "all"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
     And user "Alice" has shared folder "/test" with user "Carol" with permissions "all"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
     And user "Alice" has shared folder "/test" with user "David" with permissions "all"
+    And user "David" has accepted share "/test" offered by user "Alice"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt"
-    And user "David" has uploaded file with content "uploaded content david" to "/test/textfile0.txt"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/test/textfile0.txt"
+    And user "David" has uploaded file with content "uploaded content david" to "/Shares/test/textfile0.txt"
     When user "Alice" gets the number of versions of file "/test/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "3"
     And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol"
     And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,Brian,Carol,David" the authors of the versions of file "/test/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Brian  |
+      | 3     | Alice  |
+    And as users "Brian,Carol,David" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
       | 3     | Alice  |
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes from multiple users for shared folder in the group
     Given user "Alice" has created folder "/test"
     And group "grp1" has been created
@@ -41,20 +48,26 @@ Feature: file versions remember the author of each version
     And user "Brian" has been added to group "grp1"
     And user "Carol" has been added to group "grp1"
     And user "Alice" has shared folder "/test" with group "grp1"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/test/textfile0.txt"
     When user "Alice" gets the number of versions of file "/test/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "2"
     And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,Brian,Carol" the authors of the versions of file "/test/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+      | 2     | Alice  |
+    And as users "Brian,Carol" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
       | index | author |
       | 1     | Brian  |
       | 2     | Alice  |
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes from multiple users for shared file in the group
     Given group "grp1" has been created
     And user "Alice" has been added to group "grp1"
@@ -62,19 +75,25 @@ Feature: file versions remember the author of each version
     And user "Carol" has been added to group "grp1"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/textfile0.txt"
     And user "Alice" has shared file "/textfile0.txt" with group "grp1"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/textfile0.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/textfile0.txt"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
+    And user "Carol" has accepted share "/textfile0.txt" offered by user "Alice"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/textfile0.txt"
     When user "Alice" gets the number of versions of file "textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "2"
     And the content of version index "1" of file "/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "2" of file "/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,Brian,Carol" the authors of the versions of file "/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/textfile0.txt" should be:
       | index | author |
       | 1     | Brian  |
       | 2     | Alice  |
+    And as users "Brian,Carol" the authors of the versions of file "/Shares/textfile0.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+      | 2     | Alice  |
+
 
-  @skip_on_objectstore @skipOnEncryption @issue-encryption-321
   Scenario: enable file versioning and check the history of changes from multiple users while moving file in/out of a subfolder
     Given user "Alice" has created folder "/test"
     And group "grp1" has been created
@@ -82,25 +101,32 @@ Feature: file versions remember the author of each version
     And user "Brian" has been added to group "grp1"
     And user "Carol" has been added to group "grp1"
     And user "Alice" has shared folder "/test" with group "grp1"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt"
-    And user "Brian" has moved file "/test/textfile0.txt" to "/textfile0.txt"
-    And user "Brian" has uploaded file with content "uploaded content brian after moving file outside subfolder" to "/textfile0.txt"
-    And user "Brian" has moved file "/textfile0.txt" to "/test/textfile0.txt"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/test/textfile0.txt"
+    And user "Alice" has moved file "/test/textfile0.txt" to "/textfile0.txt"
+    And user "Alice" has uploaded file with content "uploaded content alice after moving file outside subfolder" to "/textfile0.txt"
+    And user "Alice" has moved file "/textfile0.txt" to "/test/textfile0.txt"
     When user "Alice" gets the number of versions of file "/test/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "3"
     And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol"
     And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,Brian,Carol" the authors of the versions of file "/test/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
       | 3     | Alice  |
+    And as users "Brian,Carol" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Brian  |
+      | 3     | Alice  |
+
 
-  @skip_on_objectstore
   Scenario: enable file versioning and check the history of changes from multiple users after renaming file by sharer
     Given group "grp1" has been created
     And user "Alice" has been added to group "grp1"
@@ -108,8 +134,10 @@ Feature: file versions remember the author of each version
     And user "Carol" has been added to group "grp1"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/exist.txt"
     And user "Alice" has shared file "/exist.txt" with group "grp1"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/exist.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/exist.txt"
+    And user "Brian" has accepted share "/exist.txt" offered by user "Alice"
+    And user "Carol" has accepted share "/exist.txt" offered by user "Alice"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/exist.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/exist.txt"
     And user "Alice" has moved file "/exist.txt" to "/textfile0.txt"
     When user "Alice" gets the number of versions of file "textfile0.txt"
     Then the HTTP status code should be "207"
@@ -120,12 +148,12 @@ Feature: file versions remember the author of each version
       | index | author |
       | 1     | Brian  |
       | 2     | Alice  |
-    And as users "Brian,Carol" the authors of the versions of file "/exist.txt" should be:
+    And as users "Brian,Carol" the authors of the versions of file "/Shares/exist.txt" should be:
       | index | author |
       | 1     | Brian  |
       | 2     | Alice  |
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes in sharer after renaming file by sharee
     Given group "grp1" has been created
     And user "Alice" has been added to group "grp1"
@@ -133,24 +161,30 @@ Feature: file versions remember the author of each version
     And user "Carol" has been added to group "grp1"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/exist.txt"
     And user "Alice" has shared file "/exist.txt" with group "grp1"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/exist.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/exist.txt"
-    And user "Brian" has moved file "/exist.txt" to "/textfile0.txt"
+    And user "Brian" has accepted share "/exist.txt" offered by user "Alice"
+    And user "Carol" has accepted share "/exist.txt" offered by user "Alice"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/exist.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/exist.txt"
+    And user "Brian" has moved file "/Shares/exist.txt" to "/Shares/textfile0.txt"
     When user "Alice" gets the number of versions of file "exist.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "2"
     And the content of version index "1" of file "/exist.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "2" of file "/exist.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,Carol" the authors of the versions of file "/exist.txt" should be:
+    And as users "Alice" the authors of the versions of file "/exist.txt" should be:
       | index | author |
       | 1     | Brian  |
       | 2     | Alice  |
-    And as user "Brian" the authors of the versions of file "/textfile0.txt" should be:
+    And as users "Carol" the authors of the versions of file "/Shares/exist.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+      | 2     | Alice  |
+    And as user "Brian" the authors of the versions of file "/Shares/textfile0.txt" should be:
       | index | author |
       | 1     | Brian  |
       | 2     | Alice  |
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes from multiple users when reshared after unshared by sharer
     Given user "Alice" has created folder "/test"
     And group "grp1" has been created
@@ -158,52 +192,70 @@ Feature: file versions remember the author of each version
     And user "Brian" has been added to group "grp1"
     And user "Carol" has been added to group "grp1"
     And user "Alice" has shared folder "/test" with group "grp1"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/test/textfile0.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/test/textfile0.txt"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/test/textfile0.txt"
     And user "Alice" has deleted the last share
     And user "Alice" has uploaded file with content "uploaded content alice after unshared folder" to "/test/textfile0.txt"
     And user "Alice" has shared folder "/test" with group "grp1"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
     When user "Alice" gets the number of versions of file "/test/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "3"
     And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol"
     And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,Brian,Carol" the authors of the versions of file "/test/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Brian  |
+      | 3     | Alice  |
+    And as users "Brian,Carol" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
       | 3     | Alice  |
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes from multiple users who have a matching folder/file
     Given user "David" has been created with default attributes and without skeleton files
     And user "Brian" has created folder "/test"
     And user "Brian" has uploaded file with content "duplicate brian" to "/test/textfile0.txt"
     And user "Brian" has uploaded file with content "overwrite brian" to "/test/textfile0.txt"
+    And user "Brian" has shared folder "/test" with user "Carol" with permissions "all"
+    And user "Carol" has accepted share "/test" offered by user "Brian"
+    And user "Brian" has shared folder "/test" with user "David" with permissions "all"
+    And user "David" has accepted share "/test" offered by user "Brian"
     And user "Carol" has created folder "/test"
     And user "Carol" has uploaded file with content "duplicate carol" to "/test/textfile0.txt"
+    And user "Carol" has shared folder "/test" with user "Brian" with permissions "all"
+    And user "Brian" has accepted share "/test" offered by user "Carol"
     And user "Alice" has created folder "/test"
     And user "Alice" has shared folder "/test" with user "Brian" with permissions "all"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
     And user "Alice" has shared folder "/test" with user "Carol" with permissions "all"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
     And user "Alice" has shared folder "/test" with user "David" with permissions "all"
+    And user "David" has accepted share "/test" offered by user "Alice"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/test (2)/textfile0.txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/test (2)/textfile0.txt"
-    And user "David" has uploaded file with content "uploaded content david" to "/test/textfile0.txt"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test (1)/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/test (1)/textfile0.txt"
+    And user "David" has uploaded file with content "uploaded content david" to "/Shares/test (1)/textfile0.txt"
     When user "Alice" gets the number of versions of file "/test/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "3"
     And the content of version index "1" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content carol"
     And the content of version index "2" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "3" of file "/test/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,David" the authors of the versions of file "/test/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
       | 3     | Alice  |
-    And as users "Brian,Carol" the authors of the versions of file "/test (2)/textfile0.txt" should be:
+    And as users "Brian,Carol,David" the authors of the versions of file "/Shares/test (1)/textfile0.txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
@@ -219,31 +271,40 @@ Feature: file versions remember the author of each version
     Then the HTTP status code should be "207"
     And the number of versions should be "0"
 
-  @skip_on_objectstore
+
   Scenario: enable file versioning and check the history of changes from multiple users who have a matching file
     Given user "David" has been created with default attributes and without skeleton files
     And user "Brian" has uploaded file with content "duplicate brian" to "/textfile0.txt"
     And user "Brian" has uploaded file with content "overwrite brian" to "/textfile0.txt"
+    And user "Brian" has shared file "/textfile0.txt" with user "Carol"
+    And user "Carol" has accepted share "/textfile0.txt" offered by user "Brian"
+    And user "Brian" has shared file "/textfile0.txt" with user "David"
+    And user "David" has accepted share "/textfile0.txt" offered by user "Brian"
     And user "Carol" has uploaded file with content "duplicate carol" to "/textfile0.txt"
+    And user "Carol" has shared file "/textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Carol"
     And user "Alice" has uploaded file with content "uploaded content alice" to "/textfile0.txt"
     And user "Alice" has shared file "/textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
     And user "Alice" has shared file "/textfile0.txt" with user "Carol"
+    And user "Carol" has accepted share "/textfile0.txt" offered by user "Alice"
     And user "Alice" has shared file "/textfile0.txt" with user "David"
-    And user "Brian" has uploaded file with content "uploaded content brian" to "/textfile0 (2).txt"
-    And user "Carol" has uploaded file with content "uploaded content carol" to "/textfile0 (2).txt"
-    And user "David" has uploaded file with content "uploaded content david" to "/textfile0.txt"
+    And user "David" has accepted share "/textfile0.txt" offered by user "Alice"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/textfile0 (1).txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/textfile0 (1).txt"
+    And user "David" has uploaded file with content "uploaded content david" to "/Shares/textfile0 (1).txt"
     When user "Alice" gets the number of versions of file "/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "3"
     And the content of version index "1" of file "/textfile0.txt" for user "Alice" should be "uploaded content carol"
     And the content of version index "2" of file "/textfile0.txt" for user "Alice" should be "uploaded content brian"
     And the content of version index "3" of file "/textfile0.txt" for user "Alice" should be "uploaded content alice"
-    And as users "Alice,David" the authors of the versions of file "/textfile0.txt" should be:
+    And as users "Alice" the authors of the versions of file "/textfile0.txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
       | 3     | Alice  |
-    And as users "Brian,Carol" the authors of the versions of file "/textfile0 (2).txt" should be:
+    And as users "Brian,Carol,David" the authors of the versions of file "/Shares/textfile0 (1).txt" should be:
       | index | author |
       | 1     | Carol  |
       | 2     | Brian  |
@@ -258,3 +319,95 @@ Feature: file versions remember the author of each version
     When user "Carol" gets the number of versions of file "/textfile0.txt"
     Then the HTTP status code should be "207"
     And the number of versions should be "0"
+
+
+  Scenario: enable file versioning and check the version author after restoring a version of a file inside a folder
+    Given user "Alice" has created folder "/test"
+    And user "Alice" has shared folder "/test" with user "Brian" with permissions "all"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
+    And user "Alice" has shared folder "/test" with user "Carol" with permissions "all"
+    And user "Carol" has accepted share "/test" offered by user "Alice"
+    And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/test/textfile0.txt"
+    When user "Brian" restores version index "1" of file "/Shares/test/textfile0.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And as user "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Alice  |
+    And as user "Brian,Carol" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Alice  |
+
+
+  Scenario: enable file versioning and check the version author after restoring a version of a file
+    Given user "Alice" has uploaded file with content "uploaded content alice" to "/textfile0.txt"
+    And user "Alice" has shared file "/textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
+    And user "Alice" has shared file "/textfile0.txt" with user "Carol"
+    And user "Carol" has accepted share "/textfile0.txt" offered by user "Alice"
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/textfile0.txt"
+    And user "Carol" has uploaded file with content "uploaded content carol" to "/Shares/textfile0.txt"
+    When user "Brian" restores version index "1" of file "/Shares/textfile0.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And as user "Alice" the authors of the versions of file "/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Alice  |
+    And as user "Brian,Carol" the authors of the versions of file "/Shares/textfile0.txt" should be:
+      | index | author |
+      | 1     | Carol  |
+      | 2     | Alice  |
+
+
+  Scenario: check the author of the file version which was created before enabling the version storage
+    Given the administrator has disabled the file version storage feature
+    And user "Alice" has uploaded file with content "uploaded content alice" to "/textfile0.txt"
+    And user "Alice" has shared folder "/textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
+    And the administrator has enabled the file version storage feature
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/textfile0.txt"
+    When user "Brian" restores version index "1" of file "/Shares/textfile0.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And as user "Alice" the authors of the versions of file "/textfile0.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+    And as user "Brian" the authors of the versions of file "/Shares/textfile0.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+    When user "Brian" restores version index "1" of file "/Shares/textfile0.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And as user "Alice" the authors of the versions of file "/textfile0.txt" should be:
+      | index | author |
+      | 1     |        |
+    And as user "Brian" the authors of the versions of file "/Shares/textfile0.txt" should be:
+      | index | author |
+      | 1     |        |
+
+
+  Scenario: check the author of the file version (inside a folder) which was created before enabling the version storage
+    Given user "Alice" has created folder "/test"
+    And the administrator has disabled the file version storage feature
+    And user "Alice" has uploaded file with content "uploaded content alice" to "/test/textfile0.txt"
+    And user "Alice" has shared folder "/test" with user "Brian" with permissions "all"
+    And user "Brian" has accepted share "/test" offered by user "Alice"
+    And the administrator has enabled the file version storage feature
+    And user "Brian" has uploaded file with content "uploaded content brian" to "/Shares/test/textfile0.txt"
+    When user "Brian" restores version index "1" of file "/Shares/test/textfile0.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And as user "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+    And as user "Brian" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
+      | index | author |
+      | 1     | Brian  |
+    When user "Brian" restores version index "1" of file "/Shares/test/textfile0.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And as user "Alice" the authors of the versions of file "/test/textfile0.txt" should be:
+      | index | author |
+      | 1     |        |
+    And as user "Brian" the authors of the versions of file "/Shares/test/textfile0.txt" should be:
+      | index | author |
+      | 1     |        |
\ No newline at end of file
diff --git a/tests/acceptance/features/coreApiVersions/fileVersions.feature b/tests/acceptance/features/coreApiVersions/fileVersions.feature
index 38a5ef2a7aa..4d4f84ea667 100644
--- a/tests/acceptance/features/coreApiVersions/fileVersions.feature
+++ b/tests/acceptance/features/coreApiVersions/fileVersions.feature
@@ -21,19 +21,6 @@ Feature: dav-versions
     And the version folder of file "/davtest.txt-newdav-regular" for user "Alice" should contain "0" elements
     And the version folder of file "/davtest.txt-olddav-oldchunking" for user "Alice" should contain "0" elements
 
-  @issue-ocis-reva-17 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321
-  Scenario: Upload file and no version is available using new chunking
-    When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API
-    Then the HTTP status code should be "201"
-    And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements
-
-  @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321
-  Scenario: Upload file and no version is available using async upload
-    Given the administrator has enabled async operations
-    When user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "/davtest.txt" in 3 chunks with new chunking and using the WebDAV API
-    Then the HTTP status code should be "202"
-    And the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements
-
   @smokeTest
   Scenario: Upload a file twice and versions are available
     When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API
@@ -51,21 +38,6 @@ Feature: dav-versions
     And the version folder of file "/davtest.txt-newdav-regular" for user "Alice" should contain "1" element
     And the version folder of file "/davtest.txt-olddav-oldchunking" for user "Alice" should contain "1" element
 
-  @issue-ocis-reva-17 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321
-  Scenario: Upload a file twice and versions are available using new chunking
-    When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API
-    And user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API
-    Then the HTTP status code of responses on each endpoint should be "201, 204" respectively
-    And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element
-
-  @issue-ocis-reva-17 @issue-ocis-reva-56 @notToImplementOnOCIS @newChunking @issue-ocis-1321
-  Scenario: Upload a file twice and versions are available using async upload
-    Given the administrator has enabled async operations
-    When user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "/davtest.txt" in 2 chunks with new chunking and using the WebDAV API
-    And user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "/davtest.txt" in 3 chunks with new chunking and using the WebDAV API
-    Then the HTTP status code of responses on all endpoints should be "202"
-    And the version folder of file "/davtest.txt" for user "Alice" should contain "1" element
-
   @smokeTest
   Scenario: Remove a file
     Given user "Alice" has uploaded file "filesForUpload/davtest.txt" to "/davtest.txt"
@@ -109,21 +81,6 @@ Feature: dav-versions
     Examples:
       | dav-path | status-code |
       | old      | 201         |
-    @notToImplementOnOCIS @newChunking @issue-ocis-1321
-    Examples:
-      | dav-path | status-code |
-      | new      | 204         |
-
-  @skipOnStorage:ceph @files_primary_s3-issue-161 @notToImplementOnOCIS @newChunking @issue-ocis-1321 @issue-ocis-reva-17 @issue-ocis-reva-56 @skipOnStorage:scality
-  Scenario: Uploading a file asynchronously does create the correct version that can be restored
-    Given the administrator has enabled async operations
-    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
-    When user "Alice" uploads file "filesForUpload/davtest.txt" asynchronously to "textfile0.txt" in 2 chunks using the WebDAV API
-    And user "Alice" uploads file "filesForUpload/lorem.txt" asynchronously to "textfile0.txt" in 2 chunks using the WebDAV API
-    And user "Alice" restores version index "1" of file "/textfile0.txt" using the WebDAV API
-    Then the HTTP status code of responses on all endpoints should be "202"
-    And the version folder of file "/textfile0.txt" for user "Alice" should contain "2" elements
-    And the content of file "/textfile0.txt" for user "Alice" should be "Dav-Test"
 
   @skipOnStorage:ceph @skipOnStorage:scality @files_primary_s3-issue-156
   Scenario: Restore a file and check, if the content and correct checksum is now in the current file
@@ -143,246 +100,6 @@ Feature: dav-versions
     When user "Brian" sends HTTP method "PROPFIND" to URL "/remote.php/dav/meta/<<FILEID>>"
     Then the HTTP status code should be "400" or "404"
 
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: User can access meta folder of a file which is owned by somebody else but shared with that user
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "123" to "/davtest.txt"
-    And user "Alice" has uploaded file with content "456789" to "/davtest.txt"
-    And we save it into "FILEID"
-    When user "Alice" creates a share using the sharing API with settings
-      | path        | /davtest.txt |
-      | shareType   | user         |
-      | shareWith   | Brian        |
-      | permissions | read         |
-    Then the HTTP status code should be "200"
-    And the version folder of fileId "<<FILEID>>" for user "Brian" should contain "1" element
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharer of a file can see the old version information when the sharee changes the content of the file
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
-    And user "Alice" has shared file "sharefile.txt" with user "Brian"
-    When user "Brian" has uploaded file with content "Second content" to "/sharefile.txt"
-    Then the HTTP status code should be "204"
-    And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharer of a file can restore the original content of a shared file after the file has been modified by the sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
-    And user "Alice" has shared file "sharefile.txt" with user "Brian"
-    And user "Brian" has uploaded file with content "Second content" to "/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharer can restore a file inside a shared folder modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a file inside a shared folder modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a file inside a shared folder created by sharee and modified by sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer, when the folder has been moved by the sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has created folder "/received"
-    And user "Brian" has moved folder "/sharingfolder" to "/received/sharingfolder"
-    When user "Brian" restores version index "1" of file "/received/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/received/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is at the top level of the sharer)
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "old content" to "/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharefile.txt"
-    And user "Alice" has shared file "/sharefile.txt" with user "Brian"
-    And user "Brian" has created folder "/received"
-    And user "Brian" has moved file "/sharefile.txt" to "/received/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/received/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is inside a folder of the sharer)
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has shared file "/sharingfolder/sharefile.txt" with user "Brian"
-    And user "Brian" has created folder "/received"
-    And user "Brian" has moved file "/sharefile.txt" to "/received/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/received/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @issue-ocis-1289 @issue-ocis-1321 @notToImplementOnOCIS
-  Scenario: sharer can restore a file inside a group shared folder modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Carol" has been created with default attributes and without skeleton files
-    And group "grp1" has been created
-    And user "Brian" has been added to group "grp1"
-    And user "Carol" has been added to group "grp1"
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with group "grp1"
-    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    And user "Carol" has uploaded file with content "Third content" to "/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "2" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Carol" should be "First content"
-
-  @files_sharing-app-required @notToImplementOnOCIS @issue-ocis-1238
-  Scenario Outline: Moving a file (with versions) into a shared folder as the sharee and as the sharer
-    Given using <dav_version> DAV path
-    And user "Brian" has been created with default attributes and without skeleton files
-    And user "Brian" has created folder "/testshare"
-    And user "Brian" has created a share with settings
-      | path        | testshare |
-      | shareType   | user      |
-      | permissions | change    |
-      | shareWith   | Alice     |
-    And user "<mover>" has uploaded file with content "test data 1" to "/testfile.txt"
-    And user "<mover>" has uploaded file with content "test data 2" to "/testfile.txt"
-    And user "<mover>" has uploaded file with content "test data 3" to "/testfile.txt"
-    When user "<mover>" moves file "/testfile.txt" to "/testshare/testfile.txt" using the WebDAV API
-    Then the HTTP status code should be "201"
-    And the content of file "/testshare/testfile.txt" for user "Alice" should be "test data 3"
-    And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data 3"
-    And as "<mover>" file "/testfile.txt" should not exist
-    And the version folder of file "/testshare/testfile.txt" for user "Alice" should contain "2" elements
-    And the version folder of file "/testshare/testfile.txt" for user "Brian" should contain "2" elements
-    Examples:
-      | dav_version | mover |
-      | old         | Alice |
-      | new         | Alice |
-      | old         | Brian |
-      | new         | Brian |
-
-  @files_sharing-app-required @notToImplementOnOCIS @issue-ocis-1238
-  Scenario Outline: Moving a file (with versions) out of a shared folder as the sharee and as the sharer
-    Given using <dav_version> DAV path
-    And user "Brian" has been created with default attributes and without skeleton files
-    And user "Brian" has created folder "/testshare"
-    And user "Brian" has uploaded file with content "test data 1" to "/testshare/testfile.txt"
-    And user "Brian" has uploaded file with content "test data 2" to "/testshare/testfile.txt"
-    And user "Brian" has uploaded file with content "test data 3" to "/testshare/testfile.txt"
-    And user "Brian" has created a share with settings
-      | path        | testshare |
-      | shareType   | user      |
-      | permissions | change    |
-      | shareWith   | Alice     |
-    When user "<mover>" moves file "/testshare/testfile.txt" to "/testfile.txt" using the WebDAV API
-    Then the HTTP status code should be "201"
-    And the content of file "/testfile.txt" for user "<mover>" should be "test data 3"
-    And as "Alice" file "/testshare/testfile.txt" should not exist
-    And as "Brian" file "/testshare/testfile.txt" should not exist
-    And the version folder of file "/testfile.txt" for user "<mover>" should contain "2" elements
-    Examples:
-      | dav_version | mover |
-      | old         | Alice |
-      | new         | Alice |
-      | old         | Brian |
-      | new         | Brian |
-
-  @files_sharing-app-required @issue-ocis-1238 @notToImplementOnOCIS
-  Scenario: Receiver tries to get file versions of unshared file from the sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "textfile1" to "textfile1.txt"
-    And user "Alice" has shared file "textfile0.txt" with user "Brian"
-    When user "Brian" tries to get versions of file "textfile1.txt" from "Alice"
-    Then the HTTP status code should be "404"
-    And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound"
-
-  @skipOnStorage:ceph @files_primary_s3-issue-161 @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: Receiver tries get file versions of shared file from the sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 3" to "textfile0.txt"
-    And user "Alice" has shared file "textfile0.txt" with user "Brian"
-    When user "Brian" tries to get versions of file "textfile0.txt" from "Alice"
-    Then the HTTP status code should be "207"
-    And the number of versions should be "3"
-
 
   Scenario: User cannot access meta folder of a file which does not exist
     Given user "Brian" has been created with default attributes and without skeleton files
@@ -553,4 +270,259 @@ Feature: dav-versions
     And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "file.txt" with mtime "Thu, 08 Aug 2019 04:18:13 GMT" using the WebDAV API
     When user "Alice" restores version index "1" of file "/file.txt" using the WebDAV API
     Then the HTTP status code should be "204"
-    And the version folder of file "/file.txt" for user "Alice" should contain "0" element
\ No newline at end of file
+    And the version folder of file "/file.txt" for user "Alice" should contain "0" element
+
+  @files_sharing-app-required
+  Scenario: User can access meta folder of a file which is owned by somebody else but shared with that user
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "123" to "/davtest.txt"
+    And user "Alice" has uploaded file with content "456789" to "/davtest.txt"
+    And we save it into "FILEID"
+    And user "Alice" has created a share with settings
+      | path        | /davtest.txt |
+      | shareType   | user         |
+      | shareWith   | Brian        |
+      | permissions | read         |
+    When user "Brian" accepts share "/davtest.txt" offered by user "Alice" using the sharing API
+    Then the HTTP status code should be "200"
+    And the version folder of fileId "<<FILEID>>" for user "Brian" should contain "1" element
+
+  @files_sharing-app-required
+  Scenario: sharer of a file can see the old version information when the sharee changes the content of the file
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
+    And user "Alice" has shared file "sharefile.txt" with user "Brian"
+    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
+    When user "Brian" uploads file with content "Second content" to "/Shares/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the version folder of file "/Shares/sharefile.txt" for user "Brian" should contain "1" element
+    And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element
+
+  @files_sharing-app-required
+  Scenario: sharer of a file can restore the original content of a shared file after the file has been modified by the sharee
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
+    And user "Alice" has shared file "sharefile.txt" with user "Brian"
+    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
+    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharefile.txt"
+    When user "Alice" restores version index "1" of file "/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharefile.txt" for user "Alice" should be "First content"
+    And the content of file "/Shares/sharefile.txt" for user "Brian" should be "First content"
+
+  @files_sharing-app-required
+  Scenario: sharer can restore a file inside a shared folder modified by sharee
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
+    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt"
+    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
+
+  @files_sharing-app-required
+  Scenario: sharee can restore a file inside a shared folder modified by sharee
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
+    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt"
+    When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
+
+  @files_sharing-app-required
+  Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharer
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Brian" has uploaded file with content "First content" to "/Shares/sharingfolder/sharefile.txt"
+    And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
+    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
+
+  @files_sharing-app-required
+  Scenario: sharee can restore a file inside a shared folder created by sharee and modified by sharer
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Brian" has uploaded file with content "First content" to "/Shares/sharingfolder/sharefile.txt"
+    And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
+    When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
+
+  @files_sharing-app-required
+  Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharee
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Brian" has uploaded file with content "old content" to "/Shares/sharingfolder/sharefile.txt"
+    And user "Brian" has uploaded file with content "new content" to "/Shares/sharingfolder/sharefile.txt"
+    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
+
+  @files_sharing-app-required
+  Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
+    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
+    When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
+
+  @files_sharing-app-required @issue-ocis-reva-34
+  Scenario: sharer can restore a file inside a group shared folder modified by sharee
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Carol" has been created with default attributes and without skeleton files
+    And group "grp1" has been created
+    And user "Brian" has been added to group "grp1"
+    And user "Carol" has been added to group "grp1"
+    And user "Alice" has created folder "/sharingfolder"
+    And user "Alice" has shared folder "/sharingfolder" with group "grp1"
+    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Carol" has accepted share "/sharingfolder" offered by user "Alice"
+    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
+    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt"
+    And user "Carol" has uploaded file with content "Third content" to "/Shares/sharingfolder/sharefile.txt"
+    When user "Alice" restores version index "2" of file "/sharingfolder/sharefile.txt" using the WebDAV API
+    Then the HTTP status code should be "204"
+    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
+    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Carol" should be "First content"
+
+  @files_sharing-app-required @issue-ocis-reva-386
+  Scenario Outline: Moving a file (with versions) into a shared folder as the sharee and as the sharer
+    Given using <dav_version> DAV path
+    And user "Brian" has been created with default attributes and without skeleton files
+    And user "Brian" has created folder "/testshare"
+    And user "Brian" has created a share with settings
+      | path        | testshare |
+      | shareType   | user      |
+      | permissions | change    |
+      | shareWith   | Alice     |
+    And user "Alice" has accepted share "/testshare" offered by user "Brian"
+    And user "<mover>" has uploaded file with content "test data 1" to "/testfile.txt"
+    And user "<mover>" has uploaded file with content "test data 2" to "/testfile.txt"
+    And user "<mover>" has uploaded file with content "test data 3" to "/testfile.txt"
+    When user "<mover>" moves file "/testfile.txt" to "<dst-folder>/testfile.txt" using the WebDAV API
+    Then the HTTP status code should be "201"
+    And the content of file "/Shares/testshare/testfile.txt" for user "Alice" should be "test data 3"
+    And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data 3"
+    And as "<mover>" file "/testfile.txt" should not exist
+    And the version folder of file "/Shares/testshare/testfile.txt" for user "Alice" should contain "2" elements
+    And the version folder of file "/testshare/testfile.txt" for user "Brian" should contain "2" elements
+    Examples:
+      | dav_version | mover | dst-folder        |
+      | old         | Brian | /testshare        |
+      | new         | Brian | /testshare        |
+
+  @files_sharing-app-required @issue-ocis-reva-386
+  Scenario Outline: Moving a file (with versions) out of a shared folder as the sharee and as the sharer
+    Given using <dav_version> DAV path
+    And user "Brian" has been created with default attributes and without skeleton files
+    And user "Brian" has created folder "/testshare"
+    And user "Brian" has uploaded file with content "test data 1" to "/testshare/testfile.txt"
+    And user "Brian" has uploaded file with content "test data 2" to "/testshare/testfile.txt"
+    And user "Brian" has uploaded file with content "test data 3" to "/testshare/testfile.txt"
+    And user "Brian" has created a share with settings
+      | path        | testshare |
+      | shareType   | user      |
+      | permissions | change    |
+      | shareWith   | Alice     |
+    And user "Alice" has accepted share "/testshare" offered by user "Brian"
+    When user "<mover>" moves file "<src-folder>/testfile.txt" to "/testfile.txt" using the WebDAV API
+    Then the HTTP status code should be "201"
+    And the content of file "/testfile.txt" for user "<mover>" should be "test data 3"
+    And as "Alice" file "/Shares/testshare/testfile.txt" should not exist
+    And as "Brian" file "/testshare/testfile.txt" should not exist
+    And the version folder of file "/testfile.txt" for user "<mover>" should contain "2" elements
+    Examples:
+      | dav_version | mover | src-folder |
+      | old         | Brian | /testshare |
+      | new         | Brian | /testshare |
+
+  @files_sharing-app-required
+  Scenario: Receiver tries to get file versions of unshared file from the sharer
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "textfile1" to "textfile1.txt"
+    And user "Alice" has shared file "textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
+    When user "Brian" tries to get versions of file "textfile1.txt" from "Alice"
+    Then the HTTP status code should be "404"
+    And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound"
+
+  @skipOnStorage:ceph @files_primary_s3-issue-161 @files_sharing-app-required
+  Scenario: Receiver tries get file versions of shared file from the sharer
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 3" to "textfile0.txt"
+    And user "Alice" has shared file "textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
+    When user "Brian" tries to get versions of file "textfile0.txt" from "Alice"
+    Then the HTTP status code should be "207"
+    And the number of versions should be "3"
+
+  @issue-760
+  Scenario: Receiver tries get file versions of shared file before receiving it
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
+    And we save it into "FILEID"
+    And user "Alice" has shared file "textfile0.txt" with user "Brian"
+    When user "Brian" tries to get versions of file "textfile0.txt" from "Alice"
+    Then the HTTP status code should be "404"
+    And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound"
+
+
+  Scenario: sharer tries get file versions of shared file when the sharee changes the content of the file
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
+    And user "Alice" has shared file "sharefile.txt" with user "Brian"
+    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
+    When user "Brian" has uploaded file with content "Second content" to "/Shares/sharefile.txt"
+    Then the HTTP status code should be "204"
+    And the version folder of file "/Shares/sharefile.txt" for user "Brian" should contain "1" element
+    And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element
+
+
+  Scenario: download old versions of a shared file as share receiver
+    Given user "Brian" has been created with default attributes and without skeleton files
+    And user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
+    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
+    And user "Alice" has shared file "textfile0.txt" with user "Brian"
+    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
+    When user "Brian" downloads the version of file "/Shares/textfile0.txt" with the index "1"
+    Then the HTTP status code should be "200"
+    And the following headers should be set
+      | header              | value                                                                |
+      | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" |
+    And the downloaded content should be "version 1"
+    When user "Brian" downloads the version of file "/Shares/textfile0.txt" with the index "2"
+    Then the HTTP status code should be "200"
+    And the following headers should be set
+      | header              | value                                                                |
+      | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" |
+    And the downloaded content should be "uploaded content"
\ No newline at end of file
diff --git a/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature b/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature
deleted file mode 100644
index c53de028988..00000000000
--- a/tests/acceptance/features/coreApiVersions/fileVersionsSharingToShares.feature
+++ /dev/null
@@ -1,324 +0,0 @@
-@api @files_versions-app-required @issue-ocis-reva-275
-
-Feature: dav-versions
-
-  Background:
-    Given using OCS API version "2"
-    And using new DAV path
-    And the administrator has set the default folder for received shares to "Shares"
-    And auto-accept shares has been disabled
-    And user "Alice" has been created with default attributes and without skeleton files
-
-
-  Scenario: Upload file and no version is available
-    When user "Alice" uploads file "filesForUpload/davtest.txt" to "/davtest.txt" using the WebDAV API
-    Then the version folder of file "/davtest.txt" for user "Alice" should contain "0" elements
-
-  @files_sharing-app-required
-  Scenario: User can access meta folder of a file which is owned by somebody else but shared with that user
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "123" to "/davtest.txt"
-    And user "Alice" has uploaded file with content "456789" to "/davtest.txt"
-    And we save it into "FILEID"
-    And user "Alice" has created a share with settings
-      | path        | /davtest.txt |
-      | shareType   | user         |
-      | shareWith   | Brian        |
-      | permissions | read         |
-    When user "Brian" accepts share "/davtest.txt" offered by user "Alice" using the sharing API
-    Then the HTTP status code should be "200"
-    And the version folder of fileId "<<FILEID>>" for user "Brian" should contain "1" element
-
-  @files_sharing-app-required
-  Scenario: sharer of a file can see the old version information when the sharee changes the content of the file
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
-    And user "Alice" has shared file "sharefile.txt" with user "Brian"
-    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
-    When user "Brian" uploads file with content "Second content" to "/Shares/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the version folder of file "/Shares/sharefile.txt" for user "Brian" should contain "1" element
-    And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element
-
-  @files_sharing-app-required
-  Scenario: sharer of a file can restore the original content of a shared file after the file has been modified by the sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
-    And user "Alice" has shared file "sharefile.txt" with user "Brian"
-    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
-    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/Shares/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required
-  Scenario: sharer can restore a file inside a shared folder modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required
-  Scenario: sharee can restore a file inside a shared folder modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required
-  Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Brian" has uploaded file with content "First content" to "/Shares/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required
-  Scenario: sharee can restore a file inside a shared folder created by sharee and modified by sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Brian" has uploaded file with content "First content" to "/Shares/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "Second content" to "/sharingfolder/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-
-  @files_sharing-app-required
-  Scenario: sharer can restore a file inside a shared folder created by sharee and modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Brian" has uploaded file with content "old content" to "/Shares/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "new content" to "/Shares/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "1" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required
-  Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/Shares/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a file inside a shared folder created by sharer and modified by sharer, when the folder has been moved by the sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with user "Brian"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has created folder "/received"
-    And user "Brian" has moved folder "/Shares/sharingfolder" to "/received/sharingfolder"
-    When user "Brian" restores version index "1" of file "/received/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/received/sharingfolder/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is at the top level of the sharer)
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "old content" to "/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharefile.txt"
-    And user "Alice" has shared file "/sharefile.txt" with user "Brian"
-    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
-    And user "Brian" has created folder "/received"
-    And user "Brian" has moved file "/Shares/sharefile.txt" to "/received/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/received/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @notToImplementOnOCIS
-  Scenario: sharee can restore a shared file created and modified by sharer, when the file has been moved by the sharee (file is inside a folder of the sharer)
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has uploaded file with content "old content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has uploaded file with content "new content" to "/sharingfolder/sharefile.txt"
-    And user "Alice" has shared file "/sharingfolder/sharefile.txt" with user "Brian"
-    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
-    And user "Brian" has created folder "/received"
-    And user "Brian" has moved file "/Shares/sharefile.txt" to "/received/sharefile.txt"
-    When user "Brian" restores version index "1" of file "/received/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "old content"
-    And the content of file "/received/sharefile.txt" for user "Brian" should be "old content"
-
-  @files_sharing-app-required @issue-ocis-reva-34
-  Scenario: sharer can restore a file inside a group shared folder modified by sharee
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Carol" has been created with default attributes and without skeleton files
-    And group "grp1" has been created
-    And user "Brian" has been added to group "grp1"
-    And user "Carol" has been added to group "grp1"
-    And user "Alice" has created folder "/sharingfolder"
-    And user "Alice" has shared folder "/sharingfolder" with group "grp1"
-    And user "Brian" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Carol" has accepted share "/sharingfolder" offered by user "Alice"
-    And user "Alice" has uploaded file with content "First content" to "/sharingfolder/sharefile.txt"
-    And user "Brian" has uploaded file with content "Second content" to "/Shares/sharingfolder/sharefile.txt"
-    And user "Carol" has uploaded file with content "Third content" to "/Shares/sharingfolder/sharefile.txt"
-    When user "Alice" restores version index "2" of file "/sharingfolder/sharefile.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "/sharingfolder/sharefile.txt" for user "Alice" should be "First content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Brian" should be "First content"
-    And the content of file "/Shares/sharingfolder/sharefile.txt" for user "Carol" should be "First content"
-
-  @files_sharing-app-required @issue-ocis-reva-386
-  Scenario Outline: Moving a file (with versions) into a shared folder as the sharee and as the sharer
-    Given using <dav_version> DAV path
-    And user "Brian" has been created with default attributes and without skeleton files
-    And user "Brian" has created folder "/testshare"
-    And user "Brian" has created a share with settings
-      | path        | testshare |
-      | shareType   | user      |
-      | permissions | change    |
-      | shareWith   | Alice     |
-    And user "Alice" has accepted share "/testshare" offered by user "Brian"
-    And user "<mover>" has uploaded file with content "test data 1" to "/testfile.txt"
-    And user "<mover>" has uploaded file with content "test data 2" to "/testfile.txt"
-    And user "<mover>" has uploaded file with content "test data 3" to "/testfile.txt"
-    When user "<mover>" moves file "/testfile.txt" to "<dst-folder>/testfile.txt" using the WebDAV API
-    Then the HTTP status code should be "201"
-    And the content of file "/Shares/testshare/testfile.txt" for user "Alice" should be "test data 3"
-    And the content of file "/testshare/testfile.txt" for user "Brian" should be "test data 3"
-    And as "<mover>" file "/testfile.txt" should not exist
-    And the version folder of file "/Shares/testshare/testfile.txt" for user "Alice" should contain "2" elements
-    And the version folder of file "/testshare/testfile.txt" for user "Brian" should contain "2" elements
-    Examples:
-      | dav_version | mover | dst-folder        |
-      | old         | Alice | /Shares/testshare |
-      | new         | Alice | /Shares/testshare |
-      | old         | Brian | /testshare        |
-      | new         | Brian | /testshare        |
-
-  @files_sharing-app-required @issue-ocis-reva-386
-  Scenario Outline: Moving a file (with versions) out of a shared folder as the sharee and as the sharer
-    Given using <dav_version> DAV path
-    And user "Brian" has been created with default attributes and without skeleton files
-    And user "Brian" has created folder "/testshare"
-    And user "Brian" has uploaded file with content "test data 1" to "/testshare/testfile.txt"
-    And user "Brian" has uploaded file with content "test data 2" to "/testshare/testfile.txt"
-    And user "Brian" has uploaded file with content "test data 3" to "/testshare/testfile.txt"
-    And user "Brian" has created a share with settings
-      | path        | testshare |
-      | shareType   | user      |
-      | permissions | change    |
-      | shareWith   | Alice     |
-    And user "Alice" has accepted share "/testshare" offered by user "Brian"
-    When user "<mover>" moves file "<src-folder>/testfile.txt" to "/testfile.txt" using the WebDAV API
-    Then the HTTP status code should be "201"
-    And the content of file "/testfile.txt" for user "<mover>" should be "test data 3"
-    And as "Alice" file "/Shares/testshare/testfile.txt" should not exist
-    And as "Brian" file "/testshare/testfile.txt" should not exist
-    And the version folder of file "/testfile.txt" for user "<mover>" should contain "2" elements
-
-    @notToImplementOnOCIS
-    Examples:
-      | dav_version | mover | src-folder        |
-      | old         | Alice | /Shares/testshare |
-      | new         | Alice | /Shares/testshare |
-
-
-    Examples:
-      | dav_version | mover | src-folder |
-      | old         | Brian | /testshare |
-      | new         | Brian | /testshare |
-
-  @files_sharing-app-required
-  Scenario: Receiver tries to get file versions of unshared file from the sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "textfile1" to "textfile1.txt"
-    And user "Alice" has shared file "textfile0.txt" with user "Brian"
-    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
-    When user "Brian" tries to get versions of file "textfile1.txt" from "Alice"
-    Then the HTTP status code should be "404"
-    And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound"
-
-  @skipOnStorage:ceph @files_primary_s3-issue-161 @files_sharing-app-required
-  Scenario: Receiver tries get file versions of shared file from the sharer
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 3" to "textfile0.txt"
-    And user "Alice" has shared file "textfile0.txt" with user "Brian"
-    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
-    When user "Brian" tries to get versions of file "textfile0.txt" from "Alice"
-    Then the HTTP status code should be "207"
-    And the number of versions should be "3"
-
-
-  Scenario: Receiver tries get file versions of shared file before receiving it
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "textfile0" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
-    And we save it into "FILEID"
-    And user "Alice" has shared file "textfile0.txt" with user "Brian"
-    When user "Brian" tries to get versions of file "textfile0.txt" from "Alice"
-    Then the HTTP status code should be "404"
-    And the value of the item "//s:exception" in the response about user "Alice" should be "Sabre\DAV\Exception\NotFound"
-
-
-  Scenario: sharer tries get file versions of shared file when the sharee changes the content of the file
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "First content" to "sharefile.txt"
-    And user "Alice" has shared file "sharefile.txt" with user "Brian"
-    And user "Brian" has accepted share "/sharefile.txt" offered by user "Alice"
-    When user "Brian" has uploaded file with content "Second content" to "/Shares/sharefile.txt"
-    Then the HTTP status code should be "204"
-    And the version folder of file "/Shares/sharefile.txt" for user "Brian" should contain "1" element
-    And the version folder of file "/sharefile.txt" for user "Alice" should contain "1" element
-
-
-  Scenario: download old versions of a shared file as share receiver
-    Given user "Brian" has been created with default attributes and without skeleton files
-    And user "Alice" has uploaded file with content "uploaded content" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 1" to "textfile0.txt"
-    And user "Alice" has uploaded file with content "version 2" to "textfile0.txt"
-    And user "Alice" has shared file "textfile0.txt" with user "Brian"
-    And user "Brian" has accepted share "/textfile0.txt" offered by user "Alice"
-    When user "Brian" downloads the version of file "/Shares/textfile0.txt" with the index "1"
-    Then the HTTP status code should be "200"
-    And the following headers should be set
-      | header              | value                                                                |
-      | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" |
-    And the downloaded content should be "version 1"
-    When user "Brian" downloads the version of file "/Shares/textfile0.txt" with the index "2"
-    Then the HTTP status code should be "200"
-    And the following headers should be set
-      | header              | value                                                                |
-      | Content-Disposition | attachment; filename*=UTF-8''textfile0.txt; filename="textfile0.txt" |
-    And the downloaded content should be "uploaded content"
diff --git a/tests/config/drone/ocis-config.json b/tests/config/drone/ocis-config.json
index 4b3e7b7b2c8..dbbf3951f43 100644
--- a/tests/config/drone/ocis-config.json
+++ b/tests/config/drone/ocis-config.json
@@ -27,11 +27,5 @@
     "pdf-viewer",
     "search",
     "admin-settings"
-  ],
-  "external_apps": [
-    {
-      "id": "settings",
-      "path": "https://ocis-server:9200/settings.js"
-    }
   ]
 }