Repository: owncloud/ocis
Pull request #3283 (2022-03-07 16:56:31)

[full-ci] add driveAlias to the graphAPI
## Description

Implements https://github.com/owncloud/libre-graph-api/issues/6
Needs https://github.com/cs3org/reva/pull/2623

The concept is described in the ADR https://owncloud.dev/ocis/adr/0011-global-url-format/#mixed-global-urls in the section `mixed global urls`.

## Related Issue
-  https://github.com/cs3org/reva/pull/2623

## Motivation and Context

### Space identifier

The primary identifier for a space is **always the UUID**. A unique logical ID is the only stable indentifier that works under all circumstances

### A technical URL with a stable identifier
`https://cloud.owncloud.com/files/spaces/95b4c6f6-d8f2-4976-b0a3-1b016bdfcb7e/relative/path/to/file.txt`. 

### Make it more user friendly
To make the URL human-readable  we replace the UUID with an alias. 
`https://cloud.owncloud.com/files/spaces/personal/einstein/relative/path/to/file.txt`

The WebUI discovers the spaces using the `/drives` Endpoint. It should return a space with the alias `personal/einstein` and the `id:95b4c6f6-d8f2-4976-b0a3-1b016bdfcb7e`. This aliasing allows the WebUI to use human-readable URLs and translate the alias back into a UUID which is then used to make the actual WebDAV Requests.

The problem with aliases is, that they are not unique, can be customized and might clash. Therefore the UUID is always appended as an URL Parameter `?id=95b4c6f6-d8f2-4976-b0a3-1b016bdfcb7e`

### Stable and user friendlyURL
`https://cloud.owncloud.com/files/spaces/personal/einstein/relative/path/to/file.txt?id=95b4c6f6-d8f2-4976-b0a3-1b016bdfcb7e`

## New Config Options
| ENV | default value | Description |
|---|---|---|
| `STORAGE_USERS_DRIVER_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE` | `{{.SpaceType}}/{{.User.Username \| lower}}` | contains the template used to construct the personal space alias |
| `STORAGE_USERS_DRIVER_S3NG_GENERAL_SPACE_ALIAS_TEMPLATE` | `{{.SpaceType}}/{{.SpaceName \| replace " " "-" \| lower}}` | contains the template used to construct the general space alias |
| `STORAGE_USERS_DRIVER_OCIS_PERSONAL_SPACE_ALIAS_TEMPLATE` | `{{.SpaceType}}/{{.User.Username \| lower}}` | contains the template used to construct the personal space alias |
| `STORAGE_USERS_DRIVER_OCIS_GENERAL_SPACE_ALIAS_TEMPLATE` | `{{.SpaceType}}/{{.SpaceName \| replace " " "-" \| lower}}` | contains the template used to construct the general space alias |


## How Has This Been Tested?

- manually
- [ ] needs tests

## Example responses (if appropriate):

### List personal space

```bash
curl -L -k -X GET "https://localhost:9200/graph/v1.0/me/drives/" \
-H "Authorization: Basic ZWluc3RlaW46cmVsYXRpdml0eQ=="
```

### Response

```json
{
    "value": [
        {
            "driveAlias": "personal/einstein",
            "driveType": "personal",
            "id": "4c510ada-c86b-4815-8820-42cdf82c3d51",
            "lastModifiedDateTime": "2022-03-07T21:00:11.840287231+01:00",
            "name": "Albert Einstein",
            "owner": {
                "user": {
                    "id": "4c510ada-c86b-4815-8820-42cdf82c3d51"
                }
            },
            "quota": {
                "remaining": 19638898688,
                "state": "normal",
                "total": 19638898688,
                "used": 0
            },
            "root": {
                "eTag": "\"f9adeb482f7dc3ad12a009e851341d02\"",
                "id": "4c510ada-c86b-4815-8820-42cdf82c3d51!4c510ada-c86b-4815-8820-42cdf82c3d51",
                "webDavUrl": "https://localhost:9200/dav/spaces/4c510ada-c86b-4815-8820-42cdf82c3d51"
            }
        }
    ]
}
```

### Create a space
```bash
curl -L -k -X POST "https://localhost:9200/graph/v1.0/drives/" \
-H "Authorization: Basic YWRtaW46YWRtaW4=" \
-H "Content-Type: application/json" \
--data-raw "{
    \"Name\": \"Project Mars\",
    \"driveAlias\": \"project/mars\",
    \"description\": \"Elon Musk wants to fly to mars\"
}"
```

### Response
```json
{
    "description": "Elon Musk wants to fly to mars",
    "driveAlias": "project/mars",
    "driveType": "project",
    "id": "016c23cc-5a72-4b35-b73e-648315c21bc0",
    "lastModifiedDateTime": "2022-03-07T21:03:08.735607295+01:00",
    "name": "Project Mars",
    "owner": {
        "user": {
            "id": "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
        }
    },
    "quota": {
        "total": 1000000000
    },
    "root": {
        "eTag": "\"d2fce34e28284ab262668deb5dfd937a\"",
        "id": "016c23cc-5a72-4b35-b73e-648315c21bc0!016c23cc-5a72-4b35-b73e-648315c21bc0",
        "permissions": [
            {
                "grantedTo": [
                    {
                        "user": {
                            "id": "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
                        }
                    }
                ],
                "roles": [
                    "manager"
                ]
            }
        ],
        "webDavUrl": "https://localhost:9200/dav/spaces/016c23cc-5a72-4b35-b73e-648315c21bc0"
    }
}
```

### Modify a Space

```bash
curl -L -k -X PATCH "https://localhost:9200/graph/v1.0/drives/016c23cc-5a72-4b35-b73e-648315c21bc0" \
-H "Authorization: Basic YWRtaW46YWRtaW4=" \
-H "Content-Type: application/json" \
--data-raw "{
    \"Name\": \"Project Mars\",
    \"driveAlias\": \"project/mars(1)\",
    \"description\": \"Elon Musk wants to fly to mars\"
}"
````

### Response

```json
{
    "description": "Elon Musk wants to fly to mars",
    "driveAlias": "project/mars(1)",
    "driveType": "project",
    "id": "016c23cc-5a72-4b35-b73e-648315c21bc0",
    "lastModifiedDateTime": "2022-03-07T21:03:08.735607295+01:00",
    "name": "Project Mars",
    "owner": {
        "user": {
            "id": "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
        }
    },
    "quota": {
        "remaining": 1000000000,
        "state": "normal",
        "total": 1000000000,
        "used": 0
    },
    "root": {
        "eTag": "\"d2fce34e28284ab262668deb5dfd937a\"",
        "id": "016c23cc-5a72-4b35-b73e-648315c21bc0!016c23cc-5a72-4b35-b73e-648315c21bc0",
        "permissions": [
            {
                "grantedTo": [
                    {
                        "user": {
                            "id": "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
                        }
                    }
                ],
                "roles": [
                    "manager"
                ]
            }
        ],
        "webDavUrl": "https://localhost:9200/dav/spaces/016c23cc-5a72-4b35-b73e-648315c21bc0"
    }
}
```
## 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
- [ ] Unit tests added
- [x] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


update-docs[bot]: Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a [changelog](https://github.com/owncloud/ocis/blob/master/changelog/README.md) item based on your changes.

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=3283&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=3283&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3283&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=3283&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=3283&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3283&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=3283&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=3283&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=3283&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=3283&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=3283&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=3283&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=3283&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=3283&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=3283&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=3283&metric=new_duplicated_lines_density&view=list)


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

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

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=3283&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=3283&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=3283&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=3283&metric=new_duplicated_lines_density&view=list)



Patch:
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/go.mod b/go.mod
index a593e47f8c9..0a7e1ac7f34 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-20220316045927-99115670eb33
 	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
diff --git a/go.sum b/go.sum
index 757c94253e4..1b077fe8e5f 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-20220316045927-99115670eb33 h1:XK88Fs9FteY9a+iKXqPhUK38zNQbP1jj60zy+Tx7SPI=
+github.com/cs3org/reva/v2 v2.0.0-20220316045927-99115670eb33/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=
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/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/config/config.go b/storage/pkg/config/config.go
index 086f4b9ef23..df983cb340e 100644
--- a/storage/pkg/config/config.go
+++ b/storage/pkg/config/config.go
@@ -241,6 +241,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.
@@ -1528,6 +1534,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 +1615,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..a24319458e9 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,
 					},
 				},
 			},


Test patch:
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% |