diff --git a/.drone.star b/.drone.star index 3e13b66bc2..25420c3d34 100644 --- a/.drone.star +++ b/.drone.star @@ -7,6 +7,7 @@ OC_CI_GOLANG = "owncloudci/golang:1.17" OC_CI_NODEJS = "owncloudci/nodejs:14" OC_CI_PHP = "owncloudci/php:7.4" OC_CI_WAIT_FOR = "owncloudci/wait-for:latest" +OC_TESTING_MIDDLEWARE = "owncloud/owncloud-test-middleware:1.2.0" MINIO_MC = "minio/mc:RELEASE.2021-10-07T04-19-58Z" REDIS = "redis:6-alpine" @@ -356,7 +357,12 @@ def uploadScanResults(ctx): "SONAR_PULL_REQUEST_KEY": "%s" % (ctx.build.ref.replace("refs/pull/", "").split("/")[0]), }) - repo_slug = ctx.build.source_repo if ctx.build.source_repo else ctx.repo.slug + fork_handling = [] + if ctx.build.source_repo != "" and ctx.build.source_repo != ctx.repo.slug: + fork_handling = [ + "git remote add fork https://github.com/%s.git" % (ctx.build.source_repo), + "git fetch fork", + ] return { "kind": "pipeline", @@ -374,9 +380,13 @@ def uploadScanResults(ctx): "name": "clone", "image": "alpine/git:latest", "commands": [ - "git clone https://github.com/%s.git ." % (repo_slug), - "git checkout $DRONE_COMMIT", - ], + # Always use the owncloud/ocis repository as base to have an up to date default branch. + # This is needed for the skipIfUnchanged step, since it references a commit on master (which could be absent on a fork) + "git clone https://github.com/%s.git ." % (ctx.repo.slug), + ] + fork_handling + + [ + "git checkout $DRONE_COMMIT", + ], }, ] + skipIfUnchanged(ctx, "unit-tests") + [ { @@ -1443,8 +1453,7 @@ def ocisServer(storage, accounts_hash_difficulty = 4, volumes = []): def middlewareService(): return [{ "name": "middleware", - "image": "owncloud/owncloud-test-middleware", - "pull": "always", + "image": OC_TESTING_MIDDLEWARE, "environment": { "BACKEND_HOST": "https://ocis-server:9200", "OCIS_REVA_DATA_ROOT": "/srv/app/tmp/ocis/storage/owncloud/", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b1eec11f2..456ff5b69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The following sections list the changes for unreleased. * Enhancement - Add spaces capability: [#2931](https://github.com/owncloud/ocis/pull/2931) * Enhancement - Support signature auth in the public share auth middleware: [#2831](https://github.com/owncloud/ocis/pull/2831) * Enhancement - Update REVA to v1.16.1-0.20220112085026-07451f6cd806: [#2953](https://github.com/owncloud/ocis/pull/2953) +* Enhancement - Add endpoint to retrieve a single space: [#2978](https://github.com/owncloud/ocis/pull/2978) * Enhancement - Add filter by driveType and id to /me/drives: [#2946](https://github.com/owncloud/ocis/pull/2946) * Enhancement - Update REVA to xxx: [#2878](https://github.com/owncloud/ocis/pull/2878) * Enhancement - Update ownCloud Web to v4.8.0: [#2895](https://github.com/owncloud/ocis/pull/2895) @@ -107,6 +108,12 @@ The following sections list the changes for unreleased. https://github.com/owncloud/ocis/pull/2953 +* Enhancement - Add endpoint to retrieve a single space: [#2978](https://github.com/owncloud/ocis/pull/2978) + + We added the endpoint ``/drives/{driveID}`` to get a single space by id from the server. + + https://github.com/owncloud/ocis/pull/2978 + * Enhancement - Add filter by driveType and id to /me/drives: [#2946](https://github.com/owncloud/ocis/pull/2946) We added two possible filter terms (driveType, id) to the /me/drives endpoint on the graph api. diff --git a/changelog/unreleased/single-space-enpoint.md b/changelog/unreleased/single-space-enpoint.md new file mode 100644 index 0000000000..c77e6a461a --- /dev/null +++ b/changelog/unreleased/single-space-enpoint.md @@ -0,0 +1,5 @@ +Enhancement: Add endpoint to retrieve a single space + +We added the endpoint ``/drives/{driveID}`` to get a single space by id from the server. + +https://github.com/owncloud/ocis/pull/2978 diff --git a/deployments/examples/ocis_keycloak/docker-compose.yml b/deployments/examples/ocis_keycloak/docker-compose.yml index ec11d218f9..72be457ae4 100644 --- a/deployments/examples/ocis_keycloak/docker-compose.yml +++ b/deployments/examples/ocis_keycloak/docker-compose.yml @@ -63,7 +63,7 @@ services: OCIS_URL: https://${OCIS_DOMAIN:-ocis.owncloud.test} OCIS_LOG_LEVEL: ${OCIS_LOG_LEVEL:-error} # make oCIS less verbose PROXY_TLS: "false" # do not use SSL between Traefik and oCIS - ACCOUNTS_DEMO_USERS_AND_GROUPS: false # don't generate demo users + ACCOUNTS_DEMO_USERS_AND_GROUPS: "false" # don't generate demo users # change default secrets IDP_LDAP_BIND_PASSWORD: ${IDP_LDAP_BIND_PASSWORD:-idp} STORAGE_LDAP_BIND_PASSWORD: ${STORAGE_LDAP_BIND_PASSWORD:-reva} diff --git a/go.mod b/go.mod index f0bb8fd601..3367895a3d 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/google/uuid v1.3.0 github.com/gookit/config/v2 v2.0.27 github.com/gorilla/mux v1.8.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3 github.com/iancoleman/strcase v0.2.0 github.com/justinas/alice v1.2.0 github.com/libregraph/lico v0.53.1 @@ -50,7 +50,7 @@ require ( github.com/onsi/gomega v1.17.0 github.com/owncloud/libre-graph-api-go v0.8.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.11.0 + github.com/prometheus/client_golang v1.12.0 github.com/rs/zerolog v1.26.1 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.3.0 @@ -67,7 +67,7 @@ require ( golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e golang.org/x/image v0.0.0-20211028202545-6944b10bf410 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa + google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 google.golang.org/grpc v1.43.0 google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 @@ -141,7 +141,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gomodule/redigo v1.8.8 // indirect - github.com/google/go-cmp v0.5.6 // indirect + github.com/google/go-cmp v0.5.7 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gookit/goutil v0.4.0 // indirect github.com/gorilla/schema v1.2.0 // indirect @@ -229,7 +229,7 @@ require ( golang.org/x/mod v0.5.1 // indirect golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect golang.org/x/tools v0.1.8 // indirect diff --git a/go.sum b/go.sum index e0c02ed2f6..13b0d7800e 100644 --- a/go.sum +++ b/go.sum @@ -654,8 +654,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -725,8 +726,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2 h1:I/pwhnUln5wbMnTyRbzswA0/JxpK8sZj0aUfI3TV1So= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2/go.mod h1:lsuH8kb4GlMdSlI4alNIBBSAt5CHJtg3i+0WuN9J5YM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3 h1:I8MsauTJQXZ8df8qJvEln0kYNc3bSapuaSsEsnFdEFU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3/go.mod h1:lZdb/YAJUSj9OqrCHs2ihjtoO3+xK3G53wTYXFWRGDo= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= @@ -1117,8 +1118,9 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= +github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1719,8 +1721,9 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1950,8 +1953,9 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q= +google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/graph/pkg/service/v0/drives.go b/graph/pkg/service/v0/drives.go index 0d15adc48d..2757c8cd98 100644 --- a/graph/pkg/service/v0/drives.go +++ b/graph/pkg/service/v0/drives.go @@ -43,46 +43,20 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) { errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) return } - g.logger.Info().Msg("Calling GetDrives") + g.logger.Info().Interface("query", r.URL.Query()).Msg("Calling GetDrives") ctx := r.Context() - client := g.GetGatewayClient() - - permissions := make(map[string]struct{}, 1) - s := sproto.NewPermissionService("com.owncloud.api.settings", grpc.DefaultClient) - - _, err = s.GetPermissionByID(ctx, &sproto.GetPermissionByIDRequest{ - PermissionId: settingsSvc.ListAllSpacesPermissionID, - }) - - // No error means the user has the permission - if err == nil { - permissions[settingsSvc.ListAllSpacesPermissionName] = struct{}{} - } - value, err := json.Marshal(permissions) - if err != nil { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } filters, err := generateCs3Filters(odataReq) if err != nil { g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error") errorcode.NotSupported.Render(w, r, http.StatusNotImplemented, err.Error()) return } - res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{ - Opaque: &types.Opaque{Map: map[string]*types.OpaqueEntry{ - "permissions": { - Decoder: "json", - Value: value, - }, - }}, - Filters: filters, - }) + res, err := g.ListStorageSpacesWithFilters(ctx, filters) switch { case err != nil: - g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + g.logger.Error().Err(err).Msg(ListStorageSpacesTransportErr) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return case res.Status.Code != cs3rpc.Code_CODE_OK: if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { @@ -91,7 +65,7 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &listResponse{}) return } - g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request") + g.logger.Error().Err(err).Msg(ListStorageSpacesReturnsErr) errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) return } @@ -113,6 +87,75 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &listResponse{Value: files}) } +// GetSingleDrive does a lookup of a single space by spaceId +func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) { + driveID := chi.URLParam(r, "driveID") + if driveID == "" { + err := fmt.Errorf("no valid space id retrieved") + g.logger.Err(err) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + g.logger.Info().Str("driveID", driveID).Msg("Calling GetSingleDrive") + ctx := r.Context() + + filters := []*storageprovider.ListStorageSpacesRequest_Filter{ + { + Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID, + Term: &storageprovider.ListStorageSpacesRequest_Filter_Id{ + Id: &storageprovider.StorageSpaceId{ + OpaqueId: driveID, + }, + }, + }, + } + res, err := g.ListStorageSpacesWithFilters(ctx, filters) + switch { + case err != nil: + g.logger.Error().Err(err).Msg(ListStorageSpacesTransportErr) + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + // the client is doing a lookup for a specific space, therefore we need to return + // not found to the caller + g.logger.Error().Str("driveID", driveID).Msg(fmt.Sprintf(NoSpaceFoundMessage, driveID)) + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, fmt.Sprintf(NoSpaceFoundMessage, driveID)) + return + } + g.logger.Error().Err(err).Msg(ListStorageSpacesReturnsErr) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) + 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 + } + spaces, err := g.formatDrives(ctx, wdu, res.StorageSpaces) + if err != nil { + g.logger.Error().Err(err).Msg("error encoding response") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + switch num := len(spaces); { + case num == 0: + g.logger.Error().Str("driveID", driveID).Msg("no space found") + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, fmt.Sprintf(NoSpaceFoundMessage, driveID)) + return + case num == 1: + render.Status(r, http.StatusOK) + render.JSON(w, r, spaces[0]) + default: + g.logger.Error().Int("number", num).Msg("expected to find a single space but found more") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "expected to find a single space but found more") + return + } +} + // CreateDrive creates a storage drive (space). func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) { us, ok := ctxpkg.ContextGetUser(r.Context()) @@ -340,6 +383,38 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, mds []*storag return responses, nil } +// ListStorageSpacesWithFilters List Storage Spaces using filters +func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*storageprovider.ListStorageSpacesRequest_Filter) (*storageprovider.ListStorageSpacesResponse, error) { + client := g.GetGatewayClient() + + permissions := make(map[string]struct{}, 1) + s := sproto.NewPermissionService("com.owncloud.api.settings", grpc.DefaultClient) + + _, err := s.GetPermissionByID(ctx, &sproto.GetPermissionByIDRequest{ + PermissionId: settingsSvc.ListAllSpacesPermissionID, + }) + + // No error means the user has the permission + if err == nil { + permissions[settingsSvc.ListAllSpacesPermissionName] = struct{}{} + } + value, err := json.Marshal(permissions) + if err != nil { + return nil, err + } + + res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{ + Opaque: &types.Opaque{Map: map[string]*types.OpaqueEntry{ + "permissions": { + Decoder: "json", + Value: value, + }, + }}, + Filters: filters, + }) + return res, err +} + func cs3StorageSpaceToDrive(baseURL *url.URL, space *storageprovider.StorageSpace) (*libregraph.Drive, error) { rootID := space.Root.StorageId + "!" + space.Root.OpaqueId if space.Root.StorageId == space.Root.OpaqueId { diff --git a/graph/pkg/service/v0/graph.go b/graph/pkg/service/v0/graph.go index 3523dd8fb5..8117859194 100644 --- a/graph/pkg/service/v0/graph.go +++ b/graph/pkg/service/v0/graph.go @@ -85,3 +85,9 @@ func (g Graph) GetHTTPClient() HTTPClient { type listResponse struct { Value interface{} `json:"value,omitempty"` } + +const ( + NoSpaceFoundMessage = "space with id `%s` not found" + ListStorageSpacesTransportErr = "transport error sending list storage spaces grpc request" + ListStorageSpacesReturnsErr = "list storage spaces grpc request returns an errorcode in the response" +) diff --git a/graph/pkg/service/v0/service.go b/graph/pkg/service/v0/service.go index 5d62e3cdb7..529fec816b 100644 --- a/graph/pkg/service/v0/service.go +++ b/graph/pkg/service/v0/service.go @@ -117,6 +117,7 @@ func NewService(opts ...Option) Service { r.Post("/", svc.CreateDrive) r.Route("/{driveID}", func(r chi.Router) { r.Patch("/", svc.UpdateDrive) + r.Get("/", svc.GetSingleDrive) }) }) }) diff --git a/tests/acceptance/features/apiSpaces/listSpaces.feature b/tests/acceptance/features/apiSpaces/listSpaces.feature index 175c562cc7..ac3afc65e9 100644 --- a/tests/acceptance/features/apiSpaces/listSpaces.feature +++ b/tests/acceptance/features/apiSpaces/listSpaces.feature @@ -79,3 +79,35 @@ Feature: List and create spaces | name | Project Venus | | quota@@@total | 2000 | | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% | + + Scenario: A user can list his personal space via multiple endpoints + When user "Alice" lists all available spaces via the GraphApi with query "$filter=driveType eq 'personal'" + Then the json responded should contain a space "Alice Hansen" with these key and value pairs: + | key | value | + | driveType | personal | + | name | Alice Hansen | + | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% | + When user "Alice" looks up the single space "Alice Hansen" via the GraphApi by using its id + Then the json responded should contain a space "Alice Hansen" with these key and value pairs: + | key | value | + | driveType | personal | + | name | Alice Hansen | + | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% | + + Scenario: A user can list his created spaces via multiple endpoints + Given the administrator has given "Alice" the role "Admin" using the settings api + When user "Alice" creates a space "Project Venus" of type "project" with quota "2000" using the GraphApi + Then the HTTP status code should be "201" + And the json responded should contain a space "Project Venus" with these key and value pairs: + | key | value | + | driveType | project | + | name | Project Venus | + | quota@@@total | 2000 | + | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% | + When user "Alice" looks up the single space "Project Venus" via the GraphApi by using its id + Then the json responded should contain a space "Project Venus" with these key and value pairs: + | key | value | + | driveType | project | + | name | Project Venus | + | quota@@@total | 2000 | + | root@@@webDavUrl | %base_url%/dav/spaces/%space_id% | diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php index c5d5a220ae..b0e03b2ef8 100644 --- a/tests/acceptance/features/bootstrap/SpacesContext.php +++ b/tests/acceptance/features/bootstrap/SpacesContext.php @@ -232,7 +232,7 @@ class SpacesContext implements Context { } /** - * Send Graph List Spaces Request + * Send Graph List My Spaces Request * * @param string $user * @param string $password @@ -245,7 +245,7 @@ class SpacesContext implements Context { * * @throws GuzzleException */ - public function listSpacesRequest( + public function listMySpacesRequest( string $user, string $password, string $urlArguments = '', @@ -258,6 +258,34 @@ class SpacesContext implements Context { return HttpRequestHelper::get($fullUrl, $xRequestId, $user, $password, $headers, $body); } + /** + * Send Graph List Single Space Request + * + * @param string $user + * @param string $password + * @param string $urlArguments + * @param string $xRequestId + * @param array $body + * @param array $headers + * + * @return ResponseInterface + * + * @throws GuzzleException + */ + public function listSingleSpaceRequest( + string $user, + string $password, + string $spaceId, + string $urlArguments = '', + string $xRequestId = '', + array $body = [], + array $headers = [] + ): ResponseInterface { + $fullUrl = $this->baseUrl . "/graph/v1.0/drives/" . $spaceId . "/" . $urlArguments; + + return HttpRequestHelper::get($fullUrl, $xRequestId, $user, $password, $headers, $body); + } + /** * Send Graph Create Space Request * @@ -342,7 +370,7 @@ class SpacesContext implements Context { */ public function theUserListsAllHisAvailableSpacesUsingTheGraphApi(string $user): void { $this->featureContext->setResponse( - $this->listSpacesRequest( + $this->listMySpacesRequest( $user, $this->featureContext->getPasswordForUser($user) ) @@ -362,7 +390,7 @@ class SpacesContext implements Context { */ public function theUserListsAllHisAvailableSpacesUsingTheGraphApiWithFilter(string $user, string $query): void { $this->featureContext->setResponse( - $this->listSpacesRequest( + $this->listMySpacesRequest( $user, $this->featureContext->getPasswordForUser($user), "?". $query @@ -370,6 +398,30 @@ class SpacesContext implements Context { ); } + /** + * @When /^user "([^"]*)" looks up the single space "([^"]*)" via the GraphApi by using its id$/ + * + * @param string $user + * @param string $query + * + * @return void + * + * @throws GuzzleException + */ + public function theUserLooksUpTheSingleSpaceUsingTheGraphApiByUsingItsId(string $user, string $spaceName): void { + $space = $this->getSpaceByName($user, $spaceName); + Assert::assertIsArray($space); + Assert::assertNotEmpty($spaceId = $space["id"]); + Assert::assertNotEmpty($spaceWebDavUrl = $space["root"]["webDavUrl"]); + $this->featureContext->setResponse( + $this->listSingleSpaceRequest( + $user, + $this->featureContext->getPasswordForUser($user), + $spaceId + ) + ); + } + /** * @When /^user "([^"]*)" creates a space "([^"]*)" of type "([^"]*)" with the default quota using the GraphApi$/ *