Repository: owncloud/ocis
Pull request #3290 (2022-03-09 15:42:15)

Add support for the cs3 metadata storage share manager
## Description
This PR adds support for the cs3 metadata storage share manager. 

## How Has This Been Tested?
- Manual testing
- Unit tests

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [X] Code changes
- [X] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


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

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3290&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=3290&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3290&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=3290&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=3290&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3290&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=3290&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=3290&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=3290&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=3290&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=3290&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3290&resolved=false&types=CODE_SMELL)

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



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eddb22771fc..2f505866545 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,9 +9,13 @@ The following sections list the changes for unreleased.
 * Bugfix - Network configuration in individiual_services example: [#3238](https://github.com/owncloud/ocis/pull/3238)
 * Bugfix - Improve gif thumbnails: [#3305](https://github.com/owncloud/ocis/pull/3305)
 * Change - Settings service now stores its data via metadata service: [#3232](https://github.com/owncloud/ocis/pull/3232)
+* Enhancement - Audit logger will now log file events: [#3332](https://github.com/owncloud/ocis/pull/3332)
+* Enhancement - Add password reset link to login page: [#3329](https://github.com/owncloud/ocis/pull/3329)
 * Enhancement - Log sharing events in audit service: [#3301](https://github.com/owncloud/ocis/pull/3301)
+* Enhancement - Add space aliases: [#3283](https://github.com/owncloud/ocis/pull/3283)
 * Enhancement - Include etags in drives listing: [#3267](https://github.com/owncloud/ocis/pull/3267)
 * Enhancement - Improve thumbnails API: [#3272](https://github.com/owncloud/ocis/pull/3272)
+* Enhancement - Update reva to vXXXX: [#3330](https://github.com/owncloud/ocis/pull/3330)
 * Enhancement - Update ownCloud Web to v5.3.0-rc.1: [#6561](https://github.com/owncloud/web/pull/6561)
 
 ## Details
@@ -35,12 +39,31 @@ The following sections list the changes for unreleased.
 
    https://github.com/owncloud/ocis/pull/3232
 
+* Enhancement - Audit logger will now log file events: [#3332](https://github.com/owncloud/ocis/pull/3332)
+
+   See full list of supported events in `audit/pkg/types/types.go`
+
+   https://github.com/owncloud/ocis/pull/3332
+
+* Enhancement - Add password reset link to login page: [#3329](https://github.com/owncloud/ocis/pull/3329)
+
+   Added a configurable passwort reset link to the login page. It can be set via
+   `IDP_PASSWORD_RESET_URI`. If the option is not set the link will not be shown.
+
+   https://github.com/owncloud/ocis/pull/3329
+
 * Enhancement - Log sharing events in audit service: [#3301](https://github.com/owncloud/ocis/pull/3301)
 
    Contains sharing related events. See full list in audit/pkg/types/events.go
 
    https://github.com/owncloud/ocis/pull/3301
 
+* Enhancement - Add space aliases: [#3283](https://github.com/owncloud/ocis/pull/3283)
+
+   Space aliases can be used to resolve spaceIDs in a client.
+
+   https://github.com/owncloud/ocis/pull/3283
+
 * Enhancement - Include etags in drives listing: [#3267](https://github.com/owncloud/ocis/pull/3267)
 
    Added etags in the response of list drives.
@@ -55,6 +78,16 @@ The following sections list the changes for unreleased.
 
    https://github.com/owncloud/ocis/pull/3272
 
+* Enhancement - Update reva to vXXXX: [#3330](https://github.com/owncloud/ocis/pull/3330)
+
+   Updated reva to version XXXX. This update includes:
+
+  * Enh [cs3org/reva#2622](https://github.com/cs3org/reva/pull/2622): Implement listing shares in spaces
+  * Enh [cs3org/reva#2624](https://github.com/cs3org/reva/pull/2624): Include spaces etags in virtual spaces
+  * Enh [cs3org/reva#2628](https://github.com/cs3org/reva/pull/2628): Webdav trash-bin API for spaces
+
+   https://github.com/owncloud/ocis/pull/3330
+
 * Enhancement - Update ownCloud Web to v5.3.0-rc.1: [#6561](https://github.com/owncloud/web/pull/6561)
 
    Tags: web
diff --git a/audit/pkg/service/service.go b/audit/pkg/service/service.go
index 905e251ee51..5edc11122d8 100644
--- a/audit/pkg/service/service.go
+++ b/audit/pkg/service/service.go
@@ -61,6 +61,20 @@ func StartAuditLogger(ctx context.Context, ch <-chan interface{}, log log.Logger
 				auditEvent = types.LinkAccessed(ev)
 			case events.LinkAccessFailed:
 				auditEvent = types.LinkAccessFailed(ev)
+			case events.FileUploaded:
+				auditEvent = types.FileUploaded(ev)
+			case events.FileDownloaded:
+				auditEvent = types.FileDownloaded(ev)
+			case events.ItemMoved:
+				auditEvent = types.ItemMoved(ev)
+			case events.ItemTrashed:
+				auditEvent = types.ItemTrashed(ev)
+			case events.ItemPurged:
+				auditEvent = types.ItemPurged(ev)
+			case events.ItemRestored:
+				auditEvent = types.ItemRestored(ev)
+			case events.FileVersionRestored:
+				auditEvent = types.FileVersionRestored(ev)
 			default:
 				log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev))
 				continue
diff --git a/audit/pkg/types/constants.go b/audit/pkg/types/constants.go
index 663996241dd..10b51a35c38 100644
--- a/audit/pkg/types/constants.go
+++ b/audit/pkg/types/constants.go
@@ -4,6 +4,7 @@ import "fmt"
 
 // short identifiers for audit actions
 const (
+	// Sharing
 	ActionShareCreated            = "file_shared"
 	ActionSharePermissionUpdated  = "share_permission_updated"
 	ActionShareDisplayNameUpdated = "share_name_updated"
@@ -13,6 +14,15 @@ const (
 	ActionShareAccepted           = "share_accepted"
 	ActionShareDeclined           = "share_declined"
 	ActionLinkAccessed            = "public_link_accessed"
+
+	// Files
+	ActionFileCreated         = "file_create"
+	ActionFileRead            = "file_read"
+	ActionFileTrashed         = "file_delete"
+	ActionFileRenamed         = "file_rename"
+	ActionFilePurged          = "file_trash_delete"
+	ActionFileRestored        = "file_trash_restore"
+	ActionFileVersionRestored = "file_version_restore"
 )
 
 // MessageShareCreated returns the human readable string that describes the action
@@ -59,3 +69,38 @@ func MessageShareDeclined(userid, shareid, sharerid string) string {
 func MessageLinkAccessed(linkid string, success bool) string {
 	return fmt.Sprintf("link '%s' was accessed. Success: %v", linkid, success)
 }
+
+// MessageFileCreated returns the human readable string that describes the action
+func MessageFileCreated(item string) string {
+	return fmt.Sprintf("File '%s' was created", item)
+}
+
+// MessageFileRead returns the human readable string that describes the action
+func MessageFileRead(item string) string {
+	return fmt.Sprintf("File '%s' was read", item)
+}
+
+// MessageFileTrashed returns the human readable string that describes the action
+func MessageFileTrashed(item string) string {
+	return fmt.Sprintf("File '%s' was trashed", item)
+}
+
+// MessageFileRenamed returns the human readable string that describes the action
+func MessageFileRenamed(item, oldpath, newpath string) string {
+	return fmt.Sprintf("File '%s' was moved from '%s' to '%s'", item, oldpath, newpath)
+}
+
+// MessageFilePurged returns the human readable string that describes the action
+func MessageFilePurged(item string) string {
+	return fmt.Sprintf("File '%s' was removed from trashbin", item)
+}
+
+// MessageFileRestored returns the human readable string that describes the action
+func MessageFileRestored(item, path string) string {
+	return fmt.Sprintf("File '%s' was restored from trashbin to '%s'", item, path)
+}
+
+// MessageFileVersionRestored returns the human readable string that describes the action
+func MessageFileVersionRestored(item string, version string) string {
+	return fmt.Sprintf("File '%s' was restored in version '%s'", item, version)
+}
diff --git a/audit/pkg/types/conversion.go b/audit/pkg/types/conversion.go
index c90ab0eec43..29dde252511 100644
--- a/audit/pkg/types/conversion.go
+++ b/audit/pkg/types/conversion.go
@@ -5,9 +5,11 @@ import (
 	"time"
 
 	"github.com/cs3org/reva/v2/pkg/events"
+	"github.com/cs3org/reva/v2/pkg/utils"
 
 	group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
 	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
 	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
 )
 
@@ -220,6 +222,94 @@ func LinkAccessFailed(ev events.LinkAccessFailed) AuditEventLinkAccessed {
 	}
 }
 
+// FilesAuditEvent creates an AuditEventFiles from the given values
+func FilesAuditEvent(base AuditEvent, itemid, owner, path string) AuditEventFiles {
+	return AuditEventFiles{
+		AuditEvent: base,
+		FileID:     itemid,
+		Owner:      owner,
+		Path:       path,
+	}
+}
+
+// FileUploaded converts a FileUploaded event to an AuditEventFileCreated
+func FileUploaded(ev events.FileUploaded) AuditEventFileCreated {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+	base := BasicAuditEvent(uid, "", MessageFileCreated(iid), ActionFileCreated)
+	return AuditEventFileCreated{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+	}
+}
+
+// FileDownloaded converts a FileDownloaded event to an AuditEventFileRead
+func FileDownloaded(ev events.FileDownloaded) AuditEventFileRead {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+	base := BasicAuditEvent(uid, "", MessageFileRead(iid), ActionFileRead)
+	return AuditEventFileRead{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+	}
+}
+
+// ItemMoved converts a ItemMoved event to an AuditEventFileRenamed
+func ItemMoved(ev events.ItemMoved) AuditEventFileRenamed {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+
+	oldpath := ""
+	if ev.OldReference != nil {
+		oldpath = ev.OldReference.GetPath()
+	}
+
+	base := BasicAuditEvent(uid, "", MessageFileRenamed(iid, oldpath, path), ActionFileRenamed)
+	return AuditEventFileRenamed{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+		OldPath:         oldpath,
+	}
+}
+
+// ItemTrashed converts a ItemTrashed event to an AuditEventFileDeleted
+func ItemTrashed(ev events.ItemTrashed) AuditEventFileDeleted {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+	base := BasicAuditEvent(uid, "", MessageFileTrashed(iid), ActionFileTrashed)
+	return AuditEventFileDeleted{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+	}
+}
+
+// ItemPurged converts a ItemPurged event to an AuditEventFilePurged
+func ItemPurged(ev events.ItemPurged) AuditEventFilePurged {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+	base := BasicAuditEvent(uid, "", MessageFilePurged(iid), ActionFilePurged)
+	return AuditEventFilePurged{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+	}
+}
+
+// ItemRestored converts a ItemRestored event to an AuditEventFileRestored
+func ItemRestored(ev events.ItemRestored) AuditEventFileRestored {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+
+	oldpath := ""
+	if ev.OldReference != nil {
+		oldpath = ev.OldReference.GetPath()
+	}
+
+	base := BasicAuditEvent(uid, "", MessageFileRestored(iid, path), ActionFileRestored)
+	return AuditEventFileRestored{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+		OldPath:         oldpath,
+	}
+}
+
+// FileVersionRestored converts a FileVersionRestored event to an AuditEventFileVersionRestored
+func FileVersionRestored(ev events.FileVersionRestored) AuditEventFileVersionRestored {
+	iid, path, uid := extractFileDetails(ev.FileID, ev.Owner)
+	base := BasicAuditEvent(uid, "", MessageFileVersionRestored(iid, ev.Key), ActionFileVersionRestored)
+	return AuditEventFileVersionRestored{
+		AuditEventFiles: FilesAuditEvent(base, iid, uid, path),
+		Key:             ev.Key,
+	}
+}
+
 func extractGrantee(uid *user.UserId, gid *group.GroupId) (string, string) {
 	switch {
 	case uid != nil && uid.OpaqueId != "":
@@ -231,6 +321,20 @@ func extractGrantee(uid *user.UserId, gid *group.GroupId) (string, string) {
 	return "", ""
 }
 
+func extractFileDetails(ref *provider.Reference, owner *user.UserId) (string, string, string) {
+	id, path := "", ""
+	if ref != nil {
+		path = ref.GetPath()
+		id, _ = utils.FormatStorageSpaceReference(ref)
+	}
+
+	uid := ""
+	if owner != nil {
+		uid = owner.GetOpaqueId()
+	}
+	return id, path, uid
+}
+
 func formatTime(t *types.Timestamp) string {
 	if t == nil {
 		return ""
diff --git a/audit/pkg/types/events.go b/audit/pkg/types/events.go
index 801dd41790d..85cb3c99c74 100644
--- a/audit/pkg/types/events.go
+++ b/audit/pkg/types/events.go
@@ -16,5 +16,12 @@ func RegisteredEvents() []events.Unmarshaller {
 		events.ReceivedShareUpdated{},
 		events.LinkAccessed{},
 		events.LinkAccessFailed{},
+		events.FileUploaded{},
+		events.FileDownloaded{},
+		events.ItemTrashed{},
+		events.ItemMoved{},
+		events.ItemPurged{},
+		events.ItemRestored{},
+		events.FileVersionRestored{},
 	}
 }
diff --git a/audit/pkg/types/types.go b/audit/pkg/types/types.go
index 17e2d0a3e16..1b1728f0237 100644
--- a/audit/pkg/types/types.go
+++ b/audit/pkg/types/types.go
@@ -15,6 +15,10 @@ type AuditEvent struct {
 	Level      int    // the log level of the entry (usually 1 for audit events)
 }
 
+/*
+   Sharing
+*/
+
 // AuditEventSharing is the basic audit event for shares
 type AuditEventSharing struct {
 	AuditEvent
@@ -76,3 +80,77 @@ type AuditEventLinkAccessed struct {
 	Success    bool   // If the request was successful.
 	ItemType   string // file or folder
 }
+
+/*
+   Files
+*/
+
+// AuditEventFiles is the basic audit event for files
+type AuditEventFiles struct {
+	AuditEvent
+
+	Path   string // The full path to the create file.
+	Owner  string // The UID of the owner of the file.
+	FileID string // The newly created files identifier.
+}
+
+// AuditEventFileCreated is the event logged when a file is created
+type AuditEventFileCreated struct {
+	AuditEventFiles
+}
+
+// AuditEventFileRead is the event logged when a file is read (aka downloaded)
+type AuditEventFileRead struct {
+	AuditEventFiles
+}
+
+// AuditEventFileUpdated is the event logged when a file is updated
+// TODO: How to differentiate between new uploads and new version uploads?
+// FIXME: implement
+type AuditEventFileUpdated struct {
+	AuditEventFiles
+}
+
+// AuditEventFileDeleted is the event logged when a file is deleted (aka trashed)
+type AuditEventFileDeleted struct {
+	AuditEventFiles
+}
+
+// AuditEventFileCopied is the event logged when a file is copied
+// TODO: copy is a download&upload for now. How to know it was a copy?
+// FIXME: implement
+type AuditEventFileCopied struct {
+	AuditEventFiles
+}
+
+// AuditEventFileRenamed is the event logged when a file is renamed (moved)
+type AuditEventFileRenamed struct {
+	AuditEventFiles
+
+	OldPath string
+}
+
+// AuditEventFilePurged is the event logged when a file is purged (deleted from trashbin)
+type AuditEventFilePurged struct {
+	AuditEventFiles
+}
+
+// AuditEventFileRestored is the event logged when a file is restored (from trashbin)
+type AuditEventFileRestored struct {
+	AuditEventFiles
+
+	OldPath string
+}
+
+// AuditEventFileVersionRestored is the event logged when a file version is restored
+type AuditEventFileVersionRestored struct {
+	AuditEventFiles
+
+	Key string
+}
+
+// AuditEventFileVersionDeleted is the event logged when a file version is deleted
+// TODO: is this even possible?
+type AuditEventFileVersionDeleted struct {
+	AuditEventFiles
+}
diff --git a/changelog/unreleased/file-events-audit-logging.md b/changelog/unreleased/file-events-audit-logging.md
new file mode 100644
index 00000000000..816f0e61d0a
--- /dev/null
+++ b/changelog/unreleased/file-events-audit-logging.md
@@ -0,0 +1,5 @@
+Enhancement: Audit logger will now log file events
+
+See full list of supported events in `audit/pkg/types/types.go`
+
+https://github.com/owncloud/ocis/pull/3332
diff --git a/changelog/unreleased/password-reset-link.md b/changelog/unreleased/password-reset-link.md
new file mode 100644
index 00000000000..01b57657ce8
--- /dev/null
+++ b/changelog/unreleased/password-reset-link.md
@@ -0,0 +1,7 @@
+Enhancement: Add password reset link to login page
+
+Added a configurable passwort reset link to the login page.
+It can be set via `IDP_PASSWORD_RESET_URI`. If the option is not set
+the link will not be shown.
+
+https://github.com/owncloud/ocis/pull/3329
diff --git a/changelog/unreleased/space-aliases.md b/changelog/unreleased/space-aliases.md
new file mode 100644
index 00000000000..4d291c09458
--- /dev/null
+++ b/changelog/unreleased/space-aliases.md
@@ -0,0 +1,5 @@
+Enhancement: Add space aliases
+
+Space aliases can be used to resolve spaceIDs in a client.
+
+https://github.com/owncloud/ocis/pull/3283
diff --git a/changelog/unreleased/update-reva.md b/changelog/unreleased/update-reva.md
new file mode 100644
index 00000000000..e7cc5b9885b
--- /dev/null
+++ b/changelog/unreleased/update-reva.md
@@ -0,0 +1,9 @@
+Enhancement: update reva to vXXXX
+
+Updated reva to version XXXX. This update includes:
+
+  * Enh [cs3org/reva#2622](https://github.com/cs3org/reva/pull/2622): Implement listing shares in spaces
+  * Enh [cs3org/reva#2624](https://github.com/cs3org/reva/pull/2624): Include spaces etags in virtual spaces
+  * Enh [cs3org/reva#2628](https://github.com/cs3org/reva/pull/2628): Webdav trash-bin API for spaces
+
+https://github.com/owncloud/ocis/pull/3330
diff --git a/docs/ocis/storage-backends/dcfsnfs.md b/docs/ocis/storage-backends/dcfsnfs.md
new file mode 100644
index 00000000000..07e68fbf435
--- /dev/null
+++ b/docs/ocis/storage-backends/dcfsnfs.md
@@ -0,0 +1,64 @@
+---
+title: "Decomposed FS on NFS"
+date: 2020-03-15T16:35:00+01:00
+weight: 30
+geekdocRepo: https://github.com/owncloud/ocis
+geekdocEditPath: edit/master/docs/ocis/storage-backends/
+geekdocFilePath: dcfsnfs.md
+---
+
+{{< toc >}}
+
+oCIS' default storage backend is the Decomposed FS. The Decomposed FS can be set up to run on a NFS share. That way the same storage can be provided over network to other nodes running oCIS.
+
+This document summarizes a few important considerations of the NFS setup and describes a tested setup. The test has not covered concurrent access of data from different nodes yet.
+
+## NFS Server Setup
+
+This document covers the linux kernel NFS server on a standard Linux running on x86_64.
+
+The NFS server needs to be set up in a way that it supports [extended file attributes](https://en.wikipedia.org/wiki/Extended_file_attributes).
+
+Extended attributes are supported by NFS starting with Kernel version 5.9, which means that the server with the NFS server has to run a kernel with that or a higher version number. To check that, run the command `uname -a` on the NFS server and compare the displayed version number.
+
+The NFS server in the test setup was configured with the following line in the config file `/etc/exports`:
+
+`/space/nfstest  192.168.178.0/24(rw,root_squash,async,subtree_check,anonuid=0,anongid=100,all_squash)`
+
+This exports the directory `/space/nfstest` to the internal network with certain options.
+
+Important:
+
+- The share needs to be exported with the `async` option for proper NFS performance.
+
+## NFS Client Mount
+
+The nodes that run oCIS need to mount the NFS storage to a local mount point.
+
+The test setup uses the client mount command: `mount -t nfs -o nfsvers=4 192.168.178.28:/space/nfstest /mnt/ocisdata/`
+
+It sets the NFS version to 4, which is important to support extended attributes.
+
+After successful mounting the storage on the client, it can be checked if the NFS setup really supports extended attributes properly using the following commands.
+
+`setfattr -n user.test -v "xattr test string" ocisdata/foo` to write an extended attribute to a file, and `getfattr -d ocisdata/foo` to list all the attributes a file has set.
+
+{{< hint info >}}
+The NFS server setup can be optimized considering system administrative-, performance- and security options. This is not (yet) covered in this documentation.
+{{< /hint >}}
+
+## oCIS Start using the NFS Share
+
+The oCIS server can be instructed to set up the decomposed FS at a certain path by setting the environment variable `OCIS_BASE_DATA_PATH`.
+
+The test setup started an oCIS tech preview single binary release using this start command:
+
+```
+OCIS_BASE_DATA_PATH=/mnt/ocisdata/ OCIS_LOG_LEVEL=debug OCIS_INSECURE=true PROXY_HTTP_ADDR=0.0.0.0:9200 OCIS_URL=https://hostname:9200  ./ocis-1.18.0-linux-amd64 server
+```
+
+This starts oCIS and a decomposed FS skeleton file system structure is set up on the NFS share.
+
+The oCIS instance is passing a smoke test.
+
+
diff --git a/go.mod b/go.mod
index 23bc4416275..5378d6c931a 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
 	github.com/blevesearch/bleve/v2 v2.3.1
 	github.com/coreos/go-oidc/v3 v3.1.0
 	github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19
-	github.com/cs3org/reva/v2 v2.0.0-20220314085001-8e5b22a20a3f
+	github.com/cs3org/reva/v2 v2.0.0-20220317153101-5a93e519610c
 	github.com/disintegration/imaging v1.6.2
 	github.com/glauth/glauth/v2 v2.0.0-20211021011345-ef3151c28733
 	github.com/go-chi/chi/v5 v5.0.7
@@ -54,23 +54,23 @@ require (
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/onsi/ginkgo v1.16.5
 	github.com/onsi/gomega v1.18.1
-	github.com/owncloud/libre-graph-api-go v0.12.0
+	github.com/owncloud/libre-graph-api-go v0.13.1
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.12.1
 	github.com/rs/zerolog v1.26.1
 	github.com/sirupsen/logrus v1.8.1
 	github.com/spf13/cobra v1.4.0
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.7.1
 	github.com/test-go/testify v1.1.4
 	github.com/thejerf/suture/v4 v4.0.2
-	github.com/urfave/cli/v2 v2.3.0
+	github.com/urfave/cli/v2 v2.4.0
 	go-micro.dev/v4 v4.6.0
 	go.opencensus.io v0.23.0
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0
-	go.opentelemetry.io/otel v1.4.1
+	go.opentelemetry.io/otel v1.5.0
 	go.opentelemetry.io/otel/exporters/jaeger v1.4.1
-	go.opentelemetry.io/otel/sdk v1.4.1
-	go.opentelemetry.io/otel/trace v1.4.1
+	go.opentelemetry.io/otel/sdk v1.5.0
+	go.opentelemetry.io/otel/trace v1.5.0
 	golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58
 	golang.org/x/image v0.0.0-20211028202545-6944b10bf410
 	golang.org/x/net v0.0.0-20220225172249-27dd8689420f
diff --git a/go.sum b/go.sum
index addc2d175de..f2cea32d57a 100644
--- a/go.sum
+++ b/go.sum
@@ -341,8 +341,8 @@ github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD9
 github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4=
 github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19 h1:1jqPH58jCxvbaJ9WLIJ7W2/m622bWS6ChptzljSG6IQ=
 github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
-github.com/cs3org/reva/v2 v2.0.0-20220314085001-8e5b22a20a3f h1:tv7v6OjbFoDFNB2ikGC+LLaWEOIAJnrZjyO5LRTDL0g=
-github.com/cs3org/reva/v2 v2.0.0-20220314085001-8e5b22a20a3f/go.mod h1:XNtK1HEClNzmz5vyQa2DUw4KH3oqBjQoEsV1LhAGlV0=
+github.com/cs3org/reva/v2 v2.0.0-20220317153101-5a93e519610c h1:tTAuVwgbDNPyeqNJPjMrT1xZ4jZYGSJ2AWqDkvSpXuA=
+github.com/cs3org/reva/v2 v2.0.0-20220317153101-5a93e519610c/go.mod h1:XNtK1HEClNzmz5vyQa2DUw4KH3oqBjQoEsV1LhAGlV0=
 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI=
 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
@@ -1158,8 +1158,8 @@ github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc/go.mod h1:L
 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.12.0 h1:2EzBEzs2r5wP3PYss2woNhTl/0/NZfMOzFe/bvtdsEU=
-github.com/owncloud/libre-graph-api-go v0.12.0/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
+github.com/owncloud/libre-graph-api-go v0.13.1 h1:Pdg+3mmYOeKb5Gr+eUHM6L1CpHcEkaNjcBnxTZfm80s=
+github.com/owncloud/libre-graph-api-go v0.13.1/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=
@@ -1368,8 +1368,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -1398,8 +1399,9 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
 github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
 github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/urfave/cli/v2 v2.4.0 h1:m2pxjjDFgDxSPtO8WSdbndj17Wu2y8vOT86wE/tjr+I=
+github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
@@ -1493,18 +1495,21 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.2
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0/go.mod h1:LsankqVDx4W+RhZNA5uWarULII/MBhF5qwCYxTuyXjs=
 go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
 go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
-go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g=
 go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
+go.opentelemetry.io/otel v1.5.0 h1:DhCU8oR2sJH9rfnwPdoV/+BJ7UIN5kXHL8DuSGrPU8E=
+go.opentelemetry.io/otel v1.5.0/go.mod h1:Jm/m+rNp/z0eqJc74H7LPwQ3G87qkU/AnnAydAjSAHk=
 go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
 go.opentelemetry.io/otel/exporters/jaeger v1.4.1 h1:VHCK+2yTZDqDaVXj7JH2Z/khptuydo6C0ttBh2bxAbc=
 go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI=
 go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
-go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y=
 go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE=
+go.opentelemetry.io/otel/sdk v1.5.0 h1:QKhWBbcOC9fDCZKCfPFjWTWpfIlJR+i9xiUDYrLVmZs=
+go.opentelemetry.io/otel/sdk v1.5.0/go.mod h1:CU4J1v+7iEljnm1G14QjdFWOXUyYLHVh0Lh+/BTYyFg=
 go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
 go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
-go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ=
 go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
+go.opentelemetry.io/otel/trace v1.5.0 h1:AKQZ9zJsBRFAp7zLdyGNkqG2rToCDIt3i5tcLzQlbmU=
+go.opentelemetry.io/otel/trace v1.5.0/go.mod h1:sq55kfhjXYr1zVSyexg0w1mpa03AYXR5eyTkB9NPPdE=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
diff --git a/graph/pkg/service/v0/drives.go b/graph/pkg/service/v0/drives.go
index e3e16186164..a82f284223c 100644
--- a/graph/pkg/service/v0/drives.go
+++ b/graph/pkg/service/v0/drives.go
@@ -218,6 +218,10 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
 		csr.Opaque = utils.AppendPlainToOpaque(csr.Opaque, "description", *drive.Description)
 	}
 
+	if drive.DriveAlias != nil {
+		csr.Opaque = utils.AppendPlainToOpaque(csr.Opaque, "spaceAlias", *drive.DriveAlias)
+	}
+
 	resp, err := client.CreateStorageSpace(r.Context(), &csr)
 	if err != nil {
 		errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -303,24 +307,19 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	// Note: this is the Opaque prop of the space itself
-	opaque := make(map[string]*types.OpaqueEntry)
 	if drive.Description != nil {
-		opaque["description"] = &types.OpaqueEntry{
-			Decoder: "plain",
-			Value:   []byte(*drive.Description),
-		}
+		updateSpaceRequest.StorageSpace.Opaque = utils.AppendPlainToOpaque(updateSpaceRequest.StorageSpace.Opaque, "description", *drive.Description)
+	}
+
+	if drive.DriveAlias != nil {
+		updateSpaceRequest.StorageSpace.Opaque = utils.AppendPlainToOpaque(updateSpaceRequest.StorageSpace.Opaque, "spaceAlias", *drive.DriveAlias)
 	}
 
 	for _, special := range drive.Special {
 		if special.Id != nil {
-			opaque[*special.SpecialFolder.Name] = &types.OpaqueEntry{
-				Decoder: "plain",
-				Value:   []byte(*special.Id),
-			}
+			updateSpaceRequest.StorageSpace.Opaque = utils.AppendPlainToOpaque(updateSpaceRequest.StorageSpace.Opaque, *special.SpecialFolder.Name, *special.Id)
 		}
 	}
-	updateSpaceRequest.StorageSpace.Opaque = &types.Opaque{Map: opaque}
 
 	if drive.Name != nil {
 		updateSpaceRequest.StorageSpace.Name = *drive.Name
@@ -508,6 +507,10 @@ func (g Graph) cs3StorageSpaceToDrive(baseURL *url.URL, space *storageprovider.S
 			drive.Description = libregraph.PtrString(string(description.Value))
 		}
 
+		if alias, ok := space.Opaque.Map["spaceAlias"]; ok {
+			drive.DriveAlias = libregraph.PtrString(string(alias.Value))
+		}
+
 		if v, ok := space.Opaque.Map["trashed"]; ok {
 			deleted := &libregraph.Deleted{}
 			deleted.SetState(string(v.Value))
diff --git a/idp/pkg/config/service.go b/idp/pkg/config/service.go
index c019b73046e..261a336e2ba 100644
--- a/idp/pkg/config/service.go
+++ b/idp/pkg/config/service.go
@@ -2,5 +2,6 @@ package config
 
 // Service defines the available service configuration.
 type Service struct {
-	Name string `ocisConfig:"-" yaml:"-"`
+	Name             string `ocisConfig:"-" yaml:"-"`
+	PasswordResetURI string `ocisConfig:"password_reset_uri" env:"IDP_PASSWORD_RESET_URI" desc:"The URI where a user can reset their password."`
 }
diff --git a/idp/pkg/service/v0/service.go b/idp/pkg/service/v0/service.go
index cf486f16214..0598f0b59d3 100644
--- a/idp/pkg/service/v0/service.go
+++ b/idp/pkg/service/v0/service.go
@@ -214,6 +214,8 @@ func (idp IDP) Index() http.HandlerFunc {
 	nonce := rndm.GenerateRandomString(32)
 	indexHTML = bytes.Replace(indexHTML, []byte("__CSP_NONCE__"), []byte(nonce), 1)
 
+	indexHTML = bytes.Replace(indexHTML, []byte("__PASSWORD_RESET_LINK__"), []byte(idp.config.Service.PasswordResetURI), 1)
+
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusOK)
 		if _, err := w.Write(indexHTML); err != nil {
diff --git a/idp/ui/public/index.html b/idp/ui/public/index.html
index 9896a1a725a..1004a430603 100644
--- a/idp/ui/public/index.html
+++ b/idp/ui/public/index.html
@@ -13,6 +13,6 @@
     <noscript>
       You need to enable JavaScript to run this app.
     </noscript>
-    <main id="root" class="oc-login-bg" data-path-prefix="__PATH_PREFIX__"></main>
+    <main id="root" class="oc-login-bg" data-path-prefix="__PATH_PREFIX__" passwort-reset-link="__PASSWORD_RESET_LINK__"></main>
   </body>
 </html>
diff --git a/idp/ui/src/containers/Login/Login.js b/idp/ui/src/containers/Login/Login.js
index 65b36c1473c..83e7c662656 100644
--- a/idp/ui/src/containers/Login/Login.js
+++ b/idp/ui/src/containers/Login/Login.js
@@ -9,6 +9,7 @@ import Button from '@material-ui/core/Button';
 import CircularProgress from '@material-ui/core/CircularProgress';
 import green from '@material-ui/core/colors/green';
 import Typography from '@material-ui/core/Typography';
+import Link from '@material-ui/core/Link';
 
 import TextInput from '../../components/TextInput'
 
@@ -55,7 +56,8 @@ class Login extends React.PureComponent {
   }
 
   render() {
-    const { loading, errors, classes, username } = this.props;
+    const { loading, errors, classes, username, passwordResetLink } = this.props;
+    const loginFailed = errors.http;
     const hasError = errors.http || errors.username || errors.password;
     const errorMessage = errors.http
       ? <ErrorMessage error={errors.http}></ErrorMessage>
@@ -109,6 +111,8 @@ class Login extends React.PureComponent {
               {hasError && <Typography id="oc-login-error-message" variant="subtitle2" component="span" color="error"
                                        className={classes.message}>{errorMessage}</Typography>}
               <div className={classes.wrapper}>
+                {loginFailed && passwordResetLink && <Link id="oc-login-password-reset" href={passwordResetLink} variant="subtitle2">{"Reset password?"}</Link>}
+                <br />
                 <Button
                     type="submit"
                     color="primary"
@@ -150,6 +154,7 @@ Login.propTypes = {
   loading: PropTypes.string.isRequired,
   username: PropTypes.string.isRequired,
   password: PropTypes.string.isRequired,
+  passwordResetLink: PropTypes.string.isRequired,
   errors: PropTypes.object.isRequired,
   hello: PropTypes.object,
   query: PropTypes.object.isRequired,
@@ -160,7 +165,7 @@ Login.propTypes = {
 
 const mapStateToProps = (state) => {
   const { loading, username, password, errors} = state.login;
-  const { hello, query } = state.common;
+  const { hello, query, passwordResetLink } = state.common;
 
   return {
     loading,
@@ -168,7 +173,8 @@ const mapStateToProps = (state) => {
     password,
     errors,
     hello,
-    query
+    query,
+    passwordResetLink
   };
 };
 
diff --git a/idp/ui/src/reducers/common.js b/idp/ui/src/reducers/common.js
index 163ecdc177d..3279df0f53a 100644
--- a/idp/ui/src/reducers/common.js
+++ b/idp/ui/src/reducers/common.js
@@ -20,13 +20,24 @@ const defaultPathPrefix = (() => {
   return pathPrefix;
 })();
 
+const defaultPasswordResetLink = (() => {
+  const root = document.getElementById('root');
+  let link = root ? root.getAttribute('passwort-reset-link') : null;
+  if (!link || link === '__PASSWORD_RESET_LINK__') {
+    // Not replaced, probably we are running in debug mode or whatever. Use sane default.
+    link = '';
+  }
+  return link;
+})();
+
 const defaultState = {
   hello: null,
   error: null,
   flow: flow,
   query: query,
   updateAvailable: false,
-  pathPrefix: defaultPathPrefix
+  pathPrefix: defaultPathPrefix,
+  passwordResetLink: defaultPasswordResetLink
 };
 
 function commonReducer(state = defaultState, action) {
diff --git a/proxy/pkg/user/backend/cs3.go b/proxy/pkg/user/backend/cs3.go
index e754760b99f..cc423ca6e95 100644
--- a/proxy/pkg/user/backend/cs3.go
+++ b/proxy/pkg/user/backend/cs3.go
@@ -61,11 +61,21 @@ func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, w
 		}
 	}
 
+	// if roles are empty, assume we haven't seen the user before and assign a
+	// default user role. At least until proper roles are provided. See
+	// https://github.com/owncloud/ocis/issues/1825 for more context.
 	if len(roleIDs) == 0 {
-		roleIDs = append(roleIDs, settingsService.BundleUUIDRoleUser, settingsService.SelfManagementPermissionID)
-		// if roles are empty, assume we haven't seen the user before and assign a default user role. At least until
-		// proper roles are provided. See https://github.com/owncloud/ocis/issues/1825 for more context.
-		//return user, nil
+		if user.Id.Type == cs3.UserType_USER_TYPE_PRIMARY {
+			c.logger.Info().Str("userid", user.Id.OpaqueId).Msg("user has no role assigned, assigning default user role")
+			_, err := c.settingsRoleService.AssignRoleToUser(ctx, &settingssvc.AssignRoleToUserRequest{
+				AccountUuid: user.Id.OpaqueId,
+				RoleId:      settingsService.BundleUUIDRoleUser,
+			})
+			if err != nil {
+				c.logger.Error().Err(err).Msg("Could not add default role")
+			}
+			roleIDs = append(roleIDs, settingsService.BundleUUIDRoleUser)
+		}
 	}
 
 	enc, err := encodeRoleIDs(roleIDs)
diff --git a/settings/package.json b/settings/package.json
index 17cf9ed6c8c..3eedad80c57 100644
--- a/settings/package.json
+++ b/settings/package.json
@@ -32,13 +32,13 @@
   "devDependencies": {
     "@babel/core": "^7.15.5",
     "@babel/plugin-proposal-class-properties": "^7.7.4",
-    "@babel/plugin-proposal-export-default-from": "^7.7.4",
+    "@babel/plugin-proposal-export-default-from": "^7.16.7",
     "@babel/plugin-proposal-object-rest-spread": "^7.15.6",
     "@babel/plugin-syntax-dynamic-import": "^7.7.4",
     "@babel/plugin-transform-runtime": "^7.15.0",
     "@babel/polyfill": "^7.10.1",
     "@babel/preset-env": "^7.16.11",
-    "@babel/register": "^7.14.5",
+    "@babel/register": "^7.17.0",
     "@cucumber/cucumber": "^7.3.1",
     "@cucumber/pretty-formatter": "^1.0.0-alpha.1",
     "@erquhart/rollup-plugin-node-builtins": "^2.1.5",
diff --git a/settings/yarn.lock b/settings/yarn.lock
index 3b6878732ba..c847c66e949 100644
--- a/settings/yarn.lock
+++ b/settings/yarn.lock
@@ -572,13 +572,13 @@
     "@babel/helper-plugin-utils" "^7.16.7"
     "@babel/plugin-syntax-dynamic-import" "^7.8.3"
 
-"@babel/plugin-proposal-export-default-from@^7.7.4":
-  version "7.14.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.14.5.tgz#8931a6560632c650f92a8e5948f6e73019d6d321"
-  integrity sha512-T8KZ5abXvKMjF6JcoXjgac3ElmXf0AWzJwi2O/42Jk+HmCky3D9+i1B7NPP1FblyceqTevKeV/9szeikFoaMDg==
+"@babel/plugin-proposal-export-default-from@^7.16.7":
+  version "7.16.7"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.16.7.tgz#a40ab158ca55627b71c5513f03d3469026a9e929"
+  integrity sha512-+cENpW1rgIjExn+o5c8Jw/4BuH4eGKKYvkMB8/0ZxFQ9mC0t4z09VsPIwNg6waF69QYC81zxGeAsREGuqQoKeg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.14.5"
-    "@babel/plugin-syntax-export-default-from" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.16.7"
+    "@babel/plugin-syntax-export-default-from" "^7.16.7"
 
 "@babel/plugin-proposal-export-namespace-from@^7.16.7":
   version "7.16.7"
@@ -702,12 +702,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-export-default-from@^7.14.5":
-  version "7.14.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.14.5.tgz#cdfa9d43d2b2c89b6f1af3e83518e8c8b9ed0dbc"
-  integrity sha512-snWDxjuaPEobRBnhpqEfZ8RMxDbHt8+87fiEioGuE+Uc0xAKgSD8QiuL3lF93hPVQfZFAcYwrrf+H5qUhike3Q==
+"@babel/plugin-syntax-export-default-from@^7.16.7":
+  version "7.16.7"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.16.7.tgz#fa89cf13b60de2c3f79acdc2b52a21174c6de060"
+  integrity sha512-4C3E4NsrLOgftKaTYTULhHsuQrGv3FHrBzOMDiS7UYKIpgGBkAdawg4h+EI8zPeK9M0fiIIh72hIwsI24K7MbA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.14.5"
+    "@babel/helper-plugin-utils" "^7.16.7"
 
 "@babel/plugin-syntax-export-namespace-from@^7.8.3":
   version "7.8.3"
@@ -1140,15 +1140,15 @@
     "@babel/types" "^7.4.4"
     esutils "^2.0.2"
 
-"@babel/register@^7.14.5":
-  version "7.15.3"
-  resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.15.3.tgz#6b40a549e06ec06c885b2ec42c3dd711f55fe752"
-  integrity sha512-mj4IY1ZJkorClxKTImccn4T81+UKTo4Ux0+OFSV9hME1ooqS9UV+pJ6BjD0qXPK4T3XW/KNa79XByjeEMZz+fw==
+"@babel/register@^7.17.0":
+  version "7.17.0"
+  resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.17.0.tgz#8051e0b7cb71385be4909324f072599723a1f084"
+  integrity sha512-UNZsMAZ7uKoGHo1HlEXfteEOYssf64n/PNLHGqOKq/bgYcu/4LrQWAHJwSCb3BRZK8Hi5gkJdRcwrGTO2wtRCg==
   dependencies:
     clone-deep "^4.0.1"
     find-cache-dir "^2.0.0"
     make-dir "^2.1.0"
-    pirates "^4.0.0"
+    pirates "^4.0.5"
     source-map-support "^0.5.16"
 
 "@babel/runtime@^7.13.8", "@babel/runtime@^7.8.4":
@@ -6409,12 +6409,10 @@ pirates@^3.0.2:
   dependencies:
     node-modules-regexp "^1.0.0"
 
-pirates@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
-  integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
-  dependencies:
-    node-modules-regexp "^1.0.0"
+pirates@^4.0.5:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
+  integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
 
 pixelmatch@^4.0.0:
   version "4.0.2"
diff --git a/storage/pkg/command/sharing.go b/storage/pkg/command/sharing.go
index f1dc02a35e2..c043b93d360 100644
--- a/storage/pkg/command/sharing.go
+++ b/storage/pkg/command/sharing.go
@@ -142,6 +142,13 @@ func sharingConfigFromStruct(c *cli.Context, cfg *config.Config) map[string]inte
 							"db_port":          cfg.Reva.Sharing.UserSQLPort,
 							"db_name":          cfg.Reva.Sharing.UserSQLName,
 						},
+						"cs3": map[string]interface{}{
+							"gateway_addr":        cfg.Reva.Gateway.Endpoint,
+							"provider_addr":       cfg.Reva.Sharing.CS3ProviderAddr,
+							"service_user_id":     cfg.Reva.Sharing.CS3ServiceUser,
+							"service_user_idp":    cfg.Reva.Sharing.CS3ServiceUserIdp,
+							"machine_auth_apikey": cfg.Reva.AuthMachineConfig.MachineAuthAPIKey,
+						},
 					},
 				},
 				"publicshareprovider": map[string]interface{}{
diff --git a/storage/pkg/command/storagedrivers/user.go b/storage/pkg/command/storagedrivers/user.go
index 412f08deef6..a15527344c3 100644
--- a/storage/pkg/command/storagedrivers/user.go
+++ b/storage/pkg/command/storagedrivers/user.go
@@ -89,12 +89,14 @@ func UserDrivers(cfg *config.Config) map[string]interface{} {
 			"userprovidersvc": cfg.Reva.Users.Endpoint,
 		},
 		"ocis": map[string]interface{}{
-			"root":                cfg.Reva.UserStorage.OCIS.Root,
-			"user_layout":         cfg.Reva.UserStorage.OCIS.UserLayout,
-			"share_folder":        cfg.Reva.UserStorage.OCIS.ShareFolder,
-			"treetime_accounting": true,
-			"treesize_accounting": true,
-			"permissionssvc":      cfg.Reva.Permissions.Endpoint,
+			"root":                        cfg.Reva.UserStorage.OCIS.Root,
+			"user_layout":                 cfg.Reva.UserStorage.OCIS.UserLayout,
+			"share_folder":                cfg.Reva.UserStorage.OCIS.ShareFolder,
+			"personalspacealias_template": cfg.Reva.UserStorage.OCIS.PersonalSpaceAliasTemplate,
+			"generalspacealias_template":  cfg.Reva.UserStorage.OCIS.GeneralSpaceAliasTemplate,
+			"treetime_accounting":         true,
+			"treesize_accounting":         true,
+			"permissionssvc":              cfg.Reva.Permissions.Endpoint,
 		},
 		"s3": map[string]interface{}{
 			"enable_home": false,
@@ -106,17 +108,19 @@ func UserDrivers(cfg *config.Config) map[string]interface{} {
 			"prefix":      cfg.Reva.UserStorage.S3.Root,
 		},
 		"s3ng": map[string]interface{}{
-			"root":                cfg.Reva.UserStorage.S3NG.Root,
-			"user_layout":         cfg.Reva.UserStorage.S3NG.UserLayout,
-			"share_folder":        cfg.Reva.UserStorage.S3NG.ShareFolder,
-			"treetime_accounting": true,
-			"treesize_accounting": true,
-			"permissionssvc":      cfg.Reva.Permissions.Endpoint,
-			"s3.region":           cfg.Reva.UserStorage.S3NG.Region,
-			"s3.access_key":       cfg.Reva.UserStorage.S3NG.AccessKey,
-			"s3.secret_key":       cfg.Reva.UserStorage.S3NG.SecretKey,
-			"s3.endpoint":         cfg.Reva.UserStorage.S3NG.Endpoint,
-			"s3.bucket":           cfg.Reva.UserStorage.S3NG.Bucket,
+			"root":                        cfg.Reva.UserStorage.S3NG.Root,
+			"user_layout":                 cfg.Reva.UserStorage.S3NG.UserLayout,
+			"share_folder":                cfg.Reva.UserStorage.S3NG.ShareFolder,
+			"personalspacealias_template": cfg.Reva.UserStorage.S3NG.PersonalSpaceAliasTemplate,
+			"generalspacealias_template":  cfg.Reva.UserStorage.S3NG.GeneralSpaceAliasTemplate,
+			"treetime_accounting":         true,
+			"treesize_accounting":         true,
+			"permissionssvc":              cfg.Reva.Permissions.Endpoint,
+			"s3.region":                   cfg.Reva.UserStorage.S3NG.Region,
+			"s3.access_key":               cfg.Reva.UserStorage.S3NG.AccessKey,
+			"s3.secret_key":               cfg.Reva.UserStorage.S3NG.SecretKey,
+			"s3.endpoint":                 cfg.Reva.UserStorage.S3NG.Endpoint,
+			"s3.bucket":                   cfg.Reva.UserStorage.S3NG.Bucket,
 		},
 	}
 }
diff --git a/storage/pkg/command/storageusers.go b/storage/pkg/command/storageusers.go
index 3edbd6a1b0a..0ebd6811762 100644
--- a/storage/pkg/command/storageusers.go
+++ b/storage/pkg/command/storageusers.go
@@ -112,6 +112,14 @@ func storageUsersConfigFromStruct(c *cli.Context, cfg *config.Config) map[string
 					"tmp_folder":         cfg.Reva.StorageUsers.TempFolder,
 				},
 			},
+			"interceptors": map[string]interface{}{
+				"eventsmiddleware": map[string]interface{}{
+					"group":     "sharing",
+					"type":      "nats",
+					"address":   cfg.Reva.Sharing.Events.Address,
+					"clusterID": cfg.Reva.Sharing.Events.ClusterID,
+				},
+			},
 		},
 		"http": map[string]interface{}{
 			"network": cfg.Reva.StorageUsers.HTTPNetwork,
diff --git a/storage/pkg/config/config.go b/storage/pkg/config/config.go
index 086f4b9ef23..fcb29b09bde 100644
--- a/storage/pkg/config/config.go
+++ b/storage/pkg/config/config.go
@@ -78,6 +78,9 @@ type Sharing struct {
 	Port
 	UserDriver                       string `ocisConfig:"user_driver"`
 	UserJSONFile                     string `ocisConfig:"user_json_file"`
+	CS3ProviderAddr                  string `ocisConfig:"provider_addr"`
+	CS3ServiceUser                   string `ocisConfig:"service_user_id"`
+	CS3ServiceUserIdp                string `ocisConfig:"service_user_idp"`
 	UserSQLUsername                  string `ocisConfig:"user_sql_username"`
 	UserSQLPassword                  string `ocisConfig:"user_sql_password"`
 	UserSQLHost                      string `ocisConfig:"user_sql_host"`
@@ -241,6 +244,12 @@ type DriverCommon struct {
 	UserLayout string `ocisConfig:"user_layout"`
 	// EnableHome enables the creation of home directories.
 	EnableHome bool `ocisConfig:"enable_home"`
+	// PersonalSpaceAliasTemplate  contains the template used to construct
+	// the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"`
+	PersonalSpaceAliasTemplate string `ocisConfig:"personalspacealias_template"`
+	// GeneralSpaceAliasTemplate contains the template used to construct
+	// the general space alias, eg: `{{.SpaceType}}/{{.SpaceName | replace " " "-" | lower}}`
+	GeneralSpaceAliasTemplate string `ocisConfig:"generalspacealias_template"`
 }
 
 // DriverEOS defines the available EOS driver configuration.
@@ -1205,7 +1214,22 @@ func structMappings(cfg *Config) []shared.EnvBinding {
 			Destination: &cfg.Reva.Sharing.PublicJanitorRunInterval,
 		},
 
-		// shqringsql
+		// sharing cs3
+
+		{
+			EnvVars:     []string{"STORAGE_SHARING_CS3_PROVIDER_ADDR"},
+			Destination: &cfg.Reva.Sharing.CS3ProviderAddr,
+		},
+		{
+			EnvVars:     []string{"STORAGE_SHARING_CS3_SERVICE_USER"},
+			Destination: &cfg.Reva.Sharing.CS3ServiceUser,
+		},
+		{
+			EnvVars:     []string{"OCIS_URL", "STORAGE_SHARING_CS3_SERVICE_USER_IDP"},
+			Destination: &cfg.Reva.Sharing.CS3ServiceUserIdp,
+		},
+
+		// sharingsql
 		{
 			EnvVars:     []string{"STORAGE_SHARING_USER_SQL_USERNAME"},
 			Destination: &cfg.Reva.Sharing.UserSQLUsername,
@@ -1528,6 +1552,14 @@ func structMappings(cfg *Config) []shared.EnvBinding {
 			EnvVars:     []string{"STORAGE_USERS_DRIVER_OCIS_SHARE_FOLDER"},
 			Destination: &cfg.Reva.UserStorage.OCIS.ShareFolder,
 		},
+		{
+			EnvVars:     []string{"STORAGE_USERS_DRIVER_OCIS_PERSONAL_SPACE_ALIAS_TEMPLATE"},
+			Destination: &cfg.Reva.UserStorage.OCIS.PersonalSpaceAliasTemplate,
+		},
+		{
+			EnvVars:     []string{"STORAGE_USERS_DRIVER_OCIS_GENERAL_SPACE_ALIAS_TEMPLATE"},
+			Destination: &cfg.Reva.UserStorage.OCIS.GeneralSpaceAliasTemplate,
+		},
 		// driver owncloud sql
 		{
 			EnvVars:     []string{"STORAGE_USERS_DRIVER_OWNCLOUDSQL_DATADIR"},
@@ -1601,6 +1633,14 @@ func structMappings(cfg *Config) []shared.EnvBinding {
 			EnvVars:     []string{"STORAGE_USERS_DRIVER_S3NG_SHARE_FOLDER"},
 			Destination: &cfg.Reva.UserStorage.S3NG.ShareFolder,
 		},
+		{
+			EnvVars:     []string{"STORAGE_USERS_DRIVER_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE"},
+			Destination: &cfg.Reva.UserStorage.S3NG.PersonalSpaceAliasTemplate,
+		},
+		{
+			EnvVars:     []string{"STORAGE_USERS_DRIVER_S3NG_GENERAL_SPACE_ALIAS_TEMPLATE"},
+			Destination: &cfg.Reva.UserStorage.S3NG.GeneralSpaceAliasTemplate,
+		},
 		{
 			EnvVars:     []string{"STORAGE_USERS_DRIVER_S3NG_REGION"},
 			Destination: &cfg.Reva.UserStorage.S3NG.Region,
diff --git a/storage/pkg/config/defaults/defaultconfig.go b/storage/pkg/config/defaults/defaultconfig.go
index 142b05db7ff..2e22656506e 100644
--- a/storage/pkg/config/defaults/defaultconfig.go
+++ b/storage/pkg/config/defaults/defaultconfig.go
@@ -9,11 +9,13 @@ import (
 )
 
 const (
-	defaultPublicURL        = "https://localhost:9200"
-	defaultShareFolder      = "/Shares"
-	defaultStorageNamespace = "/users/{{.Id.OpaqueId}}"
-	defaultGatewayAddr      = "127.0.0.1:9142"
-	defaultUserLayout       = "{{.Id.OpaqueId}}"
+	defaultPublicURL                  = "https://localhost:9200"
+	defaultShareFolder                = "/Shares"
+	defaultStorageNamespace           = "/users/{{.Id.OpaqueId}}"
+	defaultGatewayAddr                = "127.0.0.1:9142"
+	defaultUserLayout                 = "{{.Id.OpaqueId}}"
+	defaultPersonalSpaceAliasTemplate = "{{.SpaceType}}/{{.User.Username | lower}}"
+	defaultGeneralSpaceAliasTemplate  = "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}"
 )
 
 func FullDefaultConfig() *config.Config {
@@ -145,10 +147,12 @@ func DefaultConfig() *config.Config {
 				},
 				S3NG: config.DriverS3NG{
 					DriverCommon: config.DriverCommon{
-						Root:        path.Join(defaults.BaseDataPath(), "storage", "users"),
-						ShareFolder: defaultShareFolder,
-						UserLayout:  defaultUserLayout,
-						EnableHome:  false,
+						Root:                       path.Join(defaults.BaseDataPath(), "storage", "users"),
+						ShareFolder:                defaultShareFolder,
+						UserLayout:                 defaultUserLayout,
+						PersonalSpaceAliasTemplate: defaultPersonalSpaceAliasTemplate,
+						GeneralSpaceAliasTemplate:  defaultGeneralSpaceAliasTemplate,
+						EnableHome:                 false,
 					},
 					Region:    "default",
 					AccessKey: "",
@@ -158,9 +162,11 @@ func DefaultConfig() *config.Config {
 				},
 				OCIS: config.DriverOCIS{
 					DriverCommon: config.DriverCommon{
-						Root:        path.Join(defaults.BaseDataPath(), "storage", "users"),
-						ShareFolder: defaultShareFolder,
-						UserLayout:  defaultUserLayout,
+						Root:                       path.Join(defaults.BaseDataPath(), "storage", "users"),
+						ShareFolder:                defaultShareFolder,
+						UserLayout:                 defaultUserLayout,
+						PersonalSpaceAliasTemplate: defaultPersonalSpaceAliasTemplate,
+						GeneralSpaceAliasTemplate:  defaultGeneralSpaceAliasTemplate,
 					},
 				},
 			},
@@ -266,7 +272,7 @@ func DefaultConfig() *config.Config {
 				},
 				CommitShareToStorageGrant:  true,
 				CommitShareToStorageRef:    true,
-				DisableHomeCreationOnLogin: false,
+				DisableHomeCreationOnLogin: true,
 				ShareFolder:                "Shares",
 				LinkGrants:                 "",
 				HomeMapping:                "",
@@ -340,6 +346,9 @@ func DefaultConfig() *config.Config {
 					GRPCAddr:    "127.0.0.1:9150",
 					Services:    []string{"usershareprovider", "publicshareprovider"},
 				},
+				CS3ProviderAddr:                  "127.0.0.1:9215",
+				CS3ServiceUser:                   "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad",
+				CS3ServiceUserIdp:                "https://localhost:9200",
 				UserDriver:                       "json",
 				UserJSONFile:                     path.Join(defaults.BaseDataPath(), "storage", "shares.json"),
 				UserSQLUsername:                  "",


Test patch:
diff --git a/audit/pkg/service/service_test.go b/audit/pkg/service/service_test.go
index 8bdcac5cd45..0bd1d22966f 100644
--- a/audit/pkg/service/service_test.go
+++ b/audit/pkg/service/service_test.go
@@ -294,6 +294,124 @@ var testCases = []struct {
 			require.Equal(t, "token-123", ev.ShareToken)
 			require.Equal(t, false, ev.Success)
 		},
+	}, {
+		Alias: "File created",
+		SystemEvent: events.FileUploaded{
+			FileID: reference("sto-123", "iid-123", "./item"),
+			Owner:  userID("uid-123"), // NOTE: owner not yet implemented in reva
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFileCreated{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was created", "file_create")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+		},
+	}, {
+		Alias: "File read",
+		SystemEvent: events.FileDownloaded{
+			FileID: reference("sto-123", "iid-123", "./item"),
+			Owner:  userID("uid-123"), // NOTE: owner not yet implemented in reva
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFileRead{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was read", "file_read")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+		},
+	}, {
+		Alias: "File trashed",
+		SystemEvent: events.ItemTrashed{
+			FileID: reference("sto-123", "iid-123", "./item"),
+			Owner:  userID("uid-123"), // NOTE: owner not yet implemented in reva
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFileDeleted{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was trashed", "file_delete")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+		},
+	}, {
+		Alias: "File renamed",
+		SystemEvent: events.ItemMoved{
+			FileID:       reference("sto-123", "iid-123", "./item"),
+			OldReference: reference("sto-123", "iid-123", "./anotheritem"),
+			Owner:        userID("uid-123"), // NOTE: owner not yet implemented in reva
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFileRenamed{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was moved from './anotheritem' to './item'", "file_rename")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+			// AuditEventFileRenamed fields
+			require.Equal(t, "./anotheritem", ev.OldPath)
+
+		},
+	}, {
+		Alias: "File purged",
+		SystemEvent: events.ItemPurged{
+			FileID: reference("sto-123", "iid-123", "./item"),
+			Owner:  userID("uid-123"), // NOTE: owner not yet implemented in reva
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFilePurged{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was removed from trashbin", "file_trash_delete")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+		},
+	}, {
+		Alias: "File restored",
+		SystemEvent: events.ItemRestored{
+			FileID:       reference("sto-123", "iid-123", "./item"),
+			Owner:        userID("uid-123"), // NOTE: owner not yet implemented in reva
+			OldReference: reference("sto-123", "sto-123!iid-123/item", "./oldpath"),
+			Key:          "",
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFileRestored{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was restored from trashbin to './item'", "file_trash_restore")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+			// AuditEventFileRestored fields
+			require.Equal(t, "./oldpath", ev.OldPath)
+
+		},
+	}, {
+		Alias: "File version restored",
+		SystemEvent: events.FileVersionRestored{
+			FileID: reference("sto-123", "iid-123", "./item"),
+			Owner:  userID("uid-123"), // NOTE: owner not yet implemented in reva
+			Key:    "v1",
+		},
+		CheckAuditEvent: func(t *testing.T, b []byte) {
+			ev := types.AuditEventFileVersionRestored{}
+			require.NoError(t, json.Unmarshal(b, &ev))
+
+			// AuditEvent fields
+			checkBaseAuditEvent(t, ev.AuditEvent, "uid-123", "", "File 'sto-123!iid-123/item' was restored in version 'v1'", "file_version_restore")
+			// AuditEventSharing fields
+			checkFilesAuditEvent(t, ev.AuditEventFiles, "sto-123!iid-123/item", "uid-123", "./item")
+			// AuditEventFileRestored fields
+			require.Equal(t, "v1", ev.Key)
+
+		},
 	},
 }
 
@@ -343,6 +461,12 @@ func checkSharingAuditEvent(t *testing.T, ev types.AuditEventSharing, itemID str
 	require.Equal(t, shareID, ev.ShareID)
 }
 
+func checkFilesAuditEvent(t *testing.T, ev types.AuditEventFiles, itemID string, owner string, path string) {
+	require.Equal(t, itemID, ev.FileID)
+	require.Equal(t, owner, ev.Owner)
+	require.Equal(t, path, ev.Path)
+}
+
 func shareID(id string) *collaboration.ShareId {
 	return &collaboration.ShareId{
 		OpaqueId: id,
@@ -376,6 +500,13 @@ func resourceID(sid, oid string) *provider.ResourceId {
 	}
 }
 
+func reference(sid, oid, path string) *provider.Reference {
+	return &provider.Reference{
+		ResourceId: resourceID(sid, oid),
+		Path:       path,
+	}
+}
+
 func timestamp(seconds uint64) *rtypes.Timestamp {
 	return &rtypes.Timestamp{
 		Seconds: seconds,
@@ -394,6 +525,7 @@ func linkPermissions(perms ...string) *link.PublicSharePermissions {
 		Permissions: permissions(perms...),
 	}
 }
+
 func permissions(permissions ...string) *provider.ResourcePermissions {
 	perms := &provider.ResourcePermissions{}
 
diff --git a/graph/pkg/service/v0/graph_test.go b/graph/pkg/service/v0/graph_test.go
index e475af9e3f9..30c1160772d 100644
--- a/graph/pkg/service/v0/graph_test.go
+++ b/graph/pkg/service/v0/graph_test.go
@@ -10,6 +10,7 @@ import (
 
 	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
 	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
+	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -116,6 +117,12 @@ var _ = Describe("Graph", func() {
 							OpaqueId:  "bopaqueid",
 						},
 						Name: "bspacename",
+						Opaque: &typesv1beta1.Opaque{
+							Map: map[string]*typesv1beta1.OpaqueEntry{
+								"spaceAlias": {Decoder: "plain", Value: []byte("bspacetype/bspacename")},
+								"etag":       {Decoder: "plain", Value: []byte("123456789")},
+							},
+						},
 					},
 					{
 						Id:        &provider.StorageSpaceId{OpaqueId: "aspaceid"},
@@ -125,6 +132,12 @@ var _ = Describe("Graph", func() {
 							OpaqueId:  "anopaqueid",
 						},
 						Name: "aspacename",
+						Opaque: &typesv1beta1.Opaque{
+							Map: map[string]*typesv1beta1.OpaqueEntry{
+								"spaceAlias": {Decoder: "plain", Value: []byte("aspacetype/aspacename")},
+								"etag":       {Decoder: "plain", Value: []byte("101112131415")},
+							},
+						},
 					},
 				},
 			}, nil)
@@ -146,19 +159,23 @@ var _ = Describe("Graph", func() {
 			{
 				"value":[
 					{
+						"driveAlias":"aspacetype/aspacename",
 						"driveType":"aspacetype",
 						"id":"aspaceid",
 						"name":"aspacename",
 						"root":{
+							"eTag":"101112131415",
 							"id":"aspaceid!anopaqueid",
 							"webDavUrl":"https://localhost:9200/dav/spaces/aspaceid"
 						}
 					},
 					{
+						"driveAlias":"bspacetype/bspacename",
 						"driveType":"bspacetype",
 						"id":"bspaceid",
 						"name":"bspacename",
 						"root":{
+							"eTag":"123456789",
 							"id":"bspaceid!bopaqueid",
 							"webDavUrl":"https://localhost:9200/dav/spaces/bspaceid"
 						}
diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
index c5630848c3c..5948ee21e21 100644
--- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
@@ -1859,7 +1859,6 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers
 #### [status does not have new product data item](https://github.com/owncloud/ocis/issues/3317)
 
 - [apiCapabilities/capabilities.feature:959](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L959)
-- [apiMain/status.feature:5](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/status.feature#L5)
 
   Note: always have an empty line at the end of this file.
   The bash script that processes this file requires that the last line has a newline on the end.
diff --git a/tests/acceptance/features/apiSpaces/listSpaces.feature b/tests/acceptance/features/apiSpaces/listSpaces.feature
index 920700154a8..7c6a01f3392 100644
--- a/tests/acceptance/features/apiSpaces/listSpaces.feature
+++ b/tests/acceptance/features/apiSpaces/listSpaces.feature
@@ -15,6 +15,7 @@ Feature: List and create spaces
     And the json responded should contain a space "Alice Hansen" with these key and value pairs:
       | key              | value        |
       | driveType        | personal     |
+      | driveAlias       | personal/alice |
       | id               | %space_id%   |
       | name             | Alice Hansen |
       | quota@@@state    | normal       |
@@ -61,6 +62,7 @@ Feature: List and create spaces
     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   |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
@@ -98,6 +100,7 @@ Feature: List and create spaces
     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          |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
@@ -105,6 +108,7 @@ Feature: List and create spaces
     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          |
       | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |