Repository: owncloud/ocis
Pull request #2979 (2022-01-18 16:19:59)

implement graph endpoint to delete spaces
<!--
Thanks for submitting a change to ownCloud!

This is the bug tracker for the oCIS component. Find other components at https://github.com/owncloud/core/blob/master/.github/CONTRIBUTING.md#guidelines

For fixing potential security issues please see https://owncloud.org/security/

To make it possible for us to get your change reviewed and merged please carefully fill out the requested information below.

Please note that any kind of change needs first be submitted to the master branch which holds the next version of oCIS.

Please set the following labels:

- Set label "Status:Needs-Review" for review or "Status:In-Progress" if the PR still has open tasks.
- Assignment: assign to self
- Reviewers: pick at least one
-->

## Description
Added a new graph endpoint to delete and purge spaces.


## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
We needed a way to delete spaces.

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- locally using curl and the graph-explorer

## 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 (some)
- [ ] 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=2979&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=2979&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=2979&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=2979&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=2979&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=2979&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=2979&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=2979&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=2979&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=2979&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=2979&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=2979&resolved=false&types=CODE_SMELL)

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



Patch:
diff --git a/changelog/unreleased/delete-spaces.md b/changelog/unreleased/delete-spaces.md
new file mode 100644
index 00000000000..c3d9109a861
--- /dev/null
+++ b/changelog/unreleased/delete-spaces.md
@@ -0,0 +1,5 @@
+Enhancement: Add graph endpoint to delete and purge spaces
+
+Added a new graph endpoint to delete and purge spaces.
+
+https://github.com/owncloud/ocis/pull/2979
diff --git a/go.mod b/go.mod
index 3367895a3d1..2c2a1c2b4fc 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/blevesearch/bleve/v2 v2.3.0
 	github.com/coreos/go-oidc/v3 v3.1.0
 	github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8
-	github.com/cs3org/reva v1.16.1-0.20220112085026-07451f6cd806
+	github.com/cs3org/reva v1.16.1-0.20220118104140-9dd1f7562012
 	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 13b0d7800ed..ee053b6fd7e 100644
--- a/go.sum
+++ b/go.sum
@@ -324,8 +324,8 @@ github.com/crewjam/saml v0.4.5/go.mod h1:qCJQpUtZte9R1ZjUBcW8qtCNlinbO363ooNl02S
 github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4=
 github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8 h1:PqOprF37OvwCbAN5W23znknGk6N/LMayqLAeP904FHE=
 github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
-github.com/cs3org/reva v1.16.1-0.20220112085026-07451f6cd806 h1:kwGCQOlC/x0sz4KZ28n7GgZ+aBlmqbKn8CmbkNK6WU4=
-github.com/cs3org/reva v1.16.1-0.20220112085026-07451f6cd806/go.mod h1:HisUI5sBYaR3kqqtP+O8MzY7XRjqpV8IgxzK4aK0BtI=
+github.com/cs3org/reva v1.16.1-0.20220118104140-9dd1f7562012 h1:cLXBV71Nnug7RMma8BDo9VcGQRUmKYH5JQiu3PaXJNk=
+github.com/cs3org/reva v1.16.1-0.20220118104140-9dd1f7562012/go.mod h1:86uvpL6IYmqoyAWT0DDCooo9Bh4tp5A33SLOLapc7V0=
 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=
@@ -638,7 +638,6 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
-github.com/gomodule/redigo v1.8.6/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
 github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E=
 github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -935,7 +934,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
diff --git a/graph/pkg/service/v0/drives.go b/graph/pkg/service/v0/drives.go
index 2757c8cd982..9b0c1745ada 100644
--- a/graph/pkg/service/v0/drives.go
+++ b/graph/pkg/service/v0/drives.go
@@ -735,3 +735,55 @@ func generateCs3Filters(request *godata.GoDataRequest) ([]*storageprovider.ListS
 	}
 	return filters, nil
 }
+
+func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) {
+	driveID, err := url.PathUnescape(chi.URLParam(r, "driveID"))
+	if err != nil {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping drive id failed")
+		return
+	}
+
+	if driveID == "" {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing drive id")
+		return
+	}
+
+	root := &storageprovider.ResourceId{}
+
+	identifierParts := strings.Split(driveID, "!")
+	switch len(identifierParts) {
+	case 1:
+		root.StorageId, root.OpaqueId = identifierParts[0], identifierParts[0]
+	case 2:
+		root.StorageId, root.OpaqueId = identifierParts[0], identifierParts[1]
+	default:
+		errorcode.GeneralException.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid resource id: %v", driveID))
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	purge := parsePurgeHeader(r.Header)
+
+	var opaque *types.Opaque
+	if purge {
+		opaque = &types.Opaque{
+			Map: map[string]*types.OpaqueEntry{
+				"purge": {},
+			},
+		}
+	}
+
+	dRes, err := g.gatewayClient.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{
+		Opaque: opaque,
+		Id: &storageprovider.StorageSpaceId{
+			OpaqueId: root.StorageId,
+		},
+	})
+	if err != nil || dRes.Status.Code != cs3rpc.Code_CODE_OK {
+		g.logger.Error().Err(err).Msg("error deleting storage space")
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.WriteHeader(http.StatusNoContent)
+}
diff --git a/graph/pkg/service/v0/service.go b/graph/pkg/service/v0/service.go
index 529fec816b3..870ea8786e4 100644
--- a/graph/pkg/service/v0/service.go
+++ b/graph/pkg/service/v0/service.go
@@ -3,6 +3,7 @@ package svc
 import (
 	"crypto/tls"
 	"net/http"
+	"strconv"
 
 	"github.com/ReneKroon/ttlcache/v2"
 	"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
@@ -15,6 +16,11 @@ import (
 	opkgm "github.com/owncloud/ocis/ocis-pkg/middleware"
 )
 
+const (
+	// HeaderPurge defines the header name for the purge header.
+	HeaderPurge = "Purge"
+)
+
 // Service defines the extension handlers.
 type Service interface {
 	ServeHTTP(http.ResponseWriter, *http.Request)
@@ -118,6 +124,7 @@ func NewService(opts ...Option) Service {
 					r.Route("/{driveID}", func(r chi.Router) {
 						r.Patch("/", svc.UpdateDrive)
 						r.Get("/", svc.GetSingleDrive)
+						r.Delete("/", svc.DeleteDrive)
 					})
 				})
 			})
@@ -126,3 +133,15 @@ func NewService(opts ...Option) Service {
 
 	return svc
 }
+
+// parseHeaderPurge parses the 'Purge' header.
+// '1', 't', 'T', 'TRUE', 'true', 'True' are parsed as true
+// all other values are false.
+func parsePurgeHeader(h http.Header) bool {
+	val := h.Get(HeaderPurge)
+
+	if b, err := strconv.ParseBool(val); err == nil {
+		return b
+	}
+	return false
+}
diff --git a/proxy/pkg/middleware/create_home.go b/proxy/pkg/middleware/create_home.go
index 9de555653fe..f85d2c8aa00 100644
--- a/proxy/pkg/middleware/create_home.go
+++ b/proxy/pkg/middleware/create_home.go
@@ -51,7 +51,9 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 		m.logger.Err(err).Msg("error calling CreateHome")
 	} else if createHomeRes.Status.Code != rpc.Code_CODE_OK {
 		err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
-		m.logger.Err(err).Msg("error when calling Createhome")
+		if createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS {
+			m.logger.Err(err).Msg("error when calling Createhome")
+		}
 	}
 
 	m.next.ServeHTTP(w, req)


Test patch:
diff --git a/graph/pkg/service/v0/service_test.go b/graph/pkg/service/v0/service_test.go
new file mode 100644
index 00000000000..8467b706db6
--- /dev/null
+++ b/graph/pkg/service/v0/service_test.go
@@ -0,0 +1,30 @@
+package svc
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestParsePurgeHeader(t *testing.T) {
+	tests := map[string]bool{
+		"":         false,
+		"f":        false,
+		"F":        false,
+		"anything": false,
+		"t":        true,
+		"T":        true,
+	}
+
+	for input, expected := range tests {
+		h := make(http.Header)
+		h.Add(HeaderPurge, input)
+
+		if expected != parsePurgeHeader(h) {
+			t.Errorf("parsePurgeHeader with input %s got %t expected %t", input, !expected, expected)
+		}
+	}
+
+	if h := make(http.Header); parsePurgeHeader(h) {
+		t.Error("parsePurgeHeader without Purge header set got true expected false")
+	}
+}