Repository: owncloud/ocis
Pull request #3809 (2022-05-17 10:47:58)

ApiTest. download files
Api test:

- [x] downloading file via HEAD method
- [x] downloading different versions of files


Patch:
diff --git a/.drone.env b/.drone.env
index 27d9dcdbcb0..33d0b580567 100644
--- a/.drone.env
+++ b/.drone.env
@@ -1,5 +1,5 @@
 # The test runner source for API tests
-CORE_COMMITID=ee75bfbb934ec300e8aa8f45e326c5308d50bdc7
+CORE_COMMITID=6321f96cf6739d9130c10376e8e4fc96af75edef
 CORE_BRANCH=master
 
 # The test runner source for UI tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35e4c8a78a4..a11e70aab99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ The following sections list the changes for unreleased.
 * Change - The `glauth` and `accounts` services are removed: [#3685](https://github.com/owncloud/ocis/pull/3685)
 * Change - Prevent access to disabled space: [#3779](https://github.com/owncloud/ocis/pull/3779)
 * Enhancement - Add descriptions for graph-explorer config: [#3759](https://github.com/owncloud/ocis/pull/3759)
+* Enhancement - Add /me/changePassword endpoint to GraphAPI: [#3063](https://github.com/owncloud/ocis/issues/3063)
 * Enhancement - Add description tags to the thumbnails config structs: [#3752](https://github.com/owncloud/ocis/pull/3752)
 * Enhancement - Update reva: [#3746](https://github.com/owncloud/ocis/pull/3746)
 * Enhancement - Add descriptions to webdav configuration: [#3755](https://github.com/owncloud/ocis/pull/3755)
@@ -93,6 +94,14 @@ The following sections list the changes for unreleased.
 
    https://github.com/owncloud/ocis/pull/3759
 
+* Enhancement - Add /me/changePassword endpoint to GraphAPI: [#3063](https://github.com/owncloud/ocis/issues/3063)
+
+   When using the builtin user management, allow users to update their own password via the
+   graph/v1.0/me/changePassword endpoint.
+
+   https://github.com/owncloud/ocis/issues/3063
+   https://github.com/owncloud/ocis/pull/3705
+
 * Enhancement - Add description tags to the thumbnails config structs: [#3752](https://github.com/owncloud/ocis/pull/3752)
 
    Added description tags to the config structs in the thumbnails service so they will be included
diff --git a/changelog/unreleased/graph-me-changepw.md b/changelog/unreleased/graph-me-changepw.md
new file mode 100644
index 00000000000..607ae4848a2
--- /dev/null
+++ b/changelog/unreleased/graph-me-changepw.md
@@ -0,0 +1,7 @@
+Enhancement: Add /me/changePassword endpoint to GraphAPI
+
+When using the builtin user management, allow users to update their own password via
+the graph/v1.0/me/changePassword endpoint.
+
+https://github.com/owncloud/ocis/issues/3063
+https://github.com/owncloud/ocis/pull/3705
diff --git a/deployments/examples/oc10_ocis_parallel/docker-compose.yml b/deployments/examples/oc10_ocis_parallel/docker-compose.yml
index b3a368a4967..a1d137df193 100644
--- a/deployments/examples/oc10_ocis_parallel/docker-compose.yml
+++ b/deployments/examples/oc10_ocis_parallel/docker-compose.yml
@@ -126,6 +126,7 @@ services:
       PROXY_TLS: "false" # do not use SSL between Traefik and oCIS
       # INSECURE: needed if oCIS / Traefik is using self generated certificates
       OCIS_INSECURE: "${INSECURE:-false}"
+      GRAPH_LDAP_INSECURE: true # https://github.com/owncloud/ocis/issues/3812
       # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
       PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
     volumes:
diff --git a/deployments/examples/ocis_hello/docker-compose.yml b/deployments/examples/ocis_hello/docker-compose.yml
index 0b416104636..34a36e716a3 100644
--- a/deployments/examples/ocis_hello/docker-compose.yml
+++ b/deployments/examples/ocis_hello/docker-compose.yml
@@ -60,6 +60,7 @@ services:
       SETTINGS_GRPC_ADDR: 0.0.0.0:9191
       # INSECURE: needed if oCIS / Traefik is using self generated certificates
       OCIS_INSECURE: "${INSECURE:-false}"
+      GRAPH_LDAP_INSECURE: true # https://github.com/owncloud/ocis/issues/3812
       # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
       PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
       # admin user password
diff --git a/deployments/examples/ocis_ldap/docker-compose.yml b/deployments/examples/ocis_ldap/docker-compose.yml
index e82a3eb9122..019045c53bb 100644
--- a/deployments/examples/ocis_ldap/docker-compose.yml
+++ b/deployments/examples/ocis_ldap/docker-compose.yml
@@ -81,6 +81,7 @@ services:
       PROXY_TLS: "false" # do not use SSL between Traefik and oCIS
       # INSECURE: needed if oCIS / Traefik is using self generated certificates
       OCIS_INSECURE: "${INSECURE:-false}"
+      GRAPH_LDAP_INSECURE: true # https://github.com/owncloud/ocis/issues/3812
       # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
       PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
       # admin user password
diff --git a/deployments/examples/ocis_s3/docker-compose.yml b/deployments/examples/ocis_s3/docker-compose.yml
index 49f34649887..ec48ecea954 100644
--- a/deployments/examples/ocis_s3/docker-compose.yml
+++ b/deployments/examples/ocis_s3/docker-compose.yml
@@ -67,6 +67,7 @@ services:
       STORAGE_USERS_S3NG_BUCKET: ${MINIO_BUCKET:-ocis-bucket}
       # INSECURE: needed if oCIS / Traefik is using self generated certificates
       OCIS_INSECURE: "${INSECURE:-false}"
+      GRAPH_LDAP_INSECURE: true # https://github.com/owncloud/ocis/issues/3812
       # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
       PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
       # admin user password
diff --git a/deployments/examples/ocis_traefik/docker-compose.yml b/deployments/examples/ocis_traefik/docker-compose.yml
index 456d3187ed3..3681efe4e48 100644
--- a/deployments/examples/ocis_traefik/docker-compose.yml
+++ b/deployments/examples/ocis_traefik/docker-compose.yml
@@ -58,6 +58,7 @@ services:
       PROXY_TLS: "false" # do not use SSL between Traefik and oCIS
       # INSECURE: needed if oCIS / Traefik is using self generated certificates
       OCIS_INSECURE: "${INSECURE:-false}"
+      GRAPH_LDAP_INSECURE: true # https://github.com/owncloud/ocis/issues/3812
       # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
       PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
       # admin user password
diff --git a/deployments/examples/ocis_wopi/docker-compose.yml b/deployments/examples/ocis_wopi/docker-compose.yml
index af3cb4a4a85..7bde4243627 100644
--- a/deployments/examples/ocis_wopi/docker-compose.yml
+++ b/deployments/examples/ocis_wopi/docker-compose.yml
@@ -62,6 +62,7 @@ services:
       GATEWAY_GRPC_ADDR: 0.0.0.0:9142 # make the REVA gateway accessible to the app drivers
       # INSECURE: needed if oCIS / Traefik is using self generated certificates
       OCIS_INSECURE: "${INSECURE:-false}"
+      GRAPH_LDAP_INSECURE: true # https://github.com/owncloud/ocis/issues/3812
       # basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)
       PROXY_ENABLE_BASIC_AUTH: "${PROXY_ENABLE_BASIC_AUTH:-false}"
       # admin user password
diff --git a/docs/extensions/graph/users.md b/docs/extensions/graph/users.md
index 2b5e18dc040..d8715d7b792 100644
--- a/docs/extensions/graph/users.md
+++ b/docs/extensions/graph/users.md
@@ -163,3 +163,27 @@ to be updated. E.g. to update the `displayName` Attribute:
 ```
 
 Similar to creating a user via `POST`,  the `PATCH` request will return the user object containing the new attribute values.
+
+### Change password
+
+#### `POST /me/changePassword`
+
+Users can change their own password by sending a POST request to `/me/changePassword`
+
+##### Request Body
+
+```
+{
+  "currentPassword": "current",
+  "newPassword": "new"
+}
+
+```
+
+When successful the API returns no response body and the HTTP status code 204 (No Content)
+
+```
+ curl -i -k --header "Content-Type: application/json" \
+      --request POST  --data '{"currentPassword": "current", "newPassword": "new" }' \
+      'https://localhost:9200/graph/v1.0/me/changePassword' -u user:current
+```
diff --git a/extensions/app-provider/pkg/config/config.go b/extensions/app-provider/pkg/config/config.go
index 5b056762b86..9a6ddbd6c02 100644
--- a/extensions/app-provider/pkg/config/config.go
+++ b/extensions/app-provider/pkg/config/config.go
@@ -45,10 +45,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"APP_PROVIDER_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"APP_PROVIDER_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"APP_PROVIDER_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"APP_PROVIDER_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"APP_PROVIDER_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"APP_PROVIDER_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"APP_PROVIDER_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"APP_PROVIDER_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-memory."`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/app-registry/pkg/config/config.go b/extensions/app-registry/pkg/config/config.go
index f8b00b1365c..3968f5ba63c 100644
--- a/extensions/app-registry/pkg/config/config.go
+++ b/extensions/app-registry/pkg/config/config.go
@@ -43,10 +43,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"APP_REGISTRY_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"APP_REGISTRY_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"APP_REGISTRY_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"APP_REGISTRY_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"APP_REGISTRY_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"APP_REGISTRY_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"APP_REGISTRY_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"APP_REGISTRY_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/audit/pkg/config/config.go b/extensions/audit/pkg/config/config.go
index 73141351b06..0e904ff0a80 100644
--- a/extensions/audit/pkg/config/config.go
+++ b/extensions/audit/pkg/config/config.go
@@ -30,8 +30,8 @@ type Events struct {
 
 // Auditlog holds audit log information
 type Auditlog struct {
-	LogToConsole bool   `yaml:"log_to_console" env:"AUDIT_LOG_TO_CONSOLE" desc:"Logs to Stdout if true."`
-	LogToFile    bool   `yaml:"log_to_file" env:"AUDIT_LOG_TO_FILE" desc:"Logs to file if true."`
+	LogToConsole bool   `yaml:"log_to_console" env:"AUDIT_LOG_TO_CONSOLE" desc:"Logs to Stdout if true. Independent of the log to file option."`
+	LogToFile    bool   `yaml:"log_to_file" env:"AUDIT_LOG_TO_FILE" desc:"Logs to file if true. Independent of the log to Stdout file option."`
 	FilePath     string `yaml:"filepath" env:"AUDIT_FILEPATH" desc:"Filepath to the logfile. Mandatory if LogToFile is true."`
 	Format       string `yaml:"format" env:"AUDIT_FORMAT" desc:"Log format. using json is advised."`
 }
diff --git a/extensions/audit/pkg/config/debug.go b/extensions/audit/pkg/config/debug.go
index 398f2e712bd..7d34e05bd99 100644
--- a/extensions/audit/pkg/config/debug.go
+++ b/extensions/audit/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"AUDIT_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"AUDIT_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"AUDIT_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"AUDIT_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"AUDIT_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"AUDIT_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"AUDIT_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"AUDIT_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/auth-basic/pkg/config/config.go b/extensions/auth-basic/pkg/config/config.go
index 6ae9b1507f0..0780f32439d 100644
--- a/extensions/auth-basic/pkg/config/config.go
+++ b/extensions/auth-basic/pkg/config/config.go
@@ -44,10 +44,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"AUTH_BASIC_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"AUTH_BASIC_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"AUTH_BASIC_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"AUTH_BASIC_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"AUTH_BASIC_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"AUTH_BASIC_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"AUTH_BASIC_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"AUTH_BASIC_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/auth-bearer/pkg/config/config.go b/extensions/auth-bearer/pkg/config/config.go
index 3973c46155a..7ddce311684 100644
--- a/extensions/auth-bearer/pkg/config/config.go
+++ b/extensions/auth-bearer/pkg/config/config.go
@@ -44,10 +44,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"AUTH_BEARER_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"AUTH_BEARER_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"AUTH_BEARER_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"AUTH_BEARER_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"AUTH_BEARER_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"AUTH_BEARER_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"AUTH_BEARER_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"AUTH_BEARER_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/auth-machine/pkg/config/config.go b/extensions/auth-machine/pkg/config/config.go
index 98473058b48..7c9b02264da 100644
--- a/extensions/auth-machine/pkg/config/config.go
+++ b/extensions/auth-machine/pkg/config/config.go
@@ -44,10 +44,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"AUTH_MACHINE_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"AUTH_MACHINE_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"AUTH_MACHINE_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"AUTH_MACHINE_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"AUTH_MACHINE_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"AUTH_MACHINE_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"AUTH_MACHINE_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"AUTH_MACHINE_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/frontend/pkg/config/config.go b/extensions/frontend/pkg/config/config.go
index 449e7f4f8f2..e49e31c2b64 100644
--- a/extensions/frontend/pkg/config/config.go
+++ b/extensions/frontend/pkg/config/config.go
@@ -64,10 +64,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"FRONTEND_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"FRONTEND_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"FRONTEND_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"FRONTEND_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"FRONTEND_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"FRONTEND_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"FRONTEND_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"FRONTEND_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type HTTPConfig struct {
diff --git a/extensions/gateway/pkg/config/config.go b/extensions/gateway/pkg/config/config.go
index c14064523b0..935cea9d2d9 100644
--- a/extensions/gateway/pkg/config/config.go
+++ b/extensions/gateway/pkg/config/config.go
@@ -68,10 +68,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"GATEWAY_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"GATEWAY_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"GATEWAY_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"GATEWAY_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"GATEWAY_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"GATEWAY_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"GATEWAY_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"GATEWAY_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/graph-explorer/pkg/config/debug.go b/extensions/graph-explorer/pkg/config/debug.go
index 8a781e439e5..ec2da8ade10 100644
--- a/extensions/graph-explorer/pkg/config/debug.go
+++ b/extensions/graph-explorer/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"GRAPH_EXPLORER_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"GRAPH_EXPLORER_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"GRAPH_EXPLORER_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"GRAPH_EXPLORER_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"GRAPH_EXPLORER_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"GRAPH_EXPLORER_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"GRAPH_EXPLORER_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"GRAPH_EXPLORER_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/graph/Makefile b/extensions/graph/Makefile
index 67ad0d888d2..d17a086a335 100644
--- a/extensions/graph/Makefile
+++ b/extensions/graph/Makefile
@@ -28,6 +28,7 @@ ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this
 	$(MOCKERY) --dir pkg/service/v0 --case underscore --name GatewayClient
 	$(MOCKERY) --dir pkg/service/v0 --case underscore --name HTTPClient
 	$(MOCKERY) --dir pkg/service/v0 --case underscore --name Publisher
+	$(MOCKERY) --srcpkg github.com/go-ldap/ldap/v3 --case underscore --filename ldapclient.go --name Client
 
 
 .PHONY: ci-node-generate
diff --git a/extensions/graph/mocks/gateway_client.go b/extensions/graph/mocks/gateway_client.go
index c2ba9bc1f2f..aba88f1eb7a 100644
--- a/extensions/graph/mocks/gateway_client.go
+++ b/extensions/graph/mocks/gateway_client.go
@@ -18,6 +18,36 @@ type GatewayClient struct {
 	mock.Mock
 }
 
+// Authenticate provides a mock function with given fields: ctx, in, opts
+func (_m *GatewayClient) Authenticate(ctx context.Context, in *gatewayv1beta1.AuthenticateRequest, opts ...grpc.CallOption) (*gatewayv1beta1.AuthenticateResponse, error) {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, in)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 *gatewayv1beta1.AuthenticateResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) *gatewayv1beta1.AuthenticateResponse); ok {
+		r0 = rf(ctx, in, opts...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*gatewayv1beta1.AuthenticateResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) error); ok {
+		r1 = rf(ctx, in, opts...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // CreateStorageSpace provides a mock function with given fields: ctx, in, opts
 func (_m *GatewayClient) CreateStorageSpace(ctx context.Context, in *providerv1beta1.CreateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateStorageSpaceResponse, error) {
 	_va := make([]interface{}, len(opts))
diff --git a/extensions/graph/mocks/ldapclient.go b/extensions/graph/mocks/ldapclient.go
new file mode 100644
index 00000000000..f2c10eb7539
--- /dev/null
+++ b/extensions/graph/mocks/ldapclient.go
@@ -0,0 +1,294 @@
+// Code generated by mockery v2.10.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	ldap "github.com/go-ldap/ldap/v3"
+	mock "github.com/stretchr/testify/mock"
+
+	time "time"
+
+	tls "crypto/tls"
+)
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+	mock.Mock
+}
+
+// Add provides a mock function with given fields: _a0
+func (_m *Client) Add(_a0 *ldap.AddRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.AddRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Bind provides a mock function with given fields: username, password
+func (_m *Client) Bind(username string, password string) error {
+	ret := _m.Called(username, password)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, string) error); ok {
+		r0 = rf(username, password)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Close provides a mock function with given fields:
+func (_m *Client) Close() {
+	_m.Called()
+}
+
+// Compare provides a mock function with given fields: dn, attribute, value
+func (_m *Client) Compare(dn string, attribute string, value string) (bool, error) {
+	ret := _m.Called(dn, attribute, value)
+
+	var r0 bool
+	if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
+		r0 = rf(dn, attribute, value)
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
+		r1 = rf(dn, attribute, value)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Del provides a mock function with given fields: _a0
+func (_m *Client) Del(_a0 *ldap.DelRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.DelRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ExternalBind provides a mock function with given fields:
+func (_m *Client) ExternalBind() error {
+	ret := _m.Called()
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func() error); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// IsClosing provides a mock function with given fields:
+func (_m *Client) IsClosing() bool {
+	ret := _m.Called()
+
+	var r0 bool
+	if rf, ok := ret.Get(0).(func() bool); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	return r0
+}
+
+// Modify provides a mock function with given fields: _a0
+func (_m *Client) Modify(_a0 *ldap.ModifyRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.ModifyRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ModifyDN provides a mock function with given fields: _a0
+func (_m *Client) ModifyDN(_a0 *ldap.ModifyDNRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.ModifyDNRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ModifyWithResult provides a mock function with given fields: _a0
+func (_m *Client) ModifyWithResult(_a0 *ldap.ModifyRequest) (*ldap.ModifyResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.ModifyResult
+	if rf, ok := ret.Get(0).(func(*ldap.ModifyRequest) *ldap.ModifyResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.ModifyResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.ModifyRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// PasswordModify provides a mock function with given fields: _a0
+func (_m *Client) PasswordModify(_a0 *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.PasswordModifyResult
+	if rf, ok := ret.Get(0).(func(*ldap.PasswordModifyRequest) *ldap.PasswordModifyResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.PasswordModifyResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.PasswordModifyRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Search provides a mock function with given fields: _a0
+func (_m *Client) Search(_a0 *ldap.SearchRequest) (*ldap.SearchResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.SearchResult
+	if rf, ok := ret.Get(0).(func(*ldap.SearchRequest) *ldap.SearchResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.SearchResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.SearchRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SearchWithPaging provides a mock function with given fields: searchRequest, pagingSize
+func (_m *Client) SearchWithPaging(searchRequest *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error) {
+	ret := _m.Called(searchRequest, pagingSize)
+
+	var r0 *ldap.SearchResult
+	if rf, ok := ret.Get(0).(func(*ldap.SearchRequest, uint32) *ldap.SearchResult); ok {
+		r0 = rf(searchRequest, pagingSize)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.SearchResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.SearchRequest, uint32) error); ok {
+		r1 = rf(searchRequest, pagingSize)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SetTimeout provides a mock function with given fields: _a0
+func (_m *Client) SetTimeout(_a0 time.Duration) {
+	_m.Called(_a0)
+}
+
+// SimpleBind provides a mock function with given fields: _a0
+func (_m *Client) SimpleBind(_a0 *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.SimpleBindResult
+	if rf, ok := ret.Get(0).(func(*ldap.SimpleBindRequest) *ldap.SimpleBindResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.SimpleBindResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.SimpleBindRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Start provides a mock function with given fields:
+func (_m *Client) Start() {
+	_m.Called()
+}
+
+// StartTLS provides a mock function with given fields: _a0
+func (_m *Client) StartTLS(_a0 *tls.Config) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*tls.Config) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// UnauthenticatedBind provides a mock function with given fields: username
+func (_m *Client) UnauthenticatedBind(username string) error {
+	ret := _m.Called(username)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string) error); ok {
+		r0 = rf(username)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/extensions/graph/pkg/config/debug.go b/extensions/graph/pkg/config/debug.go
index ceff19159ba..004ccb919cd 100644
--- a/extensions/graph/pkg/config/debug.go
+++ b/extensions/graph/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"GRAPH_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"GRAPH_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"GRAPH_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"GRAPH_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"GRAPH_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"GRAPH_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"GRAPH_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"GRAPH_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/graph/pkg/service/v0/graph.go b/extensions/graph/pkg/service/v0/graph.go
index 676c8d7b955..1b0ab42462c 100644
--- a/extensions/graph/pkg/service/v0/graph.go
+++ b/extensions/graph/pkg/service/v0/graph.go
@@ -17,12 +17,14 @@ import (
 	"google.golang.org/grpc"
 )
 
-//go:generate make generate
+//go:generate make -C ../../.. generate
 
 // GatewayClient is the subset of the gateway.GatewayAPIClient that is being used to interact with the gateway
 type GatewayClient interface {
 	//gateway.GatewayAPIClient
 
+	// Authenticates a user.
+	Authenticate(ctx context.Context, in *gateway.AuthenticateRequest, opts ...grpc.CallOption) (*gateway.AuthenticateResponse, error)
 	// Returns the home path for the given authenticated user.
 	// When a user has access to multiple storage providers, one of them is the home.
 	GetHome(ctx context.Context, in *provider.GetHomeRequest, opts ...grpc.CallOption) (*provider.GetHomeResponse, error)
diff --git a/extensions/graph/pkg/service/v0/instrument.go b/extensions/graph/pkg/service/v0/instrument.go
index 0b31c66172d..7cad7f7c9c0 100644
--- a/extensions/graph/pkg/service/v0/instrument.go
+++ b/extensions/graph/pkg/service/v0/instrument.go
@@ -54,6 +54,11 @@ func (i instrument) PatchUser(w http.ResponseWriter, r *http.Request) {
 	i.next.PatchUser(w, r)
 }
 
+// ChangeOwnPassword implements the Service interface.
+func (i instrument) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	i.next.ChangeOwnPassword(w, r)
+}
+
 // GetGroups implements the Service interface.
 func (i instrument) GetGroups(w http.ResponseWriter, r *http.Request) {
 	i.next.GetGroups(w, r)
diff --git a/extensions/graph/pkg/service/v0/logging.go b/extensions/graph/pkg/service/v0/logging.go
index 6b28bf523bb..51213475d7e 100644
--- a/extensions/graph/pkg/service/v0/logging.go
+++ b/extensions/graph/pkg/service/v0/logging.go
@@ -54,6 +54,11 @@ func (l logging) PatchUser(w http.ResponseWriter, r *http.Request) {
 	l.next.PatchUser(w, r)
 }
 
+// ChangeOwnPassword implements the Service interface.
+func (l logging) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	l.next.ChangeOwnPassword(w, r)
+}
+
 // GetGroups implements the Service interface.
 func (l logging) GetGroups(w http.ResponseWriter, r *http.Request) {
 	l.next.GetGroups(w, r)
diff --git a/extensions/graph/pkg/service/v0/option.go b/extensions/graph/pkg/service/v0/option.go
index a5feb4e69d4..b8ebe47479c 100644
--- a/extensions/graph/pkg/service/v0/option.go
+++ b/extensions/graph/pkg/service/v0/option.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/cs3org/reva/v2/pkg/events"
 	"github.com/owncloud/ocis/v2/extensions/graph/pkg/config"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/identity"
 	"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"
@@ -19,6 +20,7 @@ type Options struct {
 	Config          *config.Config
 	Middleware      []func(http.Handler) http.Handler
 	GatewayClient   GatewayClient
+	IdentityBackend identity.Backend
 	HTTPClient      HTTPClient
 	RoleService     settingssvc.RoleService
 	RoleManager     *roles.Manager
@@ -64,6 +66,13 @@ func WithGatewayClient(val GatewayClient) Option {
 	}
 }
 
+// WithIdentityBackend provides a function to set the IdentityBackend option.
+func WithIdentityBackend(val identity.Backend) Option {
+	return func(o *Options) {
+		o.IdentityBackend = val
+	}
+}
+
 // WithHTTPClient provides a function to set the http client option.
 func WithHTTPClient(val HTTPClient) Option {
 	return func(o *Options) {
diff --git a/extensions/graph/pkg/service/v0/password.go b/extensions/graph/pkg/service/v0/password.go
new file mode 100644
index 00000000000..51da7b41ba5
--- /dev/null
+++ b/extensions/graph/pkg/service/v0/password.go
@@ -0,0 +1,98 @@
+package svc
+
+import (
+	"encoding/json"
+	"net/http"
+	"strings"
+
+	"github.com/CiscoM31/godata"
+	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/events"
+	"github.com/go-chi/render"
+	libregraph "github.com/owncloud/libre-graph-api-go"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode"
+)
+
+// ChangeOwnPassword implements the Service interface. It allows the user to change
+// its own password
+func (g Graph) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	ctx := r.Context()
+	u, ok := revactx.ContextGetUser(ctx)
+	if !ok {
+		g.logger.Error().Msg("user not in context")
+		errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "user not in context")
+		return
+	}
+
+	sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
+	_, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
+	if err != nil {
+		g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error")
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+		return
+	}
+	cpw := libregraph.NewPasswordChange()
+	err = json.NewDecoder(r.Body).Decode(cpw)
+	if err != nil {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+		return
+	}
+
+	currentPw := cpw.GetCurrentPassword()
+	if currentPw == "" {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "current password cannot be empty")
+		return
+	}
+
+	newPw := cpw.GetNewPassword()
+	if newPw == "" {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "new password cannot be empty")
+		return
+	}
+
+	if newPw == currentPw {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "new password must be differnt from current password")
+		return
+	}
+
+	authReq := &gateway.AuthenticateRequest{
+		Type:         "basic",
+		ClientId:     u.Username,
+		ClientSecret: currentPw,
+	}
+	authRes, err := g.gatewayClient.Authenticate(r.Context(), authReq)
+	if err != nil {
+		errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	if authRes.Status.Code != cs3rpc.Code_CODE_OK {
+		errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "password change failed")
+		return
+	}
+
+	newPwProfile := libregraph.NewPasswordProfile()
+	newPwProfile.SetPassword(newPw)
+	changes := libregraph.NewUser()
+	changes.SetPasswordProfile(*newPwProfile)
+	_, err = g.identityBackend.UpdateUser(ctx, u.Id.OpaqueId, *changes)
+	if err != nil {
+		errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "password change failed")
+		g.logger.Debug().Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to update user password")
+		return
+	}
+
+	g.publishEvent(
+		events.UserFeatureChanged{
+			UserID: u.Id.OpaqueId,
+			Features: []events.UserFeature{
+				events.UserFeature{Name: "password", Value: "***"},
+			},
+		},
+	)
+
+	render.Status(r, http.StatusNoContent)
+	render.NoContent(w, r)
+}
diff --git a/extensions/graph/pkg/service/v0/service.go b/extensions/graph/pkg/service/v0/service.go
index 4184a1fea57..cf328ce0503 100644
--- a/extensions/graph/pkg/service/v0/service.go
+++ b/extensions/graph/pkg/service/v0/service.go
@@ -35,6 +35,7 @@ type Service interface {
 	PostUser(http.ResponseWriter, *http.Request)
 	DeleteUser(http.ResponseWriter, *http.Request)
 	PatchUser(http.ResponseWriter, *http.Request)
+	ChangeOwnPassword(http.ResponseWriter, *http.Request)
 
 	GetGroups(http.ResponseWriter, *http.Request)
 	GetGroup(http.ResponseWriter, *http.Request)
@@ -55,46 +56,10 @@ func NewService(opts ...Option) Service {
 	m := chi.NewMux()
 	m.Use(options.Middleware...)
 
-	var backend identity.Backend
-	switch options.Config.Identity.Backend {
-	case "cs3":
-		backend = &identity.CS3{
-			Config: options.Config.Reva,
-			Logger: &options.Logger,
-		}
-	case "ldap":
-		var err error
-
-		var tlsConf *tls.Config
-		if options.Config.Identity.LDAP.Insecure {
-			tlsConf = &tls.Config{
-				//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
-				InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
-			}
-		}
-
-		conn := ldap.NewLDAPWithReconnect(&options.Logger,
-			ldap.Config{
-				URI:          options.Config.Identity.LDAP.URI,
-				BindDN:       options.Config.Identity.LDAP.BindDN,
-				BindPassword: options.Config.Identity.LDAP.BindPassword,
-				TLSConfig:    tlsConf,
-			},
-		)
-		if backend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil {
-			options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
-			return nil
-		}
-	default:
-		options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
-		return nil
-	}
-
 	svc := Graph{
 		config:               options.Config,
 		mux:                  m,
 		logger:               &options.Logger,
-		identityBackend:      backend,
 		spacePropertiesCache: ttlcache.NewCache(),
 		eventsPublisher:      options.EventsPublisher,
 	}
@@ -108,6 +73,44 @@ func NewService(opts ...Option) Service {
 	} else {
 		svc.gatewayClient = options.GatewayClient
 	}
+	if options.IdentityBackend == nil {
+		switch options.Config.Identity.Backend {
+		case "cs3":
+			svc.identityBackend = &identity.CS3{
+				Config: options.Config.Reva,
+				Logger: &options.Logger,
+			}
+		case "ldap":
+			var err error
+
+			var tlsConf *tls.Config
+			if options.Config.Identity.LDAP.Insecure {
+				tlsConf = &tls.Config{
+					//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
+					InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
+				}
+			}
+
+			conn := ldap.NewLDAPWithReconnect(&options.Logger,
+				ldap.Config{
+					URI:          options.Config.Identity.LDAP.URI,
+					BindDN:       options.Config.Identity.LDAP.BindDN,
+					BindPassword: options.Config.Identity.LDAP.BindPassword,
+					TLSConfig:    tlsConf,
+				},
+			)
+			if svc.identityBackend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil {
+				options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
+				return nil
+			}
+		default:
+			options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
+			return nil
+		}
+	} else {
+		svc.identityBackend = options.IdentityBackend
+	}
+
 	if options.HTTPClient == nil {
 		http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
 			InsecureSkipVerify: options.Config.Spaces.Insecure, //nolint:gosec
@@ -143,6 +146,7 @@ func NewService(opts ...Option) Service {
 				r.Get("/", svc.GetMe)
 				r.Get("/drives", svc.GetDrives)
 				r.Get("/drive/root/children", svc.GetRootDriveChildren)
+				r.Post("/changePassword", svc.ChangeOwnPassword)
 			})
 			r.Route("/users", func(r chi.Router) {
 				r.With(requireAdmin).Get("/", svc.GetUsers)
diff --git a/extensions/graph/pkg/service/v0/tracing.go b/extensions/graph/pkg/service/v0/tracing.go
index 0d2581ba98d..d3a0167b463 100644
--- a/extensions/graph/pkg/service/v0/tracing.go
+++ b/extensions/graph/pkg/service/v0/tracing.go
@@ -50,6 +50,11 @@ func (t tracing) PatchUser(w http.ResponseWriter, r *http.Request) {
 	t.next.PatchUser(w, r)
 }
 
+// ChangeOwnPassword implements the Service interface.
+func (t tracing) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	t.next.ChangeOwnPassword(w, r)
+}
+
 // GetGroups implements the Service interface.
 func (t tracing) GetGroups(w http.ResponseWriter, r *http.Request) {
 	t.next.GetGroups(w, r)
diff --git a/extensions/groups/pkg/config/config.go b/extensions/groups/pkg/config/config.go
index 6c58eb65955..b9e33192662 100644
--- a/extensions/groups/pkg/config/config.go
+++ b/extensions/groups/pkg/config/config.go
@@ -45,10 +45,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"GROUPS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"GROUPS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"GROUPS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"GROUPS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"GROUPS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"GROUPS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"GROUPS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"GROUPS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/idm/pkg/config/debug.go b/extensions/idm/pkg/config/debug.go
index 43ffdf37602..77c5588878a 100644
--- a/extensions/idm/pkg/config/debug.go
+++ b/extensions/idm/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"IDM_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"IDM_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"IDM_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"IDM_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"IDM_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"IDM_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"IDM_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"IDM_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/idp/pkg/config/debug.go b/extensions/idp/pkg/config/debug.go
index f2a30076f2c..5f7f3d3d50b 100644
--- a/extensions/idp/pkg/config/debug.go
+++ b/extensions/idp/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"IDP_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"IDP_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"IDP_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"IDP_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"IDP_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"IDP_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"IDP_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"IDP_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/nats/pkg/config/debug.go b/extensions/nats/pkg/config/debug.go
index 8c316683a44..efda49fc466 100644
--- a/extensions/nats/pkg/config/debug.go
+++ b/extensions/nats/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"NATS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"NATS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"NATS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"NATS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"NATS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"NATS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"NATS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"NATS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/notifications/pkg/config/debug.go b/extensions/notifications/pkg/config/debug.go
index df19aca9685..55c6b005bf4 100644
--- a/extensions/notifications/pkg/config/debug.go
+++ b/extensions/notifications/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"NOTIFICATIONS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"NOTIFICATIONS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"NOTIFICATIONS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"NOTIFICATIONS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"NOTIFICATIONS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"NOTIFICATIONS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"NOTIFICATIONS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"NOTIFICATIONS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/ocdav/pkg/config/config.go b/extensions/ocdav/pkg/config/config.go
index 72d60a06d3e..ca7108de95e 100644
--- a/extensions/ocdav/pkg/config/config.go
+++ b/extensions/ocdav/pkg/config/config.go
@@ -54,10 +54,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"OCDAV_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"OCDAV_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"OCDAV_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"OCDAV_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"OCDAV_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"OCDAV_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"OCDAV_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"OCDAV_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type HTTPConfig struct {
diff --git a/extensions/ocs/pkg/config/debug.go b/extensions/ocs/pkg/config/debug.go
index 9ed071c3330..a57d443f91d 100644
--- a/extensions/ocs/pkg/config/debug.go
+++ b/extensions/ocs/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"OCS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"OCS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"OCS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"OCS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"OCS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"OCS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"OCS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"OCS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/proxy/pkg/config/debug.go b/extensions/proxy/pkg/config/debug.go
index 1b9287b3de8..806a6fa95d5 100644
--- a/extensions/proxy/pkg/config/debug.go
+++ b/extensions/proxy/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"PROXY_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"PROXY_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"PROXY_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"PROXY_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"PROXY_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"PROXY_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"PROXY_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"PROXY_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/search/pkg/config/debug.go b/extensions/search/pkg/config/debug.go
index a6ab80cbdaa..72016b65424 100644
--- a/extensions/search/pkg/config/debug.go
+++ b/extensions/search/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `ocisConfig:"addr" env:"SEARCH_DEBUG_ADDR"`
-	Token  string `ocisConfig:"token" env:"SEARCH_DEBUG_TOKEN"`
-	Pprof  bool   `ocisConfig:"pprof" env:"SEARCH_DEBUG_PPROF"`
-	Zpages bool   `ocisConfig:"zpages" env:"SEARCH_DEBUG_ZPAGES"`
+	Addr   string `ocisConfig:"addr" env:"SEARCH_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `ocisConfig:"token" env:"SEARCH_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `ocisConfig:"pprof" env:"SEARCH_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `ocisConfig:"zpages" env:"SEARCH_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/settings/pkg/config/debug.go b/extensions/settings/pkg/config/debug.go
index e86d9056a8d..14f6f41a79f 100644
--- a/extensions/settings/pkg/config/debug.go
+++ b/extensions/settings/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"SETTINGS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"SETTINGS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"SETTINGS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"SETTINGS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"SETTINGS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"SETTINGS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"SETTINGS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"SETTINGS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/sharing/pkg/config/config.go b/extensions/sharing/pkg/config/config.go
index 5ede701c6a1..d1c6555ec2e 100644
--- a/extensions/sharing/pkg/config/config.go
+++ b/extensions/sharing/pkg/config/config.go
@@ -48,10 +48,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"SHARING_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"SHARING_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"SHARING_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"SHARING_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"SHARING_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"SHARING_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"SHARING_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"SHARING_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/storage-publiclink/pkg/config/config.go b/extensions/storage-publiclink/pkg/config/config.go
index f359a4969ca..848d45f7ca7 100644
--- a/extensions/storage-publiclink/pkg/config/config.go
+++ b/extensions/storage-publiclink/pkg/config/config.go
@@ -44,10 +44,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"STORAGE_PUBLICLINK_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"STORAGE_PUBLICLINK_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"STORAGE_PUBLICLINK_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"STORAGE_PUBLICLINK_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"STORAGE_PUBLICLINK_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"STORAGE_PUBLICLINK_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"STORAGE_PUBLICLINK_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"STORAGE_PUBLICLINK_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/storage-shares/pkg/config/config.go b/extensions/storage-shares/pkg/config/config.go
index af0a4e50b3f..eaee90d4565 100644
--- a/extensions/storage-shares/pkg/config/config.go
+++ b/extensions/storage-shares/pkg/config/config.go
@@ -46,10 +46,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"STORAGE_SHARES_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"STORAGE_SHARES_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"STORAGE_SHARES_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"STORAGE_SHARES_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"STORAGE_SHARES_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"STORAGE_SHARES_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"STORAGE_SHARES_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"STORAGE_SHARES_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/storage-system/pkg/config/config.go b/extensions/storage-system/pkg/config/config.go
index ab6caefb09f..d5e9ed246cc 100644
--- a/extensions/storage-system/pkg/config/config.go
+++ b/extensions/storage-system/pkg/config/config.go
@@ -51,10 +51,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"STORAGE_SYSTEM_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"STORAGE_SYSTEM_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"STORAGE_SYSTEM_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"STORAGE_SYSTEM_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"STORAGE_SYSTEM_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"STORAGE_SYSTEM_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"STORAGE_SYSTEM_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"STORAGE_SYSTEM_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/storage-users/pkg/config/config.go b/extensions/storage-users/pkg/config/config.go
index 54f9723cfda..f64405dcf69 100644
--- a/extensions/storage-users/pkg/config/config.go
+++ b/extensions/storage-users/pkg/config/config.go
@@ -53,10 +53,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"STORAGE_USERS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"STORAGE_USERS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"STORAGE_USERS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"STORAGE_USERS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"STORAGE_USERS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"STORAGE_USERS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"STORAGE_USERS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"STORAGE_USERS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/store/pkg/config/debug.go b/extensions/store/pkg/config/debug.go
index 4aa297a177f..8d4bcba3cee 100644
--- a/extensions/store/pkg/config/debug.go
+++ b/extensions/store/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"STORE_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"STORE_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"STORE_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"STORE_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"STORE_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"STORE_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"STORE_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"STORE_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/thumbnails/pkg/config/debug.go b/extensions/thumbnails/pkg/config/debug.go
index ee9d8f48333..755cf9cf775 100644
--- a/extensions/thumbnails/pkg/config/debug.go
+++ b/extensions/thumbnails/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"THUMBNAILS_DEBUG_ADDR" desc:"The debug address"`
-	Token  string `yaml:"token" env:"THUMBNAILS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"THUMBNAILS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"THUMBNAILS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"THUMBNAILS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"THUMBNAILS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"THUMBNAILS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"THUMBNAILS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/users/pkg/config/config.go b/extensions/users/pkg/config/config.go
index 006fb47d42c..92d4f74453a 100644
--- a/extensions/users/pkg/config/config.go
+++ b/extensions/users/pkg/config/config.go
@@ -45,10 +45,10 @@ type Service struct {
 }
 
 type Debug struct {
-	Addr   string `yaml:"addr" env:"USERS_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"USERS_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"USERS_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"USERS_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"USERS_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"USERS_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"USERS_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"USERS_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
 
 type GRPCConfig struct {
diff --git a/extensions/web/pkg/config/debug.go b/extensions/web/pkg/config/debug.go
index e3473636a85..a604b622a76 100644
--- a/extensions/web/pkg/config/debug.go
+++ b/extensions/web/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"WEB_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"WEB_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"WEB_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"WEB_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"WEB_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"WEB_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"WEB_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"WEB_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/extensions/webdav/pkg/config/debug.go b/extensions/webdav/pkg/config/debug.go
index 8ce9689f7f4..6f2306cb3ee 100644
--- a/extensions/webdav/pkg/config/debug.go
+++ b/extensions/webdav/pkg/config/debug.go
@@ -2,8 +2,8 @@ package config
 
 // Debug defines the available debug configuration.
 type Debug struct {
-	Addr   string `yaml:"addr" env:"WEBDAV_DEBUG_ADDR"`
-	Token  string `yaml:"token" env:"WEBDAV_DEBUG_TOKEN"`
-	Pprof  bool   `yaml:"pprof" env:"WEBDAV_DEBUG_PPROF"`
-	Zpages bool   `yaml:"zpages" env:"WEBDAV_DEBUG_ZPAGES"`
+	Addr   string `yaml:"addr" env:"WEBDAV_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."`
+	Token  string `yaml:"token" env:"WEBDAV_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint"`
+	Pprof  bool   `yaml:"pprof" env:"WEBDAV_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling"`
+	Zpages bool   `yaml:"zpages" env:"WEBDAV_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-me"`
 }
diff --git a/go.mod b/go.mod
index 52f6bed48a6..cece48d345d 100644
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,7 @@ require (
 	github.com/onsi/ginkgo v1.16.5
 	github.com/onsi/ginkgo/v2 v2.1.4
 	github.com/onsi/gomega v1.19.0
-	github.com/owncloud/libre-graph-api-go v0.13.3
+	github.com/owncloud/libre-graph-api-go v0.14.2
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.12.1
 	github.com/rs/zerolog v1.26.1
@@ -67,8 +67,8 @@ require (
 	go.opentelemetry.io/otel/trace v1.7.0
 	golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
 	golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
-	golang.org/x/net v0.0.0-20220225172249-27dd8689420f
-	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
+	golang.org/x/net v0.0.0-20220516155154-20f960328961
+	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
 	google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb
 	google.golang.org/grpc v1.46.0
 	google.golang.org/protobuf v1.28.0
diff --git a/go.sum b/go.sum
index 577328e1e96..05d0bf74a8f 100644
--- a/go.sum
+++ b/go.sum
@@ -1018,8 +1018,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 v0.13.3 h1:jNtQ8QcT7AZTfhdVHDaqAOs2xaJTfqfkucM9GARBIrQ=
-github.com/owncloud/libre-graph-api-go v0.13.3/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
+github.com/owncloud/libre-graph-api-go v0.14.2 h1:JiI32eDp7JZmiVv4aUpC4yaPpv6gK4xxM9MOe/0cXpE=
+github.com/owncloud/libre-graph-api-go v0.14.2/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
 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=
@@ -1485,8 +1485,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220516155154-20f960328961 h1:+W/iTMPG0EL7aW+/atntZwZrvSRIj3m3yX414dSULUU=
+golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1504,8 +1505,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
 golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=


Test patch:
diff --git a/docs/ocis/development/beta-testplan.md b/docs/ocis/development/beta-testplan.md
index 1e3a8b4d113..5788b1b7245 100644
--- a/docs/ocis/development/beta-testplan.md
+++ b/docs/ocis/development/beta-testplan.md
@@ -104,10 +104,10 @@ SHARE_API_PATH=ocs/v2.php/apps/files_sharing/api/v1/shares
 USER=admin
 PASSWORD=admin
 ```
-- create a new user `curl -k -u $USER:$PASSWORD $SERVER_URI/ocs/v2.php/cloud/users -d'userid=<username>&password=<password>&displayname=<displayName>&username=<username>&email=<email>'`
+- create a new user `curl -k -u $USER:$PASSWORD $SERVER_URI/$GRAPH_API_PATH/users -d '{"displayName": "<display name>", "mail": "<mail-address>", "onPremisesSamAccountName": "<username>", "passwordProfile":{ "password": "<password>" }}'`
 - give the user the "Admin" role
   1. get the id of the admin role `curl -k -u $USER:$PASSWORD  $SERVER_URI/api/v0/settings/roles-list -d"{}" | jq '.bundles[] | select (.name | test("admin")) | .id'`
-  2. get the id of the user: `curl -k -u $USER:$PASSWORD  $SERVER_URI/api/v0/accounts/accounts-list -d"{}" | jq '.accounts[] | select (.preferredName | test("<user-name>")) | .id'`
+  2. get the id of the user: `curl -k -u $USER:$PASSWORD  $SERVER_URI/$GRAPH_API_PATH/users/<username>" | jq '.id'`
   3. assign role user to role: `curl -k -u $USER:$PASSWORD  $SERVER_URI/api/v0/settings/assignments-add -d'{"account_uuid":"<user-id>","role_id":"<admin-role-id>"}`
 - create a space: `curl -k -u $USER:$PASSWORD  "$SERVER_URI/$GRAPH_API_PATH/drives" -X POST -d'{"Name":"Space for finance","driveType":"project", "description":"we need some space"}'`
 - list spaces: `curl -k -u $USER:$PASSWORD  $SERVER_URI/$GRAPH_API_PATH/me/drives`
diff --git a/extensions/graph/pkg/service/v0/password_test.go b/extensions/graph/pkg/service/v0/password_test.go
new file mode 100644
index 00000000000..65a0cb2edf2
--- /dev/null
+++ b/extensions/graph/pkg/service/v0/password_test.go
@@ -0,0 +1,160 @@
+package svc_test
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"net/http"
+	"net/http/httptest"
+
+	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
+	"github.com/go-ldap/ldap/v3"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	libregraph "github.com/owncloud/libre-graph-api-go"
+	"github.com/owncloud/ocis/v2/extensions/graph/mocks"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/config"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/identity"
+	service "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0"
+	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+	"github.com/stretchr/testify/mock"
+)
+
+type changePwTest struct {
+	desc      string
+	currentpw string
+	newpw     string
+	expected  int
+}
+
+var _ = Describe("Users changing their own password", func() {
+	var (
+		svc             service.Service
+		gatewayClient   *mocks.GatewayClient
+		httpClient      *mocks.HTTPClient
+		ldapClient      *mocks.Client
+		ldapConfig      config.LDAP
+		identityBackend identity.Backend
+		eventsPublisher mocks.Publisher
+		ctx             context.Context
+		cfg             *config.Config
+		user            *userv1beta1.User
+		err             error
+	)
+
+	JustBeforeEach(func() {
+		ctx = context.Background()
+		cfg = defaults.FullDefaultConfig()
+		cfg.TokenManager.JWTSecret = "loremipsum"
+
+		gatewayClient = &mocks.GatewayClient{}
+		ldapClient = mockedLDAPClient()
+
+		ldapConfig = config.LDAP{
+			WriteEnabled:             true,
+			UserDisplayNameAttribute: "displayName",
+			UserNameAttribute:        "uid",
+			UserEmailAttribute:       "mail",
+			UserIDAttribute:          "ownclouduuid",
+			UserSearchScope:          "sub",
+			GroupNameAttribute:       "cn",
+			GroupIDAttribute:         "ownclouduui",
+			GroupSearchScope:         "sub",
+		}
+		loggger := log.NewLogger()
+		identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger)
+		Expect(err).To(BeNil())
+
+		httpClient = &mocks.HTTPClient{}
+		eventsPublisher = mocks.Publisher{}
+		svc = service.NewService(
+			service.Config(cfg),
+			service.WithGatewayClient(gatewayClient),
+			service.WithIdentityBackend(identityBackend),
+			service.WithHTTPClient(httpClient),
+			service.EventsPublisher(&eventsPublisher),
+		)
+		user = &userv1beta1.User{
+			Id: &userv1beta1.UserId{
+				OpaqueId: "user",
+			},
+		}
+		ctx = revactx.ContextSetUser(ctx, user)
+		eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil)
+	})
+
+	It("fails if no user in context", func() {
+		r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/changePassword", nil)
+		rr := httptest.NewRecorder()
+		svc.ChangeOwnPassword(rr, r)
+		Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+	})
+
+	DescribeTable("changing the password",
+		func(current string, newpw string, authresult string, expected int) {
+			switch authresult {
+			case "error":
+				gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(nil, errors.New("fail"))
+			case "deny":
+				gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
+					Status: status.NewPermissionDenied(ctx, errors.New("wrong password"), "wrong password"),
+					Token:  "authtoken",
+				}, nil)
+			default:
+				gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
+					Status: status.NewOK(ctx),
+					Token:  "authtoken",
+				}, nil)
+			}
+			cpw := libregraph.NewPasswordChange()
+			cpw.SetCurrentPassword(current)
+			cpw.SetNewPassword(newpw)
+			body, _ := json.Marshal(cpw)
+			b := bytes.NewBuffer(body)
+			r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/me/changePassword", b).WithContext(ctx)
+			rr := httptest.NewRecorder()
+			svc.ChangeOwnPassword(rr, r)
+			Expect(rr.Code).To(Equal(expected))
+		},
+		Entry("fails when current password is empty", "", "newpassword", "", http.StatusBadRequest),
+		Entry("fails when new password is empty", "currentpassword", "", "", http.StatusBadRequest),
+		Entry("fails when current and new password are equal", "password", "password", "", http.StatusBadRequest),
+		Entry("fails authentication with current password errors", "currentpassword", "newpassword", "error", http.StatusInternalServerError),
+		Entry("fails when current password is wrong", "currentpassword", "newpassword", "deny", http.StatusInternalServerError),
+		Entry("succeeds when current password is correct", "currentpassword", "newpassword", "", http.StatusNoContent),
+	)
+})
+
+func mockedLDAPClient() *mocks.Client {
+	lm := &mocks.Client{}
+
+	userEntry := ldap.NewEntry("uid=test", map[string][]string{
+		"uid":         {"test"},
+		"displayName": {"test"},
+		"mail":        {"test@example.org"},
+	})
+
+	lm.On("Search", mock.Anything, mock.Anything, mock.Anything, mock.Anything,
+		mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
+		Return(
+			&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}},
+			nil)
+
+	mr := ldap.NewModifyRequest("uid=test", nil)
+	mr.Changes = []ldap.Change{
+		{
+			Operation: ldap.ReplaceAttribute,
+			Modification: ldap.PartialAttribute{
+				Type: "userPassword",
+				Vals: []string{"newpassword"},
+			},
+		},
+	}
+	lm.On("Modify", mr).Return(nil)
+	return lm
+}
diff --git a/tests/acceptance/features/apiSpaces/download.feature b/tests/acceptance/features/apiSpaces/download.feature
new file mode 100644
index 00000000000..5dd610344d2
--- /dev/null
+++ b/tests/acceptance/features/apiSpaces/download.feature
@@ -0,0 +1,38 @@
+@api @skipOnOcV10
+Feature: Download file in project space
+    As a user, I want to be able to download files
+
+    Note - this feature is run in CI with ACCOUNTS_HASH_DIFFICULTY set to the default for production
+    See https://github.com/owncloud/ocis/issues/1542 and https://github.com/owncloud/ocis/pull/839
+
+    Background:
+        Given these users have been created with default attributes and without skeleton files:
+            | username |
+            | Alice    |
+            | Brian    |
+        And the administrator has given "Alice" the role "Admin" using the settings api
+        And user "Alice" has created a space "download file" with the default quota using the GraphApi
+        And user "Alice" has uploaded a file inside space "download file" with content "some content" to "file.txt"
+
+
+    Scenario: An user downloads a file in the project space
+        When user "Alice" downloads the file "file.txt" of the space "download file" using the WebDAV API
+        Then the HTTP status code should be "200"
+        And the following headers should be set
+            | header         | value |
+            | Content-Length | 12    |
+
+
+    Scenario: An user downloads an old version of the file in the project space
+        Given user "Alice" has uploaded a file inside space "download file" with content "new content" to "file.txt"
+        And user "Alice" has uploaded a file inside space "download file" with content "newest content" to "file.txt"
+        When user "Alice" downloads version of the file "file.txt" with the index "1" of the space "download file" using the WebDAV API
+        Then the HTTP status code should be "200"
+        And the following headers should be set
+            | header         | value |
+            | Content-Length | 11    |
+        When user "Alice" downloads version of the file "file.txt" with the index "2" of the space "download file" using the WebDAV API
+        Then the HTTP status code should be "200"
+        And the following headers should be set
+            | header         | value |
+            | Content-Length | 12    |
diff --git a/tests/acceptance/features/apiSpaces/listSpaces.feature b/tests/acceptance/features/apiSpaces/listSpaces.feature
index 392900a7ac8..1c6d7623f99 100644
--- a/tests/acceptance/features/apiSpaces/listSpaces.feature
+++ b/tests/acceptance/features/apiSpaces/listSpaces.feature
@@ -54,11 +54,11 @@ Feature: List and create spaces
     When user "Alice" creates a space "Project Mars" of type "project" with the default quota using the GraphApi
     Then the HTTP status code should be "201"
     And the json responded should contain a space "Project Mars" with these key and value pairs:
-      | key              | value        |
-      | driveType        | project      |
-      | driveAlias       | project/project-mars |
-      | name             | Project Mars |
-      | quota@@@total    | 1000000000   |
+      | key              | value                            |
+      | driveType        | project                          |
+      | driveAlias       | project/project-mars             |
+      | name             | Project Mars                     |
+      | quota@@@total    | 1000000000                       |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
 
   Scenario: An admin user can create a Space via the Graph API with certain quota
@@ -66,25 +66,25 @@ Feature: List and create spaces
     When user "Alice" creates a space "Project Venus" of type "project" with quota "2000" using the GraphApi
     Then the HTTP status code should be "201"
     And the json responded should contain a space "Project Venus" with these key and value pairs:
-      | key              | value         |
-      | driveType        | project       |
-      | name             | Project Venus |
-      | quota@@@total    | 2000          |
+      | key              | value                            |
+      | driveType        | project                          |
+      | name             | Project Venus                    |
+      | quota@@@total    | 2000                             |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
 
   Scenario: A user can list his personal space via multiple endpoints
     When user "Alice" lists all available spaces via the GraphApi with query "$filter=driveType eq 'personal'"
     Then the json responded should contain a space "Alice Hansen" owned by "Alice" with these key and value pairs:
-      | key              | value         |
-      | driveType        | personal      |
-      | name             | Alice Hansen  |
-      | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
-      | owner@@@user@@@id | %user_id%    |
+      | key               | value                            |
+      | driveType         | personal                         |
+      | name              | Alice Hansen                     |
+      | root@@@webDavUrl  | %base_url%/dav/spaces/%space_id% |
+      | owner@@@user@@@id | %user_id%                        |
     When user "Alice" looks up the single space "Alice Hansen" via the GraphApi by using its id
     Then the json responded should contain a space "Alice Hansen" with these key and value pairs:
-      | key              | value         |
-      | driveType        | personal      |
-      | name             | Alice Hansen  |
+      | key              | value                            |
+      | driveType        | personal                         |
+      | name             | Alice Hansen                     |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
 
   Scenario: A user can list his created spaces via multiple endpoints
@@ -92,17 +92,17 @@ Feature: List and create spaces
     When user "Alice" creates a space "Project Venus" of type "project" with quota "2000" using the GraphApi
     Then the HTTP status code should be "201"
     And the json responded should contain a space "Project Venus" with these key and value pairs:
-      | key              | value         |
-      | driveType        | project       |
-      | driveAlias       | project/project-venus |
-      | name             | Project Venus |
-      | quota@@@total    | 2000          |
+      | key              | value                            |
+      | driveType        | project                          |
+      | driveAlias       | project/project-venus            |
+      | name             | Project Venus                    |
+      | quota@@@total    | 2000                             |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
     When user "Alice" looks up the single space "Project Venus" via the GraphApi by using its id
     Then the json responded should contain a space "Project Venus" with these key and value pairs:
-      | key              | value         |
-      | driveType        | project       |
-      | driveAlias       | project/project-venus |
-      | name             | Project Venus |
-      | quota@@@total    | 2000          |
+      | key              | value                            |
+      | driveType        | project                          |
+      | driveAlias       | project/project-venus            |
+      | name             | Project Venus                    |
+      | quota@@@total    | 2000                             |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php
index 561247c4178..441050c7645 100644
--- a/tests/acceptance/features/bootstrap/SpacesContext.php
+++ b/tests/acceptance/features/bootstrap/SpacesContext.php
@@ -2268,4 +2268,108 @@ class SpacesContext implements Context {
 			)
 		);
 	}
+
+	/**
+	 * @When /^user "([^"]*)" downloads the file "([^"]*)" of the space "([^"]*)" using the WebDAV API$/
+	 *
+	 * @param  string $user
+	 * @param  string $fileName 
+	 * @param  string $spaceName
+	 *
+	 * @throws GuzzleException
+	 */
+	public function downloadFile(
+		string $user,
+		string $fileName,
+		string $spaceName
+	): void {		
+		$space = $this->getSpaceByName($user, $spaceName);
+		$fullUrl = $this->baseUrl . $this->davSpacesUrl . $space['id'] . '/' . $fileName;
+
+		$this->featureContext->setResponse(
+			HttpRequestHelper::sendRequest(
+				$fullUrl,
+				"",
+				'HEAD',
+				$user,
+				$this->featureContext->getPasswordForUser($user),
+				[],
+				""
+			)
+		);
+	}
+
+	/**
+	 * @When /^user "([^"]*)" downloads version of the file "([^"]*)" with the index "([^"]*)" of the space "([^"]*)" using the WebDAV API$/
+	 *
+	 * @param  string $user
+	 * @param  string $fileName 
+	 * @param  string $index
+	 * @param  string $spaceName
+	 *
+	 * @throws GuzzleException
+	 */
+	public function downloadVersionOfTheFile(
+		string $user,
+		string $fileName,
+		string $index,
+		string $spaceName
+	): void {		
+		$fileVersion = $this->listFileVersion($user, $fileName, $spaceName);
+		if (!isset($fileVersion[$index])) {
+			Assert::fail(
+				'could not find version of file "' . $fileName . '" with index "' . $index . '"'
+			);
+		}
+		$url = $this->baseUrl . $fileVersion[$index][0];		
+		
+		$this->featureContext->setResponse(
+			HttpRequestHelper::sendRequest(
+				$url,
+				"",
+				'HEAD',
+				$user,
+				$this->featureContext->getPasswordForUser($user),
+				[],
+				""
+			)
+		);
+	}
+
+	/**
+	 * Method returns an array with url values from the propfind request 
+	 * like: /remote.php/dav/meta/spaceUuid%fileUuid/v/fileUuid.REV.2022-05-17T10:39:49.672285951Z
+
+	 * 
+	 * @param  string $user
+	 * @param  string $fileName 
+	 * @param  string $spaceName
+	 *
+	 * @return array
+	 * @throws GuzzleException
+	 */
+	public function listFileVersion(
+		string $user,
+		string $fileName,
+		string $spaceName
+	): array {		
+
+		$fileId = $this->getFileId($user, $spaceName, $fileName);
+		$fullUrl = $this->baseUrl . '/remote.php/dav/meta/' . $fileId . '/v';
+
+		$this->featureContext->setResponse(
+			HttpRequestHelper::sendRequest(
+				$fullUrl,
+				"",
+				'PROPFIND',
+				$user,
+				$this->featureContext->getPasswordForUser($user),
+				[],
+				""
+			)
+		);
+
+		$responseXml = HttpRequestHelper::getResponseXml($this->featureContext->getResponse());
+		return $responseXml->xpath("//d:response/d:href");
+	}
 }