Merge pull request #2869 from owncloud/graph-api-rest

Graph api rest
This commit is contained in:
Michael Barz
2021-12-13 19:59:01 +01:00
committed by GitHub
9 changed files with 58 additions and 43 deletions

View File

@@ -0,0 +1,5 @@
Change: Return not found when updating non existent space
If a spaceid of a space which is updated doesn't exist, handle it as a not found error.
https://github.com/cs3org/reva/pull/2869

View File

@@ -47,16 +47,16 @@ This is an extract of an element of the list spaces response. An entire object h
Having introduced the above, one can refer to a Drive with the following URL format:
```console
'https://localhost:9200/graph/v1.0/Drive(1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570")
'https://localhost:9200/graph/v1.0/drives/1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570
```
Updating an entity attribute:
```console
curl -X PATCH 'https://localhost:9200/graph/v1.0/Drive("1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570)' -d '{"name":"42"}' -v
curl -X PATCH 'https://localhost:9200/graph/v1.0/drives/1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570' -d '{"name":"42"}' -v
```
The previous URL resource path segment (`Drive(1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570)`) is parsed and handed over to the storage registry in order to apply the patch changes in the body, in this case update the space name attribute to `42`. Since space names are not unique we only support addressing them by their unique identifiers, any other query would render too ambiguous and explode in complexity.
The previous URL resource path segment (`1284d238-aa92-42ce-bdc4-0b0000009157!07c26b3a-9944-4f2b-ab33-b0b326fc7570`) is parsed and handed over to the storage registry in order to apply the patch changes in the body, in this case update the space name attribute to `42`. Since space names are not unique we only support addressing them by their unique identifiers, any other query would render too ambiguous and explode in complexity.
### Updating a space description

View File

