Repository: owncloud/ocis
Pull request #7250 (2023-09-07 21:21:20)

give graph driveItems some love
This PR adds two endpoints that allow navigating the file tree via the graph API:
* `/drives/{driveID}/items/{driveItemID}` as a stat
* `/drives/{driveID}/items/{driveItemID}/children` to list all children of a folder

This is just a basic implementation, but I needed some fun today ...

- [x] It also works around the flakyness of `/me/drive/root/children` ... path based ListContainer requests for `/users/some-u-u-i-d` sometimes are routed to the sharesstorageprovider because that is mounted at `/users/some-u-u-i-d/Shares`. And the gateway just picks the first storage provider it encounters ... Urgh ... this might be  worth an issue ... for future me ... tracked in https://github.com/owncloud/ocis/issues/7255

- [x] we need better error handling in the graph service ... a consistent way to map cs3 codes to graph apis ... also something for future me ... tracked in https://github.com/owncloud/ocis/issues/7256



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

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

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



![idea](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png 'idea') Catch issues before they fail your Quality Gate with our IDE extension ![sonarlint](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png 'sonarlint') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarcloud-welcome)
sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7250)

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

[![17.9%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '17.9%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_coverage&view=list) [17.9% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_coverage&view=list)  
[![47.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20plus-16px.png '47.7%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_duplicated_lines_density&view=list) [47.7% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_duplicated_lines_density&view=list)



![idea](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png 'idea') Catch issues before they fail your Quality Gate with our IDE extension ![sonarlint](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png 'sonarlint') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarcloud-welcome)
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7250)

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

[![36.6%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/25-16px.png '36.6%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_coverage&view=list) [36.6% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_coverage&view=list)  
[![35.8%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20plus-16px.png '35.8%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_duplicated_lines_density&view=list) [35.8% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_duplicated_lines_density&view=list)


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

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

[![54.2%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/50-16px.png '54.2%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_coverage&view=list) [54.2% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_coverage&view=list)  
[![27.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/20plus-16px.png '27.7%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_duplicated_lines_density&view=list) [27.7% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7250&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a17d66aa0d..9e101744465 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The following sections list the changes for unreleased.
 
 * Bugfix - Fixed cunary in the beginning: [#7247](https://github.com/owncloud/ocis/pull/7247)
 * Enhancement - Support spec violating AD FS access token issuer: [#7138](https://github.com/owncloud/ocis/pull/7138)
+* Enhancement - Add the password policies: [#7194](https://github.com/owncloud/ocis/pull/7194)
 * Enhancement - Auto-Accept Shares: [#7097](https://github.com/owncloud/ocis/pull/7097)
 * Enhancement - Bump Reva: [#7138](https://github.com/owncloud/ocis/pull/7138)
 * Enhancement - Introduce clientlog service: [#7217](https://github.com/owncloud/ocis/pull/7217)
@@ -32,6 +33,13 @@ The following sections list the changes for unreleased.
 
    https://github.com/owncloud/ocis/pull/7138
 
+* Enhancement - Add the password policies: [#7194](https://github.com/owncloud/ocis/pull/7194)
+
+   Add the password policies OCIS-3767
+
+   https://github.com/owncloud/ocis/pull/7194
+   https://github.com/cs3org/reva/pull/4147
+
 * Enhancement - Auto-Accept Shares: [#7097](https://github.com/owncloud/ocis/pull/7097)
 
    Automatically accept shares when configured by the user or admin
diff --git a/changelog/unreleased/add-passwod-policies.md b/changelog/unreleased/add-passwod-policies.md
new file mode 100644
index 00000000000..ece2306e56d
--- /dev/null
+++ b/changelog/unreleased/add-passwod-policies.md
@@ -0,0 +1,6 @@
+Enhancement: Add the password policies
+
+Add the password policies OCIS-3767
+
+https://github.com/owncloud/ocis/pull/7194
+https://github.com/cs3org/reva/pull/4147
diff --git a/go.mod b/go.mod
index 1ac01b855f2..091f874c02e 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/coreos/go-oidc v2.2.1+incompatible
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/cs3org/go-cs3apis v0.0.0-20230516150832-730ac860c71d
-	github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f
+	github.com/cs3org/reva/v2 v2.16.1-0.20230908122008-491b5c5bf72a
 	github.com/disintegration/imaging v1.6.2
 	github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
 	github.com/egirna/icap-client v0.1.1
diff --git a/go.sum b/go.sum
index 8719735e944..8e874fc1c73 100644
--- a/go.sum
+++ b/go.sum
@@ -1013,8 +1013,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo
 github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
 github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc=
 github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA=
-github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f h1:0mbvh+AvpYOp29R5LFgeqddyI0uKZnO/E3MyPEIaYdg=
-github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f/go.mod h1:RvhuweTFqzezjUFU0SIdTXakrEx9vJlMvQ7znPXSP1g=
+github.com/cs3org/reva/v2 v2.16.1-0.20230908122008-491b5c5bf72a h1:E7XghgSsNsR57wd0z/emKRfwDmoT2q/vuIYGNtx9IkA=
+github.com/cs3org/reva/v2 v2.16.1-0.20230908122008-491b5c5bf72a/go.mod h1:RvhuweTFqzezjUFU0SIdTXakrEx9vJlMvQ7znPXSP1g=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
diff --git a/services/frontend/README.md b/services/frontend/README.md
index 36c9b2a7b3e..ed087d6def1 100644
--- a/services/frontend/README.md
+++ b/services/frontend/README.md
@@ -63,3 +63,24 @@ The `frontend` service contains an eventhandler for handling `ocs` related event
 ### Auto-Accept Shares
 
 When setting the `FRONTEND_AUTO_ACCEPT_SHARES` to `true`, all incoming shares will be accepted automatically. Users can overwrite this setting individually in their profile.
+
+## The password policy
+
+Note that the password policy currently impacts _only_ public link password validation.
+
+With the password policy, mandatory criteria for the password can be defined via the environment variables listed below.
+
+Generally, a password can contain any UTF-8 characters, however some characters are regarded as special since they are not used in ordinary texts. Which characters should be treated as special is defined by "The OWASP® Foundation" [password-special-characters](https://owasp.org/www-community/password-special-characters) (between double quotes): " !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
+
+Note that a password can have a maximum length of **72 bytes**. Depending on the alphabet used, a character is encoded by 1 to 4 bytes, defining the maximum length of a password indirectly. While US-ASCII will only need one byte, Latin alphabets and also Greek or Cyrillic ones need two bytes. Three bytes are needed for characters in Chinese, Japanese and Korean etc.
+
+-   `FRONTEND_PASSWORD_POLICIES_MIN_CHARACTERS`
+Define the minimum password length.
+-   `FRONTEND_PASSWORD_POLICIES_MIN_LOWER_CASE_CHARACTERS`
+Define the minimum number of uppercase letters.
+-   `FRONTEND_PASSWORD_POLICIES_MIN_UPPER_CASE_CHARACTERS`
+Define the minimum number of lowercase letters.
+-   `FRONTEND_PASSWORD_POLICIES_MIN_DIGITS`
+Define the minimum number of digits.
+-   `FRONTEND_PASSWORD_POLICIES_MIN_SPECIAL_CHARACTERS`
+Define the minimum number of special characters.
diff --git a/services/frontend/pkg/config/config.go b/services/frontend/pkg/config/config.go
index 21acc43fc32..2e9d87afc71 100644
--- a/services/frontend/pkg/config/config.go
+++ b/services/frontend/pkg/config/config.go
@@ -58,6 +58,8 @@ type Config struct {
 	AutoAcceptShares bool                  `yaml:"auto_accept_shares" env:"FRONTEND_AUTO_ACCEPT_SHARES" desc:"Defines if shares should be auto accepted by default. Users can change this setting individually in their profile."`
 	ServiceAccount   ServiceAccount        `yaml:"service_account"`
 
+	PasswordPolicies PasswordPolicies `yaml:"password_policies"`
+
 	Supervised bool            `yaml:"-"`
 	Context    context.Context `yaml:"-"`
 }
@@ -171,3 +173,12 @@ type ServiceAccount struct {
 	ServiceAccountID     string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;FRONTEND_SERVICE_ACCOUNT_ID" desc:"The ID of the service account the service should use. See the 'auth-service' service description for more details."`
 	ServiceAccountSecret string `yaml:"service_account_secret" env:"OCIS_SERVICE_ACCOUNT_SECRET;FRONTEND_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."`
 }
+
+// PasswordPolicies configures reva password policies
+type PasswordPolicies struct {
+	MinCharacters          int `yaml:"min_characters,omitempty" env:"FRONTEND_PASSWORD_POLICIES_MIN_CHARACTERS" desc:"Define the minimum password length. Defaults to 0 if not set."`
+	MinLowerCaseCharacters int `yaml:"min_lower_case_characters" env:"FRONTEND_PASSWORD_POLICIES_MIN_LOWER_CASE_CHARACTERS" desc:"Define the minimum number of uppercase letters. Defaults to 0 if not set."`
+	MinUpperCaseCharacters int `yaml:"min_upper_case_characters" env:"FRONTEND_PASSWORD_POLICIES_MIN_UPPER_CASE_CHARACTERS" desc:"Define the minimum number of lowercase letters. Defaults to 0 if not set."`
+	MinDigits              int `yaml:"min_digits" env:"FRONTEND_PASSWORD_POLICIES_MIN_DIGITS" desc:"Define the minimum number of digits. Defaults to 0 if not set."`
+	MinSpecialCharacters   int `yaml:"min_special_characters" env:"FRONTEND_PASSWORD_POLICIES_MIN_SPECIAL_CHARACTERS" desc:"Define the minimum number of characters from the special characters list to be present. Defaults to 0 if not set."`
+}
diff --git a/services/frontend/pkg/revaconfig/config.go b/services/frontend/pkg/revaconfig/config.go
index 3383251c1db..e800c5a4368 100644
--- a/services/frontend/pkg/revaconfig/config.go
+++ b/services/frontend/pkg/revaconfig/config.go
@@ -274,6 +274,14 @@ func FrontendConfigFromStruct(cfg *config.Config) (map[string]interface{}, error
 								"share_jail": cfg.EnableShareJail,
 								"max_quota":  cfg.MaxQuota,
 							},
+							"password_policies": map[string]interface{}{
+								"max_characters":            72,
+								"min_characters":            cfg.PasswordPolicies.MinCharacters,
+								"min_lower_case_characters": cfg.PasswordPolicies.MinLowerCaseCharacters,
+								"min_upper_case_characters": cfg.PasswordPolicies.MinUpperCaseCharacters,
+								"min_digits":                cfg.PasswordPolicies.MinDigits,
+								"min_special_characters":    cfg.PasswordPolicies.MinSpecialCharacters,
+							},
 							"notifications": map[string]interface{}{
 								"endpoints": []string{"list", "get", "delete"},
 							},
diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go
index 16387914cda..0e31bb677d0 100644
--- a/services/graph/pkg/service/v0/driveitems.go
+++ b/services/graph/pkg/service/v0/driveitems.go
@@ -12,6 +12,7 @@ import (
 	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
 	storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/cs3org/reva/v2/pkg/utils"
 	"github.com/go-chi/render"
@@ -32,10 +33,18 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	res, err := gatewayClient.GetHome(ctx, &storageprovider.GetHomeRequest{})
+	currentUser := revactx.ContextMustGetUser(r.Context())
+	// do we need to list all or only the personal drive
+	filters := []*storageprovider.ListStorageSpacesRequest_Filter{}
+	filters = append(filters, listStorageSpacesUserFilter(currentUser.GetId().OpaqueId))
+	filters = append(filters, listStorageSpacesTypeFilter("personal"))
+
+	res, err := gatewayClient.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{
+		Filters: filters,
+	})
 	switch {
 	case err != nil:
-		g.logger.Error().Err(err).Msg("error sending get home grpc request")
+		g.logger.Error().Err(err).Msg("error making ListStorageSpaces grpc call")
 		errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
 	case res.Status.Code != cs3rpc.Code_CODE_OK:
@@ -43,29 +52,34 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
 			errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
 			return
 		}
-		g.logger.Error().Err(err).Msg("error sending get home grpc request")
+		g.logger.Error().Err(err).Msg("error sending ListStorageSpaces grpc request")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
 		return
 	}
 
+	var space *storageprovider.StorageSpace
+	for _, s := range res.StorageSpaces {
+		if utils.UserIDEqual(currentUser.GetId(), s.GetOwner().GetId()) {
+			space = s
+		}
+	}
+
 	lRes, err := gatewayClient.ListContainer(ctx, &storageprovider.ListContainerRequest{
-		Ref: &storageprovider.Reference{
-			Path: res.Path,
-		},
+		Ref: &storageprovider.Reference{ResourceId: space.Root},
 	})
 	switch {
 	case err != nil:
-		g.logger.Error().Err(err).Msg("error sending list container grpc request")
+		g.logger.Error().Err(err).Msg("error making ListContainer grpc call")
 		errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
-	case res.Status.Code != cs3rpc.Code_CODE_OK:
-		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
-			errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
+	case lRes.Status.Code != cs3rpc.Code_CODE_OK:
+		if lRes.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
+			errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, lRes.Status.Message)
 			return
 		}
-		if res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED {
+		if lRes.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED {
 			// TODO check if we should return 404 to not disclose existing items
-			errorcode.AccessDenied.Render(w, r, http.StatusForbidden, res.Status.Message)
+			errorcode.AccessDenied.Render(w, r, http.StatusForbidden, lRes.Status.Message)
 			return
 		}
 		g.logger.Error().Err(err).Msg("error sending list container grpc request")
@@ -84,6 +98,138 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
 	render.JSON(w, r, &ListResponse{Value: files})
 }
 
+// GetDriveItem returns a driveItem
+func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
+	g.logger.Info().Msg("Calling GetDriveItem")
+	ctx := r.Context()
+
+	driveID, err := parseIDParam(r, "driveID")
+	if err != nil {
+		errorcode.RenderError(w, r, err)
+		return
+	}
+	driveItemID, err := parseIDParam(r, "driveItemID")
+	if err != nil {
+		errorcode.RenderError(w, r, err)
+		return
+	}
+	if driveID.StorageId != driveItemID.StorageId || driveID.SpaceId != driveItemID.SpaceId {
+		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Item does not exist")
+		return
+	}
+	/*
+		sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
+		// Parse the request with odata parser
+		odataReq, err := godata.ParseRequest(ctx, sanitizedPath, r.URL.Query())
+		if err != nil {
+			errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+			return
+		}
+	*/
+
+	gatewayClient, err := g.gatewaySelector.Next()
+	if err != nil {
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	}
+	res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &driveItemID}})
+	switch {
+	case err != nil:
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	case res.Status.Code == cs3rpc.Code_CODE_OK:
+		// ok
+	case res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND:
+		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
+		return
+	case res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED:
+		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) // do not leak existence? check what graph does
+		return
+	case res.Status.Code == cs3rpc.Code_CODE_UNAUTHENTICATED:
+		errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.Status.Message) // do not leak existence? check what graph does
+		return
+	default:
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
+		return
+	}
+	driveItem, err := cs3ResourceToDriveItem(res.Info)
+	if err != nil {
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	render.Status(r, http.StatusOK)
+	render.JSON(w, r, &driveItem)
+}
+
+// GetDriveItemChildren lists the children of a driveItem
+func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
+	g.logger.Info().Msg("Calling GetDriveItemChildren")
+	ctx := r.Context()
+
+	driveID, err := parseIDParam(r, "driveID")
+	if err != nil {
+		errorcode.RenderError(w, r, err)
+		return
+	}
+	driveItemID, err := parseIDParam(r, "driveItemID")
+	if err != nil {
+		errorcode.RenderError(w, r, err)
+		return
+	}
+	if driveID.StorageId != driveItemID.StorageId || driveID.SpaceId != driveItemID.SpaceId {
+		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Item does not exist")
+		return
+	}
+	/*
+		sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
+		// Parse the request with odata parser
+		odataReq, err := godata.ParseRequest(ctx, sanitizedPath, r.URL.Query())
+		if err != nil {
+			errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+			return
+		}
+	*/
+
+	gatewayClient, err := g.gatewaySelector.Next()
+	if err != nil {
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	res, err := gatewayClient.ListContainer(ctx, &storageprovider.ListContainerRequest{
+		Ref: &storageprovider.Reference{ResourceId: &driveItemID},
+	})
+	switch {
+	case err != nil:
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	case res.Status.Code == cs3rpc.Code_CODE_OK:
+		// ok
+	case res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND:
+		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message)
+		return
+	case res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED:
+		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) // do not leak existence? check what graph does
+		return
+	case res.Status.Code == cs3rpc.Code_CODE_UNAUTHENTICATED:
+		errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, res.Status.Message) // do not leak existence? check what graph does
+		return
+	default:
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
+		return
+	}
+
+	files, err := formatDriveItems(res.Infos)
+	if err != nil {
+		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	render.Status(r, http.StatusOK)
+	render.JSON(w, r, &ListResponse{Value: files})
+}
+
 func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference) (*libregraph.DriveItem, error) {
 	gatewayClient, err := g.gatewaySelector.Next()
 	if err != nil {
diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go
index 2e3898e678a..f3f1d5da35f 100644
--- a/services/graph/pkg/service/v0/drives.go
+++ b/services/graph/pkg/service/v0/drives.go
@@ -17,13 +17,11 @@ import (
 	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
 	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
 	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
-	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
 	storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
 	revactx "github.com/cs3org/reva/v2/pkg/ctx"
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/cs3org/reva/v2/pkg/utils"
-	"github.com/go-chi/chi/v5"
 	"github.com/go-chi/render"
 	"github.com/jellydator/ttlcache/v3"
 	libregraph "github.com/owncloud/libre-graph-api-go"
@@ -157,41 +155,37 @@ func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bo
 
 // GetSingleDrive does a lookup of a single space by spaceId
 func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
-	logger := g.logger.SubloggerWithRequestID(r.Context())
+	ctx := r.Context()
+	logger := g.logger.SubloggerWithRequestID(ctx)
 	logger.Info().Interface("query", r.URL.Query()).Msg("calling get drive")
-	driveID, err := url.PathUnescape(chi.URLParam(r, "driveID"))
+
+	rid, err := parseIDParam(r, "driveID")
 	if err != nil {
-		logger.Debug().Err(err).Str("driveID", chi.URLParam(r, "driveID")).Msg("could not get drive: unescaping drive id failed")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping drive id failed")
+		errorcode.RenderError(w, r, err)
 		return
 	}
+	log := logger.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger()
 
-	if driveID == "" {
-		logger.Debug().Msg("could not get drive: missing drive id")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing drive id")
-		return
-	}
+	log.Debug().Msg("calling list storage spaces with id filter")
 
-	logger.Debug().Str("driveID", driveID).Msg("calling list storage spaces with id filter")
-	ctx := r.Context()
-
-	filters := []*storageprovider.ListStorageSpacesRequest_Filter{listStorageSpacesIDFilter(driveID)}
+	filters := []*storageprovider.ListStorageSpacesRequest_Filter{
+		listStorageSpacesIDFilter(storagespace.FormatResourceID(rid)),
+	}
 	res, err := g.ListStorageSpacesWithFilters(ctx, filters, true)
 	switch {
 	case err != nil:
-		logger.Error().Err(err).Msg("could not get drive: transport error")
+		log.Error().Err(err).Msg("could not get drive: transport error")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
 	case res.Status.Code != cs3rpc.Code_CODE_OK:
 		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
 			// the client is doing a lookup for a specific space, therefore we need to return
 			// not found to the caller
-			logger.Debug().Str("driveID", driveID).Msg("could not get drive: not found")
+			log.Debug().Msg("could not get drive: not found")
 			errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
 			return
 		}
-		logger.Debug().
-			Str("id", driveID).
+		log.Debug().
 			Str("grpcmessage", res.GetStatus().GetMessage()).
 			Msg("could not get drive: grpc error")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
@@ -200,26 +194,26 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
 
 	webDavBaseURL, err := g.getWebDavBaseURL()
 	if err != nil {
-		logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drive: error parsing webdav base url")
+		log.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drive: error parsing webdav base url")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
 	}
 	spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces)
 	if err != nil {
-		logger.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
+		log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
 		return
 	}
 	switch num := len(spaces); {
 	case num == 0:
-		logger.Debug().Str("id", driveID).Msg("could not get drive: no drive returned from storage")
+		log.Debug().Msg("could not get drive: no drive returned from storage")
 		errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "no drive returned from storage")
 		return
 	case num == 1:
 		render.Status(r, http.StatusOK)
 		render.JSON(w, r, spaces[0])
 	default:
-		logger.Debug().Int("number", num).Msg("could not get drive: expected to find a single drive but fetched more")
+		log.Debug().Int("number", num).Msg("could not get drive: expected to find a single drive but fetched more")
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get drive: expected to find a single drive but fetched more")
 		return
 	}
@@ -354,16 +348,10 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
 func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 	logger := g.logger.SubloggerWithRequestID(r.Context())
 	logger.Info().Msg("calling update drive")
-	driveID, err := url.PathUnescape(chi.URLParam(r, "driveID"))
-	if err != nil {
-		logger.Debug().Err(err).Str("id", chi.URLParam(r, "driveID")).Msg("could not update drive, unescaping drive id failed")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping drive id failed")
-		return
-	}
 
-	if driveID == "" {
-		logger.Debug().Msg("Could not update drive, missing drive id")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing drive id")
+	rid, err := parseIDParam(r, "driveID")
+	if err != nil {
+		errorcode.RenderError(w, r, err)
 		return
 	}
 
@@ -374,15 +362,6 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	rid, err := storagespace.ParseID(driveID)
-	if err != nil {
-		logger.Debug().Err(err).Interface("id", rid).Msg("could not update drive, invalid resource id")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid resource id")
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-	root := &rid
-
 	gatewayClient, err := g.gatewaySelector.Next()
 	if err != nil {
 		g.logger.Error().Err(err).Msg("could not select next gateway client")
@@ -390,6 +369,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	root := &rid
 	updateSpaceRequest := &storageprovider.UpdateStorageSpaceRequest{
 		// Prepare the object to apply the diff from. The properties on StorageSpace will overwrite
 		// the original storage space.
@@ -445,7 +425,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 		dt := _spaceTypePersonal
 		filters := []*storageprovider.ListStorageSpacesRequest_Filter{listStorageSpacesTypeFilter(_spaceTypeProject)}
 		res, err := g.ListStorageSpacesWithFilters(r.Context(), filters, true)
-		if err == nil && res.GetStatus().GetCode() == rpc.Code_CODE_OK {
+		if err == nil && res.GetStatus().GetCode() == cs3rpc.Code_CODE_OK {
 			for _, sp := range res.StorageSpaces {
 				id, _ := storagespace.ParseID(sp.GetId().GetOpaqueId())
 				if id.GetSpaceId() == rid.GetSpaceId() {
@@ -1032,23 +1012,9 @@ func listStorageSpacesTypeFilter(spaceType string) *storageprovider.ListStorageS
 func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) {
 	logger := g.logger.SubloggerWithRequestID(r.Context())
 	logger.Info().Msg("calling delete drive")
-	driveID, err := url.PathUnescape(chi.URLParam(r, "driveID"))
-	if err != nil {
-		logger.Debug().Err(err).Str("id", chi.URLParam(r, "driveID")).Msg("could not delete drive: unescaping drive id failed")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping drive id failed")
-		return
-	}
-
-	if driveID == "" {
-		logger.Debug().Msg("could not delete drive: missing drive id")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing drive id")
-		return
-	}
-
-	rid, err := storagespace.ParseID(driveID)
+	rid, err := parseIDParam(r, "driveID")
 	if err != nil {
-		logger.Debug().Interface("id", rid).Err(err).Msg("could not delete drive: invalid resource id")
-		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid resource id")
+		errorcode.RenderError(w, r, err)
 		return
 	}
 
diff --git a/services/graph/pkg/service/v0/errorcode/errorcode.go b/services/graph/pkg/service/v0/errorcode/errorcode.go
index e15289f327f..6ac3b0a44a3 100644
--- a/services/graph/pkg/service/v0/errorcode/errorcode.go
+++ b/services/graph/pkg/service/v0/errorcode/errorcode.go
@@ -18,6 +18,7 @@ type Error struct {
 	msg       string
 }
 
+// List taken from https://github.com/microsoft/microsoft-graph-docs-1/blob/main/concepts/errors.md#code-property
 const (
 	// AccessDenied defines the error if the caller doesn't have permission to perform the action.
 	AccessDenied ErrorCode = iota
@@ -47,6 +48,8 @@ const (
 	ResyncRequired
 	// ServiceNotAvailable defines the error if the service is not available. Try the request again after a delay. There may be a Retry-After header.
 	ServiceNotAvailable
+	// The sync state generation is not found. The delta token is expired and data must be synchronized again.
+	SyncStateNotFound
 	// QuotaLimitReached the user has reached their quota limit.
 	QuotaLimitReached
 	// Unauthenticated the caller is not authenticated.
@@ -70,6 +73,7 @@ var errorCodes = [...]string{
 	"resourceModified",
 	"resyncRequired",
 	"serviceNotAvailable",
+	"syncStateNotFound",
 	"quotaLimitReached",
 	"unauthenticated",
 	"preconditionFailed",
@@ -103,12 +107,18 @@ func (e ErrorCode) Render(w http.ResponseWriter, r *http.Request, status int, ms
 func (e Error) Render(w http.ResponseWriter, r *http.Request) {
 	var status int
 	switch e.errorCode {
+	case AccessDenied:
+		status = http.StatusForbidden
+	case InvalidRange:
+		status = http.StatusRequestedRangeNotSatisfiable
+	case InvalidRequest:
+		status = http.StatusBadRequest
 	case ItemNotFound:
 		status = http.StatusNotFound
 	case NameAlreadyExists:
 		status = http.StatusConflict
 	case NotAllowed:
-		status = http.StatusForbidden
+		status = http.StatusMethodNotAllowed
 	default:
 		status = http.StatusInternalServerError
 	}
diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go
index 2d3c0995c13..29d22beccaa 100644
--- a/services/graph/pkg/service/v0/graph.go
+++ b/services/graph/pkg/service/v0/graph.go
@@ -9,8 +9,10 @@ import (
 	"strings"
 
 	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	"github.com/cs3org/reva/v2/pkg/events"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/go-chi/chi/v5"
 	"github.com/jellydator/ttlcache/v3"
 	libregraph "github.com/owncloud/libre-graph-api-go"
@@ -21,6 +23,7 @@ import (
 	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/config"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
+	"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
 	"go-micro.dev/v4/client"
 	mevents "go-micro.dev/v4/events"
 	"go.opentelemetry.io/otel/trace"
@@ -134,3 +137,16 @@ func (g Graph) parseMemberRef(ref string) (string, string, error) {
 	memberType := segments[len(segments)-2]
 	return memberType, id, nil
 }
+
+func parseIDParam(r *http.Request, param string) (storageprovider.ResourceId, error) {
+	driveID, err := url.PathUnescape(chi.URLParam(r, param))
+	if err != nil {
+		return storageprovider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error())
+	}
+
+	id, err := storagespace.ParseID(driveID)
+	if err != nil {
+		return storageprovider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error())
+	}
+	return id, nil
+}
diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go
index e47c562e821..93cbeebe0fd 100644
--- a/services/graph/pkg/service/v0/service.go
+++ b/services/graph/pkg/service/v0/service.go
@@ -96,11 +96,14 @@ type Service interface {
 	GetDrives(w http.ResponseWriter, r *http.Request)
 	GetSingleDrive(w http.ResponseWriter, r *http.Request)
 	GetAllDrives(w http.ResponseWriter, r *http.Request)
-	GetRootDriveChildren(w http.ResponseWriter, r *http.Request)
 	CreateDrive(w http.ResponseWriter, r *http.Request)
 	UpdateDrive(w http.ResponseWriter, r *http.Request)
 	DeleteDrive(w http.ResponseWriter, r *http.Request)
 
+	GetRootDriveChildren(w http.ResponseWriter, r *http.Request)
+	GetDriveItem(w http.ResponseWriter, r *http.Request)
+	GetDriveItemChildren(w http.ResponseWriter, r *http.Request)
+
 	GetTags(w http.ResponseWriter, r *http.Request)
 	AssignTags(w http.ResponseWriter, r *http.Request)
 	UnassignTags(w http.ResponseWriter, r *http.Request)
@@ -252,6 +255,10 @@ func NewService(opts ...Option) (Graph, error) {
 					r.Patch("/", svc.UpdateDrive)
 					r.Get("/", svc.GetSingleDrive)
 					r.Delete("/", svc.DeleteDrive)
+					r.Route("/items/{driveItemID}", func(r chi.Router) {
+						r.Get("/", svc.GetDriveItem)
+						r.Get("/children", svc.GetDriveItemChildren)
+					})
 				})
 			})
 			r.With(requireAdmin).Route("/education", func(r chi.Router) {
diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go
index 562187df502..ff239b649c0 100644
--- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go
+++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go
@@ -126,7 +126,6 @@ type GatewayClientGetter func() (gateway.GatewayAPIClient, error)
 
 // Init initializes this and any contained handlers
 func (h *Handler) Init(c *config.Config) error {
-	var err error
 	h.gatewayAddr = c.GatewaySvc
 	h.machineAuthAPIKey = c.MachineAuthAPIKey
 	h.storageRegistryAddr = c.StorageregistrySvc
@@ -142,10 +141,7 @@ func (h *Handler) Init(c *config.Config) error {
 	h.deniable = c.EnableDenials
 	h.resharing = resharing(c)
 	h.publicPasswordEnforced = publicPwdEnforced(c)
-	h.passwordValidator, err = passwordPolicies(c)
-	if err != nil {
-		return err
-	}
+	h.passwordValidator = passwordPolicies(c)
 
 	h.statCache = cache.GetStatCache(c.StatCacheStore, c.StatCacheNodes, c.StatCacheDatabase, "stat", time.Duration(c.StatCacheTTL)*time.Second, c.StatCacheSize)
 	if c.CacheWarmupDriver != "" {
@@ -1594,28 +1590,17 @@ func publicPwdEnforced(c *config.Config) passwordEnforced {
 	return enf
 }
 
-func passwordPolicies(c *config.Config) (password.Validator, error) {
-	var pv password.Validator
-	var err error
+func passwordPolicies(c *config.Config) password.Validator {
 	if c.Capabilities.Capabilities == nil || c.Capabilities.Capabilities.PasswordPolicies == nil {
-		pv, err = password.NewPasswordPolicies(0, 0, 0, 0, 0, "")
-		if err != nil {
-			return nil, fmt.Errorf("can't init the Password Policies %w", err)
-		}
-		return pv, nil
+		return password.NewPasswordPolicies(0, 0, 0, 0, 0)
 	}
-	pv, err = password.NewPasswordPolicies(
+	return password.NewPasswordPolicies(
 		c.Capabilities.Capabilities.PasswordPolicies.MinCharacters,
 		c.Capabilities.Capabilities.PasswordPolicies.MinLowerCaseCharacters,
 		c.Capabilities.Capabilities.PasswordPolicies.MinUpperCaseCharacters,
 		c.Capabilities.Capabilities.PasswordPolicies.MinDigits,
 		c.Capabilities.Capabilities.PasswordPolicies.MinSpecialCharacters,
-		c.Capabilities.Capabilities.PasswordPolicies.SpecialCharacters,
 	)
-	if err != nil {
-		return nil, fmt.Errorf("can't init the Password Policies %w", err)
-	}
-	return pv, nil
 }
 
 // sufficientPermissions returns true if the `existing` permissions contain the `requested` permissions
diff --git a/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go b/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go
index 9ebba7b7839..9a499ba8ce7 100644
--- a/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go
+++ b/vendor/github.com/cs3org/reva/v2/pkg/password/password_policies.go
@@ -8,6 +8,9 @@ import (
 	"unicode/utf8"
 )
 
+// https://owasp.org/www-community/password-special-characters
+var _defaultSpecialCharacters = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+
 // Validator describes the interface providing a password Validate method
 type Validator interface {
 	Validate(str string) error
@@ -20,32 +23,23 @@ type Policies struct {
 	minUpperCaseCharacters  int
 	minDigits               int
 	minSpecialCharacters    int
-	specialCharacters       string
 	digitsRegexp            *regexp.Regexp
 	specialCharactersRegexp *regexp.Regexp
 }
 
 // NewPasswordPolicies returns a new NewPasswordPolicies instance
-func NewPasswordPolicies(minCharacters, minLowerCaseCharacters, minUpperCaseCharacters, minDigits, minSpecialCharacters int,
-	specialCharacters string) (Validator, error) {
+func NewPasswordPolicies(minCharacters, minLowerCaseCharacters, minUpperCaseCharacters, minDigits, minSpecialCharacters int) Validator {
 	p := &Policies{
 		minCharacters:          minCharacters,
 		minLowerCaseCharacters: minLowerCaseCharacters,
 		minUpperCaseCharacters: minUpperCaseCharacters,
 		minDigits:              minDigits,
 		minSpecialCharacters:   minSpecialCharacters,
-		specialCharacters:      specialCharacters,
 	}
 
 	p.digitsRegexp = regexp.MustCompile("[0-9]")
-	if len(specialCharacters) > 0 {
-		var err error
-		p.specialCharactersRegexp, err = regexp.Compile(specialCharactersExp(specialCharacters))
-		if err != nil {
-			return nil, err
-		}
-	}
-	return p, nil
+	p.specialCharactersRegexp = regexp.MustCompile(specialCharactersExp(_defaultSpecialCharacters))
+	return p
 }
 
 // Validate implements a password validation regarding the policy
@@ -110,7 +104,7 @@ func (s Policies) validateDigits(str string) error {
 
 func (s Policies) validateSpecialCharacters(str string) error {
 	if s.countSpecialCharacters(str) < s.minSpecialCharacters {
-		return fmt.Errorf("at least %d special characters are required. %s", s.minSpecialCharacters, s.specialCharacters)
+		return fmt.Errorf("at least %d special characters are required. %s", s.minSpecialCharacters, _defaultSpecialCharacters)
 	}
 	return nil
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9c67374e466..fb61d89d91a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -356,7 +356,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
 github.com/cs3org/go-cs3apis/cs3/types/v1beta1
-# github.com/cs3org/reva/v2 v2.16.1-0.20230906142214-864d9012e37f
+# github.com/cs3org/reva/v2 v2.16.1-0.20230908122008-491b5c5bf72a
 ## explicit; go 1.20
 github.com/cs3org/reva/v2/cmd/revad/internal/grace
 github.com/cs3org/reva/v2/cmd/revad/runtime


Test patch:
diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go
index 6e6b5ccd0a2..8b307d4330f 100644
--- a/services/graph/pkg/service/v0/driveitems_test.go
+++ b/services/graph/pkg/service/v0/driveitems_test.go
@@ -3,17 +3,21 @@ package svc_test
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"io"
 	"net/http"
 	"net/http/httptest"
 	"time"
 
 	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
 	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
 	"github.com/cs3org/reva/v2/pkg/utils"
 	cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
+	"github.com/go-chi/chi/v5"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	libregraph "github.com/owncloud/libre-graph-api-go"
@@ -44,6 +48,12 @@ var _ = Describe("Driveitems", func() {
 		rr *httptest.ResponseRecorder
 
 		newGroup *libregraph.Group
+
+		currentUser = &userpb.User{
+			Id: &userpb.UserId{
+				OpaqueId: "user",
+			},
+		}
 	)
 
 	BeforeEach(func() {
@@ -84,31 +94,78 @@ var _ = Describe("Driveitems", func() {
 	})
 
 	Describe("GetRootDriveChildren", func() {
-		It("handles failing GetHome", func() {
-			gatewayClient.On("GetHome", mock.Anything, mock.Anything).Return(&provider.GetHomeResponse{
+		It("handles ListStorageSpaces not found ", func() {
+			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
 				Status: status.NewNotFound(ctx, "not found"),
 			}, nil)
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children", nil)
+			r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
 			svc.GetRootDriveChildren(rr, r)
 			Expect(rr.Code).To(Equal(http.StatusNotFound))
 		})
 
-		It("handles failing GetHome", func() {
-			gatewayClient.On("GetHome", mock.Anything, mock.Anything).Return(&provider.GetHomeResponse{
-				Status: status.NewInternal(ctx, "not found"),
+		It("handles ListStorageSpaces error", func() {
+			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
+				Status: status.NewInternal(ctx, "internal error"),
 			}, nil)
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children", nil)
+			r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
+			svc.GetRootDriveChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+		})
+
+		It("handles ListContainer not found", func() {
+			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
+				Status:        status.NewOK(ctx),
+				StorageSpaces: []*provider.StorageSpace{{Owner: currentUser, Root: &provider.ResourceId{}}},
+			}, nil)
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewNotFound(ctx, "not found"),
+			}, nil)
+
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children", nil)
+			r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
+			svc.GetRootDriveChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusNotFound))
+		})
+
+		It("handles ListContainer permission denied", func() {
+			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
+				Status:        status.NewOK(ctx),
+				StorageSpaces: []*provider.StorageSpace{{Owner: currentUser, Root: &provider.ResourceId{}}},
+			}, nil)
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewPermissionDenied(ctx, errors.New("denied"), "denied"),
+			}, nil)
+
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children", nil)
+			r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
+			svc.GetRootDriveChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
+		})
+
+		It("handles ListContainer error", func() {
+			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
+				Status:        status.NewOK(ctx),
+				StorageSpaces: []*provider.StorageSpace{{Owner: currentUser, Root: &provider.ResourceId{}}},
+			}, nil)
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewInternal(ctx, "internal"),
+			}, nil)
+
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children", nil)
+			r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
 			svc.GetRootDriveChildren(rr, r)
 			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
 		})
 
 		It("succeeds", func() {
 			mtime := time.Now()
-			gatewayClient.On("GetHome", mock.Anything, mock.Anything).Return(&provider.GetHomeResponse{
-				Status: status.NewOK(ctx),
-				Path:   "/",
+			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
+				Status:        status.NewOK(ctx),
+				StorageSpaces: []*provider.StorageSpace{{Owner: currentUser, Root: &provider.ResourceId{}}},
 			}, nil)
 			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
 				Status: status.NewOK(ctx),
@@ -122,6 +179,7 @@ var _ = Describe("Driveitems", func() {
 				},
 			}, nil)
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children", nil)
+			r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
 			svc.GetRootDriveChildren(rr, r)
 			Expect(rr.Code).To(Equal(http.StatusOK))
 			data, err := io.ReadAll(rr.Body)
@@ -138,4 +196,82 @@ var _ = Describe("Driveitems", func() {
 			Expect(res.Value[0].GetId()).To(Equal("storageid$spaceid!opaqueid"))
 		})
 	})
+
+	Describe("GetDriveItemChildren", func() {
+		It("handles ListContainer not found", func() {
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewNotFound(ctx, "not found"),
+			}, nil)
+
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives/storageid$spaceid/items/storageid$spaceid!nodeid/children", nil)
+			rctx := chi.NewRouteContext()
+			rctx.URLParams.Add("driveID", "storageid$spaceid")
+			rctx.URLParams.Add("driveItemID", "storageid$spaceid!nodeid")
+			r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
+			svc.GetDriveItemChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusNotFound))
+		})
+
+		It("handles ListContainer permission denied as not found", func() {
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewPermissionDenied(ctx, errors.New("denied"), "denied"),
+			}, nil)
+
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives/storageid$spaceid/items/storageid$spaceid!nodeid/children", nil)
+			rctx := chi.NewRouteContext()
+			rctx.URLParams.Add("driveID", "storageid$spaceid")
+			rctx.URLParams.Add("driveItemID", "storageid$spaceid!nodeid")
+			r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
+			svc.GetDriveItemChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusNotFound))
+		})
+
+		It("handles ListContainer error", func() {
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewInternal(ctx, "internal"),
+			}, nil)
+
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives/storageid$spaceid/items/storageid$spaceid!nodeid/children", nil)
+			rctx := chi.NewRouteContext()
+			rctx.URLParams.Add("driveID", "storageid$spaceid")
+			rctx.URLParams.Add("driveItemID", "storageid$spaceid!nodeid")
+			r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
+			svc.GetDriveItemChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+		})
+
+		It("succeeds", func() {
+			mtime := time.Now()
+			gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+				Status: status.NewOK(ctx),
+				Infos: []*provider.ResourceInfo{
+					{
+						Type:  provider.ResourceType_RESOURCE_TYPE_FILE,
+						Id:    &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
+						Etag:  "etag",
+						Mtime: utils.TimeToTS(mtime),
+					},
+				},
+			}, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives/storageid$spaceid/items/storageid$spaceid!nodeid/children", nil)
+			rctx := chi.NewRouteContext()
+			rctx.URLParams.Add("driveID", "storageid$spaceid")
+			rctx.URLParams.Add("driveItemID", "storageid$spaceid!nodeid")
+			r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
+			svc.GetDriveItemChildren(rr, r)
+			Expect(rr.Code).To(Equal(http.StatusOK))
+			data, err := io.ReadAll(rr.Body)
+			Expect(err).ToNot(HaveOccurred())
+
+			res := itemsList{}
+
+			err = json.Unmarshal(data, &res)
+			Expect(err).ToNot(HaveOccurred())
+
+			Expect(len(res.Value)).To(Equal(1))
+			Expect(res.Value[0].GetLastModifiedDateTime().Equal(mtime)).To(BeTrue())
+			Expect(res.Value[0].GetETag()).To(Equal("etag"))
+			Expect(res.Value[0].GetId()).To(Equal("storageid$spaceid!opaqueid"))
+		})
+	})
 })
diff --git a/services/graph/pkg/service/v0/educationclasses_test.go b/services/graph/pkg/service/v0/educationclasses_test.go
index 58681312774..24b7d5f0866 100644
--- a/services/graph/pkg/service/v0/educationclasses_test.go
+++ b/services/graph/pkg/service/v0/educationclasses_test.go
@@ -134,7 +134,7 @@ var _ = Describe("EducationClass", func() {
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/education/classes", nil)
 			svc.GetEducationClasses(rr, r)
 
-			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
 			data, err := io.ReadAll(rr.Body)
 			Expect(err).ToNot(HaveOccurred())
 
diff --git a/services/graph/pkg/service/v0/educationschools_test.go b/services/graph/pkg/service/v0/educationschools_test.go
index 798a565106c..9b9e4e57a5b 100644
--- a/services/graph/pkg/service/v0/educationschools_test.go
+++ b/services/graph/pkg/service/v0/educationschools_test.go
@@ -130,7 +130,7 @@ var _ = Describe("Schools", func() {
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/education/schools", nil)
 			svc.GetEducationSchools(rr, r)
 
-			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
 			data, err := io.ReadAll(rr.Body)
 			Expect(err).ToNot(HaveOccurred())
 
@@ -268,7 +268,7 @@ var _ = Describe("Schools", func() {
 
 			svc.PostEducationSchool(rr, r)
 
-			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
 		})
 
 		It("creates the school", func() {
diff --git a/services/graph/pkg/service/v0/groups_test.go b/services/graph/pkg/service/v0/groups_test.go
index 79d52c83657..7a492d110a6 100644
--- a/services/graph/pkg/service/v0/groups_test.go
+++ b/services/graph/pkg/service/v0/groups_test.go
@@ -133,7 +133,7 @@ var _ = Describe("Groups", func() {
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups", nil)
 			svc.GetGroups(rr, r)
 
-			Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
 			data, err := io.ReadAll(rr.Body)
 			Expect(err).ToNot(HaveOccurred())
 
diff --git a/tests/acceptance/features/apiSearch/apiSpaceSearch.feature b/tests/acceptance/features/apiSearch/apiSpaceSearch.feature
index fdbd277a705..137f19a04c5 100644
--- a/tests/acceptance/features/apiSearch/apiSpaceSearch.feature
+++ b/tests/acceptance/features/apiSearch/apiSpaceSearch.feature
@@ -16,10 +16,10 @@ Feature: Search
     And user "Alice" has created a space "find data" with the default quota using the GraphApi
     And user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "find data"
     And user "Alice" has uploaded a file inside space "find data" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt"
-    And using new DAV path
 
 
-  Scenario: user can find data from the project space
+  Scenario Outline: user can find data from the project space
+    Given using <dav-path-version> DAV path
     When user "Alice" searches for "*fol*" using the WebDAV API
     Then the HTTP status code should be "207"
     And the search result should contain "4" entries
@@ -28,9 +28,15 @@ Feature: Search
       | /folderMain/SubFolder1                                |
       | /folderMain/SubFolder1/subFOLDER2                     |
       | /folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user can only find data that they searched for from the project space
+  Scenario Outline: user can only find data that they searched for from the project space
+    Given using <dav-path-version> DAV path
     When user "Alice" searches for "*SUB*" using the WebDAV API
     Then the HTTP status code should be "207"
     And the search result should contain "2" entries
@@ -40,10 +46,16 @@ Feature: Search
     But the search result of user "Alice" should not contain these entries:
       | /folderMain                                           |
       | /folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user can find data from the shares
-    Given user "Alice" has created a share inside of space "find data" with settings:
+  Scenario Outline: user can find data from the shares
+    Given using <dav-path-version> DAV path
+    And user "Alice" has created a share inside of space "find data" with settings:
       | path      | folderMain |
       | shareWith | Brian      |
       | role      | viewer     |
@@ -56,19 +68,31 @@ Feature: Search
       | /SubFolder1/subFOLDER2                     |
       | /SubFolder1/subFOLDER2/insideTheFolder.txt |
     And for user "Brian" the search result should contain space "mountpoint/folderMain"
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user can find hidden file
-    Given user "Alice" has created a folder ".space" in space "find data"
+  Scenario Outline: user can find hidden file
+    Given using <dav-path-version> DAV path
+    And user "Alice" has created a folder ".space" in space "find data"
     When user "Alice" searches for "*.sp*" using the WebDAV API
     Then the HTTP status code should be "207"
     And the search result should contain "1" entries
     And the search result of user "Alice" should contain these entries:
       | /.space |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user cannot find pending share
-    Given user "Alice" has created a share inside of space "find data" with settings:
+  Scenario Outline: user cannot find pending share
+    Given using <dav-path-version> DAV path
+    And user "Alice" has created a share inside of space "find data" with settings:
       | path      | folderMain |
       | shareWith | Brian      |
       | role      | viewer     |
@@ -79,10 +103,16 @@ Feature: Search
       | /SubFolder1                                |
       | /SubFolder1/subFOLDER2                     |
       | /SubFolder1/subFOLDER2/insideTheFolder.txt |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user cannot find declined share
-    Given user "Alice" has created a share inside of space "find data" with settings:
+  Scenario Outline: user cannot find declined share
+    Given using <dav-path-version> DAV path
+    And user "Alice" has created a share inside of space "find data" with settings:
       | path      | folderMain |
       | shareWith | Brian      |
       | role      | viewer     |
@@ -94,23 +124,41 @@ Feature: Search
       | /SubFolder1                                |
       | /SubFolder1/subFOLDER2                     |
       | /SubFolder1/subFOLDER2/insideTheFolder.txt |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user cannot find deleted folder
-    Given user "Alice" has removed the folder "folderMain" from space "find data"
+  Scenario Outline: user cannot find deleted folder
+    Given using <dav-path-version> DAV path
+    And user "Alice" has removed the folder "folderMain" from space "find data"
     When user "Alice" searches for "*folderMain*" using the WebDAV API
     Then the HTTP status code should be "207"
     And the search result should contain "0" entries
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user can find project space by name
+  Scenario Outline: user can find project space by name
+    Given using <dav-path-version> DAV path
     When user "Alice" searches for '"*find data*"' using the WebDAV API
     Then the HTTP status code should be "207"
     And the search result should contain "1" entries
     And for user "Alice" the search result should contain space "find data"
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: user can search inside folder in space
+  Scenario Outline: user can search inside folder in space
+    Given using <dav-path-version> DAV path
     When user "Alice" searches for "*folder*" inside folder "/folderMain" in space "find data" using the WebDAV API
     Then the HTTP status code should be "207"
     And the search result should contain "3" entries
@@ -120,10 +168,16 @@ Feature: Search
       | /SubFolder1/subFOLDER2/insideTheFolder.txt |
     But the search result of user "Alice" should not contain these entries:
       | /folderMain |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
 
-  Scenario: search inside folder in shares
-    Given user "Alice" has created a share inside of space "find data" with settings:
+  Scenario Outline: search inside folder in shares
+    Given using <dav-path-version> DAV path
+    And user "Alice" has created a share inside of space "find data" with settings:
       | path      | folderMain |
       | shareWith | Brian      |
       | role      | viewer     |
@@ -136,10 +190,38 @@ Feature: Search
       | /SubFolder1/subFOLDER2/insideTheFolder.txt |
     But the search result of user "Brian" should not contain these entries:
       | /folderMain |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
 
-  @issue-enterprise-6000
-  Scenario: sharee cannot find resources that are not shared
-    Given user "Alice" has created a folder "foo/sharedToBrian" in space "Alice Hansen"
+
+  @skipOnStable3.0
+  Scenario Outline: search files inside the folder
+    Given using <dav-path-version> DAV path
+    And user "Alice" has uploaded file with content "hello world inside root" to "file1.txt"
+    And user "Alice" has created folder "/Folder"
+    And user "Alice" has uploaded file with content "hello world inside folder" to "/Folder/file2.txt"
+    And user "Alice" has created folder "/Folder/SubFolder"
+    And user "Alice" has uploaded file with content "hello world inside sub-folder" to "/Folder/SubFolder/file3.txt"
+    When user "Alice" searches for "*file*" inside folder "/Folder" using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the search result of user "Alice" should contain only these entries:
+      | file2.txt |
+      | file3.txt |
+    But the search result of user "Alice" should not contain these entries:
+      | file1.txt |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
+
+  @issue-enterprise-6000 @issue-7028 @issue-7092
+  Scenario Outline: sharee cannot find resources that are not shared
+    Given using <dav-path-version> DAV path
+    And user "Alice" has created a folder "foo/sharedToBrian" in space "Alice Hansen"
     And user "Alice" has created a folder "sharedToCarol" in space "Alice Hansen"
     And user "Alice" has created a share inside of space "Alice Hansen" with settings:
       | path      | foo    |
@@ -152,3 +234,25 @@ Feature: Search
       | /sharedToBrian |
     But the search result of user "Brian" should not contain these entries:
       | /sharedToCarol |
+    Examples:
+      | dav-path-version |
+      | old              |
+      | new              |
+      | spaces           |
+
+
+  Scenario Outline: search resources using different search patterns (KQL feature)
+    Given using spaces DAV path
+    And user "Alice" has created a folder "subfolder" in space "find data"
+    When user "Alice" searches for '<pattern>' using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the search result should contain "1" entries
+    And the search result of user "Alice" should contain these entries:
+      | <search-result> |
+    Examples:
+      | description                                            | pattern      | search-result                     |
+      | starts with                                            | fold*        | /folderMain                       |
+      | ends with                                              | *der1        | /folderMain/SubFolder1            |
+      | strict search                                          | subfolder    | /subfolder                        |
+      | using patern "name:"                                   | name:*der2   | /folderMain/SubFolder1/subFOLDER2 |
+      | using the pattern "name:" where the value is in quotes | name:"*der2" | /folderMain/SubFolder1/subFOLDER2 |
diff --git a/tests/acceptance/features/apiSearch/contentSearch.feature b/tests/acceptance/features/apiSearch/contentSearch.feature
index 7be1880d201..d80b92ae890 100644
--- a/tests/acceptance/features/apiSearch/contentSearch.feature
+++ b/tests/acceptance/features/apiSearch/contentSearch.feature
@@ -46,10 +46,6 @@ Feature: content search
       | dav-path-version |
       | old              |
       | new              |
-
-    @skipOnStable3.0
-    Examples:
-      | dav-path-version |
       | spaces           |
 
 
@@ -125,10 +121,6 @@ Feature: content search
       | dav-path-version |
       | old              |
       | new              |
-
-    @skipOnStable3.0
-    Examples:
-      | dav-path-version |
       | spaces           |
 
 
@@ -156,8 +148,27 @@ Feature: content search
       | dav-path-version |
       | old              |
       | new              |
+      | spaces           |
+
 
-    @skipOnStable3.0
+  Scenario Outline: search resources using different search patterns (KQL feature)
+    Given using spaces DAV path
+    And user "Alice" has uploaded file with content "hello world, let start to test" to "technical task.txt"
+    And user "Alice" has uploaded file with content "it's been hell" to "task comments.txt"
+    And user "Alice" has tagged the following files of the space "Personal":
+      | path               | tagName |
+      | technical task.txt | test    |
+    When user "Alice" searches for '<pattern>' using the WebDAV API
+    Then the HTTP status code should be "207"
+    And the search result should contain "<result-count>" entries
+    And the search result of user "Alice" should contain these entries:
+      | <search-result-1> |
+      | <search-result-2> |
     Examples:
-      | dav-path-version |
-      | spaces           |
+      | pattern                                     | result-count | search-result-1     | search-result-2    |
+      | Content:hello                               | 1            | /technical task.txt |                    |
+      | content:hello                               | 1            | /technical task.txt |                    |
+      | content:"hello"                             | 1            | /technical task.txt |                    |
+      | content:hel*                                | 2            | /technical task.txt | /task comments.txt |
+      | content:hel* AND tag:test                   | 1            | /technical task.txt |                    |
+      | (name:*task* AND content:hel*) NOT tag:test | 1            | /task comments.txt  |                    |
diff --git a/tests/acceptance/features/apiSearch/tagSearch.feature b/tests/acceptance/features/apiSearch/tagSearch.feature
index 239192a6200..0701a829a41 100644
--- a/tests/acceptance/features/apiSearch/tagSearch.feature
+++ b/tests/acceptance/features/apiSearch/tagSearch.feature
@@ -235,23 +235,43 @@ Feature: tag search
       | new              |
       | spaces           |
 
-  @skipOnStable3.0
-  Scenario Outline: search files inside the folder
-    Given using <dav-path-version> DAV path
-    And user "Alice" has uploaded file with content "hello world inside root" to "file1.txt"
-    And user "Alice" has created folder "/Folder"
-    And user "Alice" has uploaded file with content "hello world inside folder" to "/Folder/file2.txt"
-    And user "Alice" has created folder "/Folder/SubFolder"
-    And user "Alice" has uploaded file with content "hello world inside sub-folder" to "/Folder/SubFolder/file3.txt"
-    When user "Alice" searches for "*file*" inside folder "/Folder" using the WebDAV API
+
+  Scenario Outline: search resources using different search patterns (KQL feature)
+    Given using spaces DAV path
+    And user "Alice" has created the following folders
+      | path              |
+      | exercises         |
+      | answers           |
+      | verification work |
+      | withoutTagFolder  |
+    And user "Alice" has tagged the following folders of the space "Personal":
+      | path              | tagName |
+      | exercises         | klass10 |
+      | exercises         | mathe   |
+      | exercises         | physik  |
+      | answers           | klass10 |
+      | answers           | mathe   |
+      | answers           | chemi   |
+      | verification work | klass10 |
+    When user "Alice" searches for '<pattern>' using the WebDAV API
     Then the HTTP status code should be "207"
-    And the search result of user "Alice" should contain only these entries:
-      | file2.txt |
-      | file3.txt |
-    But the search result of user "Alice" should not contain these entries:
-      | file1.txt |
+    And the search result should contain "<result-count>" entries
+    And the search result of user "Alice" should contain these entries:
+      | <search-result-1> |
+      | <search-result-2> |
     Examples:
-      | dav-path-version |
-      | old              |
-      | new              |
-      | spaces           |
+      | pattern                                   | result-count | search-result-1    | search-result-2    |
+      | Tags:mathe                                | 2            | /exercises         | /answers           |
+      | tag:mathe                                 | 2            | /exercises         | /answers           |
+      | tag:(mathe klass10)                       | 2            | /exercises         | /answers           |
+      | tag:klass10  AND tag:chemi                | 1            | /answers           |                    |
+      | tag:chemi OR tag:physik                   | 2            | /exercises         | /answers           |
+      # why we need use quotes? it'll be fixed in future. Actual: search result gives all user content
+      #| (tag:klass10) NOT tag:physik              | 2      | /answers           | verification work |
+      | (tag:(mathe klass10)) NOT tag:chemi       | 1            | /exercises         |                    |
+      | (tag:mathe OR tag:klass10) NOT tag:physik | 2            | /answers           | /verification work |
+      | tag:mathe NOT name:exercises              | 1            | /answers           |                    |
+      | tag:mathe AND NOT name:exercises          | 1            | /answers           |                    |
+      # The third finding is the personal space itself
+      | NOT tag:mathe                             | 3            | /verification work | /withoutTagFolder  |
+
diff --git a/tests/acceptance/features/bootstrap/SearchContext.php b/tests/acceptance/features/bootstrap/SearchContext.php
index b0fd1e81357..a3163aa304b 100644
--- a/tests/acceptance/features/bootstrap/SearchContext.php
+++ b/tests/acceptance/features/bootstrap/SearchContext.php
@@ -169,28 +169,4 @@ class SearchContext implements Context {
 		// Get all the contexts you need in this context
 		$this->featureContext = $environment->getContext('FeatureContext');
 	}
-
-	/**
-	 * @Then the search result by tags for user :user should contain these entries:
-	 *
-	 * @param string|null $user
-	 * @param TableNode $expectedEntries
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function theSearchResultByTagsForUserShouldContainTheseEntries(
-		?string $user,
-		TableNode $expectedEntries
-	):void {
-		$user = $this->featureContext->getActualUsername($user);
-		$this->featureContext->verifyTableNodeColumnsCount($expectedEntries, 1);
-		$expectedEntries = $expectedEntries->getRows();
-		$expectedEntriesArray = [];
-		$responseResourcesArray = $this->featureContext->findEntryFromReportResponse($user);
-		foreach ($expectedEntries as $item) {
-			$expectedEntriesArray[] = $item[0];
-		}
-		Assert::assertEqualsCanonicalizing($expectedEntriesArray, $responseResourcesArray);
-	}
 }