@@ -45,7 +45,7 @@ This the DRAFT for the API.
ownCloud servers provide an API to query for available spaces of an user.
See the openAPI Specification for the [open Graph API](https://owncloud.dev/open-graph-api/).
See the openAPI Specification for the [Libre Graph API](https://owncloud.dev/libre-graph-api/).
Most important, the API returns the WebDAV endpoint for each space. With that, clients do not have to make assumptions about WebDAV routes any more.
@@ -80,8 +80,7 @@ The reply to both calls is either one or a list of [Drive representation objects
"owner": { "@odata.type": "microsoft.graph.identitySet" },
"quota": { "@odata.type": "microsoft.graph.quota" },
"root": { "@odata.type": "microsoft.graph.driveItem" },
"webUrl": "url",
"ocCoOwner": [ { "@odata.type": "microsoft.graph.identitySet" } ]
"webUrl": "url"
}
```
@@ -93,7 +92,6 @@ The meaning of the objects in Open Graph API context are:
4. **quota** - quota information about this space
5. **root** - the root driveItem object.
6. **webUrl** - The URL to make this space visible in the browser.
7. **ocCoOwner** - optional array owner objects of the co-owners of a space (*)
The following *driveType* values are available in the first step, but might be enhanced later:

2
go.mod
View File

@@ -19,7 +19,7 @@ require (
github.com/blevesearch/bleve/v2 v2.2.2
github.com/coreos/go-oidc/v3 v3.1.0
github.com/cs3org/go-cs3apis v0.0.0-20211104090126-8e972dca8304
github.com/cs3org/reva v1.17.0
github.com/cs3org/reva v1.17.1-0.20211212151213-778de37266ff
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

4
go.sum
View File

@@ -299,8 +299,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-20211104090126-8e972dca8304 h1:e/nIPR518vyvrulo9goAZTtYD6gFfu/2/9MDe6mTGcw=
github.com/cs3org/go-cs3apis v0.0.0-20211104090126-8e972dca8304/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva v1.17.0 h1:UVdiiK6gUF2pb7qN9TKhMuf55sUKVnCcVjiVSzodluw=
github.com/cs3org/reva v1.17.0/go.mod h1:gtsVzMfDrUiUjH6qlHx+QqiRKsSYjVO6wEcCzANiqUg=
github.com/cs3org/reva v1.17.1-0.20211212151213-778de37266ff h1:IUj5GfxDa8WgNzeH+5MnJREzjoneejygmnMgU2hAWCg=
github.com/cs3org/reva v1.17.1-0.20211212151213-778de37266ff/go.mod h1:gtsVzMfDrUiUjH6qlHx+QqiRKsSYjVO6wEcCzANiqUg=
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=

View File

@@ -12,7 +12,6 @@ import (
"strings"
"time"
"github.com/CiscoM31/godata"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
@@ -21,6 +20,7 @@ import (
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
@@ -240,7 +240,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
newDrive, err := cs3StorageSpaceToDrive(wdu, resp.StorageSpace)
if err != nil {
g.logger.Error().Err(err).Msg("error parsing url")
g.logger.Error().Err(err).Msg("error parsing space")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
@@ -249,22 +249,14 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
}
func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
// wildcards however addressed here is not yet supported. We want to address drives by their unique
// identifiers. Any open queries need to be implemented. Same applies for sub-entities.
// For further reading: http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_AddressingaSubsetofaCollection
// strip "/graph/v1.0/" out and parse the rest. This is how godata input is expected.
//https://github.com/CiscoM31/godata/blob/d70e191d2908191623be84401fecc40d6af4afde/url_parser_test.go#L10
sanitized := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
req, err := godata.ParseRequest(r.Context(), sanitized, r.URL.Query())
driveID, err := url.PathUnescape(chi.URLParam(r, "driveID"))
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping drive id failed")
return
}
if req.FirstSegment.Identifier.Get() == "" {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, "identifier cannot be empty")
if driveID == "" {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing drive id")
return
}
@@ -274,9 +266,9 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
return
}
identifierParts := strings.Split(req.FirstSegment.Identifier.Get(), "!")
identifierParts := strings.Split(driveID, "!")
if len(identifierParts) != 2 {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid resource id: %v", req.FirstSegment.Identifier.Get()))
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid resource id: %v", driveID))
w.WriteHeader(http.StatusInternalServerError)
return
}
@@ -294,7 +286,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
// the original storage space.
StorageSpace: &provider.StorageSpace{
Id: &storageprovider.StorageSpaceId{
OpaqueId: req.FirstSegment.Identifier.Get(),
OpaqueId: driveID,
},
Root: &provider.ResourceId{
StorageId: storageID,
@@ -330,11 +322,32 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
}
if resp.GetStatus().GetCode() != v1beta11.Code_CODE_OK {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "")
switch resp.Status.GetCode() {
case v1beta11.Code_CODE_NOT_FOUND:
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, resp.GetStatus().GetMessage())
return
default:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, resp.GetStatus().GetMessage())
return
}
}
wdu, err := url.Parse(g.config.Spaces.WebDavBase + g.config.Spaces.WebDavPath)
if err != nil {
g.logger.Error().Err(err).Msg("error parsing url")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
updatedDrive, err := cs3StorageSpaceToDrive(wdu, resp.StorageSpace)
if err != nil {
g.logger.Error().Err(err).Msg("error parsing space")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, updatedDrive)
}
func cs3TimestampToTime(t *types.Timestamp) time.Time {

View File

@@ -85,9 +85,9 @@ func NewService(opts ...Option) Service {
r.Route("/drives", func(r chi.Router) {
r.Get("/", svc.GetDrives)
r.Post("/", svc.CreateDrive)
})
r.Route("/Drive({firstSegmentIdentifier})", func(r chi.Router) {
r.Patch("/*", svc.UpdateDrive)
r.Route("/{driveID}", func(r chi.Router) {
r.Patch("/", svc.UpdateDrive)
})
})
})
})

View File

@@ -11,9 +11,9 @@ Feature: Change data of space
And the administrator has given "Alice" the role "Admin" using the settings api
Scenario: Alice changes a name of the space via the Graph api, she expects a 204 code and checks that the space name has changed
Given user "Alice" has created a space "Project Jupiter" of type "project" with quota "20"
Given user "Alice" has created a space "Project Jupiter" of type "project" with quota "20"
When user "Alice" changes the name of the "Project Jupiter" space to "Project Death Star"
Then the HTTP status code should be "204"
Then the HTTP status code should be "200"
When user "Alice" lists all available spaces via the GraphApi
Then the json responded should contain a space "Project Death Star" with these key and value pairs:
| key | value |
@@ -23,12 +23,11 @@ Feature: Change data of space
| root@@@webDavUrl | %base_url%/dav/spaces/%space_id% |
Scenario: Alice increases quota of the space via the Graph api, she expects a 204 code and checks that the quota has changed
Given user "Alice" has created a space "Project Earth" of type "project" with quota "20"
Given user "Alice" has created a space "Project Earth" of type "project" with quota "20"
When user "Alice" changes the quota of the "Project Earth" space to "100"
Then the HTTP status code should be "204"
Then the HTTP status code should be "200"
When user "Alice" lists all available spaces via the GraphApi
Then the json responded should contain a space "Project Earth" with these key and value pairs:
| key | value |
| name | Project Earth |
| quota@@@total | 100 |

View File

@@ -187,11 +187,11 @@ class SpacesContext implements Context {
*/
public function getSpaceByName(string $user, string $name): array {
$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
$spaces = $this->getAvailableSpaces();
Assert::assertIsArray($spaces[$name], "Space with name $name for user $user not found");
Assert::assertNotEmpty($spaces[$name]["root"]["webDavUrl"], "WebDavUrl for space with name $name for user $user not found");
return $spaces[$name];
}
@@ -802,7 +802,7 @@ class SpacesContext implements Context {
string $spaceName
): void {
$space = $this->getSpaceByName($user, $spaceName);
$baseUrl = $this->featureContext->getBaseUrl();
if (!str_ends_with($baseUrl, '/')) {
$baseUrl .= '/';
@@ -838,7 +838,7 @@ class SpacesContext implements Context {
string $ownerUser
): void {
$space = $this->getSpaceByName($ownerUser, $spaceName);
$baseUrl = $this->featureContext->getBaseUrl();
if (!str_ends_with($baseUrl, '/')) {
$baseUrl .= '/';
@@ -1044,7 +1044,7 @@ class SpacesContext implements Context {
if (!str_ends_with($fullUrl, '/')) {
$fullUrl .= '/';
}
$fullUrl .= "graph/v1.0/Drive($spaceId)";
$fullUrl .= "graph/v1.0/drives/$spaceId";
$method = 'PATCH';
return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers, $body);
@@ -1090,7 +1090,7 @@ class SpacesContext implements Context {
*/
public function userHasUploadedFile(string $user, string $spaceName, string $fileContent, string $destination):void {
$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
$space = $this->getSpaceByName($user, $spaceName);
Assert::assertIsArray($space, "Space with name $spaceName not found");
Assert::assertNotEmpty($space["root"]["webDavUrl"], "WebDavUrl for space with name $spaceName not found");