feature: add beta drive listing endpoints to the graph api (#7861)
* feature: add beta drive listing endpoints to the graph api and hydrate them to contain the new grantedtoV2 property and use unified roles instead of the cs3 roles * enhancement: make use of owner conditions for drive listing * enhancement: provide GetDrivesV1Beta1 and GetAllDrivesV1Beta1 graph endpoint tests
This commit is contained in:
@@ -121,8 +121,9 @@ func (e Error) Render(w http.ResponseWriter, r *http.Request) {
|
||||
switch e.errorCode {
|
||||
case AccessDenied:
|
||||
status = http.StatusForbidden
|
||||
case
|
||||
InvalidRange:
|
||||
case NotSupported:
|
||||
status = http.StatusNotImplemented
|
||||
case InvalidRange:
|
||||
status = http.StatusRequestedRangeNotSatisfiable
|
||||
case InvalidRequest:
|
||||
status = http.StatusBadRequest
|
||||
|
||||
@@ -25,13 +25,16 @@ import (
|
||||
merrors "go-micro.dev/v4/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
revaConversions "github.com/cs3org/reva/v2/pkg/conversions"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
|
||||
v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
|
||||
settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
|
||||
)
|
||||
|
||||
@@ -57,19 +60,175 @@ var (
|
||||
ErrForbiddenCharacter = fmt.Errorf("spacenames must not contain %v", _invalidSpaceNameCharacters)
|
||||
)
|
||||
|
||||
// GetDrives lists all drives the current user has access to
|
||||
func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
g.getDrives(w, r, false)
|
||||
// GetDrives serves as a factory method that returns the appropriate
|
||||
// http.Handler function based on the specified API version.
|
||||
func (g Graph) GetDrives(version APIVersion) http.HandlerFunc {
|
||||
switch version {
|
||||
case APIVersion_1:
|
||||
return g.GetDrivesV1
|
||||
case APIVersion_1_Beta_1:
|
||||
return g.GetDrivesV1Beta1
|
||||
default:
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
errorcode.New(errorcode.NotSupported, "api version not supported").Render(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllDrives lists all drives, including other user's drives, if the current
|
||||
// user has the permission.
|
||||
func (g Graph) GetAllDrives(w http.ResponseWriter, r *http.Request) {
|
||||
g.getDrives(w, r, true)
|
||||
// GetDrivesV1 attempts to retrieve the current users drives;
|
||||
// it lists all drives the current user has access to.
|
||||
func (g Graph) GetDrivesV1(w http.ResponseWriter, r *http.Request) {
|
||||
spaces, errCode := g.getDrives(r, false)
|
||||
if errCode != nil {
|
||||
errCode.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
|
||||
switch {
|
||||
case spaces == nil && errCode == nil:
|
||||
render.JSON(w, r, nil)
|
||||
default:
|
||||
render.JSON(w, r, &ListResponse{Value: spaces})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GetDrivesV1Beta1 is the same as the GetDrivesV1 endpoint, expect:
|
||||
// it includes the grantedtoV2 property
|
||||
// it uses unified roles instead of the cs3 representations
|
||||
func (g Graph) GetDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
|
||||
spaces, errCode := g.getDrivesBeta(r, false)
|
||||
if errCode != nil {
|
||||
errCode.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
|
||||
switch {
|
||||
case spaces == nil && errCode == nil:
|
||||
render.JSON(w, r, nil)
|
||||
default:
|
||||
render.JSON(w, r, &ListResponse{Value: spaces})
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllDrives serves as a factory method that returns the appropriate
|
||||
// http.Handler function based on the specified API version.
|
||||
func (g Graph) GetAllDrives(version APIVersion) http.HandlerFunc {
|
||||
switch version {
|
||||
case APIVersion_1:
|
||||
return g.GetAllDrivesV1
|
||||
case APIVersion_1_Beta_1:
|
||||
return g.GetAllDrivesV1Beta1
|
||||
default:
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
errorcode.New(errorcode.NotSupported, "api version not supported").Render(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllDrivesV1 attempts to retrieve the current users drives;
|
||||
// it includes another user's drives, if the current user has the permission.
|
||||
func (g Graph) GetAllDrivesV1(w http.ResponseWriter, r *http.Request) {
|
||||
spaces, errCode := g.getDrives(r, true)
|
||||
if errCode != nil {
|
||||
errCode.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
|
||||
switch {
|
||||
case spaces == nil && errCode == nil:
|
||||
render.JSON(w, r, nil)
|
||||
default:
|
||||
render.JSON(w, r, &ListResponse{Value: spaces})
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllDrivesV1Beta1 is the same as the GetAllDrivesV1 endpoint, expect:
|
||||
// it includes the grantedtoV2 property
|
||||
// it uses unified roles instead of the cs3 representations
|
||||
func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
|
||||
drives, errCode := g.getDrivesBeta(r, true)
|
||||
if errCode != nil {
|
||||
errCode.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
|
||||
switch {
|
||||
case drives == nil && errCode == nil:
|
||||
render.JSON(w, r, nil)
|
||||
default:
|
||||
render.JSON(w, r, &ListResponse{Value: drives})
|
||||
}
|
||||
}
|
||||
|
||||
// getDrivesBeta retrieves the drives associated with the given request 'r'.
|
||||
// It updates the 'GrantedToIdentities' to 'GrantedToV2',
|
||||
// which represents the transition from legacy identity representation to a newer version.
|
||||
// It also maps the old role names to their new unified role identifiers.
|
||||
func (g Graph) getDrivesBeta(r *http.Request, unrestricted bool) ([]*libregraph.Drive, *errorcode.Error) {
|
||||
drives, errCode := g.getDrives(r, unrestricted)
|
||||
if errCode != nil {
|
||||
return nil, errCode
|
||||
}
|
||||
|
||||
for _, drive := range drives {
|
||||
for i, permission := range drive.GetRoot().Permissions {
|
||||
grantedToIdentities := permission.GetGrantedToIdentities()
|
||||
|
||||
if len(grantedToIdentities) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
permission.GrantedToIdentities = nil
|
||||
grantedToIdentity := grantedToIdentities[0]
|
||||
|
||||
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
|
||||
User: grantedToIdentity.User,
|
||||
Group: grantedToIdentity.Group,
|
||||
}
|
||||
|
||||
for i, role := range permission.GetRoles() {
|
||||
|
||||
// v1 implementation for getDrives > ** > cs3PermissionsToLibreGraph
|
||||
// does not use space related role names, we first need to resolve the correct descriptor.
|
||||
switch role {
|
||||
case revaConversions.RoleViewer:
|
||||
role = revaConversions.RoleSpaceViewer
|
||||
case revaConversions.RoleEditor:
|
||||
role = revaConversions.RoleSpaceEditor
|
||||
}
|
||||
|
||||
cs3Role := revaConversions.RoleFromName(role, g.config.FilesSharing.EnableResharing)
|
||||
uniRole := unifiedrole.CS3ResourcePermissionsToUnifiedRole(
|
||||
*cs3Role.CS3ResourcePermissions(),
|
||||
unifiedrole.UnifiedRoleConditionOwner,
|
||||
g.config.FilesSharing.EnableResharing,
|
||||
)
|
||||
|
||||
if uniRole == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
permission.Roles[i] = uniRole.GetId()
|
||||
}
|
||||
|
||||
drive.Root.Permissions[i] = permission
|
||||
}
|
||||
}
|
||||
|
||||
return drives, nil
|
||||
}
|
||||
|
||||
// getDrives implements the Service interface.
|
||||
func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bool) {
|
||||
func (g Graph) getDrives(r *http.Request, unrestricted bool) ([]*libregraph.Drive, *errorcode.Error) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().
|
||||
Interface("query", r.URL.Query()).
|
||||
@@ -80,23 +239,20 @@ func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bo
|
||||
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: query error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, err.Error()))
|
||||
}
|
||||
ctx := r.Context()
|
||||
|
||||
filters, err := generateCs3Filters(odataReq)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: error parsing filters")
|
||||
errorcode.NotSupported.Render(w, r, http.StatusNotImplemented, err.Error())
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.NotSupported, err.Error()))
|
||||
}
|
||||
if !unrestricted {
|
||||
user, ok := revactx.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
logger.Debug().Msg("could not create drive: invalid user")
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusUnauthorized, "invalid user")
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.AccessDenied, "invalid user"))
|
||||
}
|
||||
filters = append(filters, &storageprovider.ListStorageSpacesRequest_Filter{
|
||||
Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_USER,
|
||||
@@ -114,43 +270,35 @@ func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bo
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.Error().Err(err).Msg("could not get drives: transport error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
|
||||
case res.Status.Code != cs3rpc.Code_CODE_OK:
|
||||
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
|
||||
// return an empty list
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{})
|
||||
return
|
||||
// ok, empty return
|
||||
return nil, nil
|
||||
}
|
||||
logger.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, res.Status.Message))
|
||||
}
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drives: error parsing url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("could not get drives: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error()))
|
||||
}
|
||||
|
||||
spaces, err = sortSpaces(odataReq, spaces)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
return nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, err.Error()))
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{Value: spaces})
|
||||
return spaces, nil
|
||||
}
|
||||
|
||||
// GetSingleDrive does a lookup of a single space by spaceId
|
||||
|
||||
@@ -10,9 +10,6 @@ import (
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
"go-micro.dev/v4/client"
|
||||
@@ -20,6 +17,10 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/keycloak"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
|
||||
@@ -123,6 +124,19 @@ const (
|
||||
SpaceImageSpecialFolderName = "image"
|
||||
)
|
||||
|
||||
type APIVersion int
|
||||
|
||||
const (
|
||||
// APIVersion_1 represents the first version of the API.
|
||||
APIVersion_1 APIVersion = iota + 1
|
||||
|
||||
// APIVersion_1_Beta_1 refers to the beta version of the API.
|
||||
// It is typically used for testing purposes and may have more
|
||||
// inconsistencies and bugs than the stable version as it is
|
||||
// still in the testing phase, use it with caution.
|
||||
APIVersion_1_Beta_1
|
||||
)
|
||||
|
||||
// TODO might be different for /education/users vs /users
|
||||
func (g Graph) parseMemberRef(ref string) (string, string, error) {
|
||||
memberURL, err := url.ParseRequestURI(ref)
|
||||
|
||||
@@ -14,15 +14,20 @@ import (
|
||||
userprovider "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/tidwall/gjson"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/go-chi/chi/v5"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
@@ -31,9 +36,7 @@ import (
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
||||
service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"google.golang.org/grpc"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
|
||||
)
|
||||
|
||||
var _ = Describe("Graph", func() {
|
||||
@@ -91,7 +94,7 @@ var _ = Describe("Graph", func() {
|
||||
})
|
||||
|
||||
Describe("Drives", func() {
|
||||
Describe("List drives", func() {
|
||||
Describe("GetDrivesV1 and GetAllDrivesV1", func() {
|
||||
It("can list an empty list of spaces", func() {
|
||||
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
@@ -101,7 +104,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
@@ -114,7 +117,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetAllDrives(rr, r)
|
||||
svc.GetAllDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
@@ -144,7 +147,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
@@ -218,7 +221,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?$orderby=name%20asc", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
@@ -309,7 +312,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
@@ -350,7 +353,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?$orderby=owner%20asc", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
|
||||
body, _ := io.ReadAll(rr.Body)
|
||||
@@ -363,7 +366,7 @@ var _ = Describe("Graph", func() {
|
||||
It("can list a spaces with invalid query parameter", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?§orderby=owner%20asc", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
|
||||
body, _ := io.ReadAll(rr.Body)
|
||||
@@ -376,7 +379,7 @@ var _ = Describe("Graph", func() {
|
||||
It("can list a spaces with an unsupported operand", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?$filter=contains(driveType,personal)", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusNotImplemented))
|
||||
|
||||
body, _ := io.ReadAll(rr.Body)
|
||||
@@ -392,7 +395,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives)", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
|
||||
|
||||
body, _ := io.ReadAll(rr.Body)
|
||||
@@ -410,7 +413,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives)", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
|
||||
|
||||
body, _ := io.ReadAll(rr.Body)
|
||||
@@ -428,7 +431,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives)", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
body, _ := io.ReadAll(rr.Body)
|
||||
@@ -464,7 +467,7 @@ var _ = Describe("Graph", func() {
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrives(rr, r)
|
||||
svc.GetDrivesV1(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
|
||||
|
||||
@@ -476,6 +479,58 @@ var _ = Describe("Graph", func() {
|
||||
Expect(libreError.Error.Code).To(Equal(errorcode.GeneralException.String()))
|
||||
})
|
||||
})
|
||||
DescribeTable("GetDrivesV1Beta1 and GetAllDrivesV1Beta1",
|
||||
func(check func(gjson.Result), resourcePermissions provider.ResourcePermissions) {
|
||||
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Times(1).Return(&provider.ListStorageSpacesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
StorageSpaces: []*provider.StorageSpace{
|
||||
{
|
||||
Opaque: utils.AppendJSONToOpaque(nil, "grants", map[string]provider.ResourcePermissions{
|
||||
"1": resourcePermissions,
|
||||
}),
|
||||
Root: &provider.ResourceId{},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gateway.InitiateFileDownloadResponse{
|
||||
Status: status.NewNotFound(ctx, "not found"),
|
||||
}, nil)
|
||||
gatewayClient.On("GetQuota", mock.Anything, mock.Anything).Return(&provider.GetQuotaResponse{
|
||||
Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"),
|
||||
}, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userprovider.GetUserResponse{
|
||||
Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"),
|
||||
}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrivesV1Beta1(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
jsonData := gjson.Get(rr.Body.String(), "value")
|
||||
|
||||
Expect(jsonData.Get("#").Num).To(Equal(float64(1)))
|
||||
Expect(jsonData.Get("0.root.permissions.#").Num).To(Equal(float64(1)))
|
||||
Expect(jsonData.Get("0.root.permissions.0.grantedToIdentities").Exists()).To(BeFalse())
|
||||
Expect(jsonData.Get("0.root.permissions.0.grantedToIdentities").Exists()).To(BeFalse())
|
||||
Expect(jsonData.Get("0.root.permissions.0.grantedToV2.user.id").Str).To(Equal("1"))
|
||||
Expect(jsonData.Get("0.root.permissions.0.roles.#").Num).To(Equal(float64(1)))
|
||||
|
||||
check(jsonData)
|
||||
},
|
||||
Entry("injects grantedToV2", func(jsonData gjson.Result) {}, provider.ResourcePermissions{RemoveGrant: true}),
|
||||
Entry("remaps manager role to the unified counterpart", func(jsonData gjson.Result) {
|
||||
Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleManagerID))
|
||||
}, provider.ResourcePermissions{RemoveGrant: true}),
|
||||
Entry("remaps editor role to the unified counterpart", func(jsonData gjson.Result) {
|
||||
Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleSpaceEditorID))
|
||||
}, provider.ResourcePermissions{InitiateFileUpload: true}),
|
||||
Entry("remaps viewer role to the unified counterpart", func(jsonData gjson.Result) {
|
||||
Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleSpaceViewerID))
|
||||
}, provider.ResourcePermissions{Stat: true}),
|
||||
)
|
||||
Describe("Create Drive", func() {
|
||||
It("cannot create a space without valid user in context", func() {
|
||||
jsonBody := []byte(`{"Name": "Test Space"}`)
|
||||
|
||||
@@ -94,9 +94,11 @@ type Service interface {
|
||||
PostEducationClassTeacher(w http.ResponseWriter, r *http.Request)
|
||||
DeleteEducationClassTeacher(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
GetDrives(w http.ResponseWriter, r *http.Request)
|
||||
GetDrivesV1(w http.ResponseWriter, r *http.Request)
|
||||
GetDrivesV1Beta1(w http.ResponseWriter, r *http.Request)
|
||||
GetSingleDrive(w http.ResponseWriter, r *http.Request)
|
||||
GetAllDrives(w http.ResponseWriter, r *http.Request)
|
||||
GetAllDrivesV1(w http.ResponseWriter, r *http.Request)
|
||||
GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request)
|
||||
CreateDrive(w http.ResponseWriter, r *http.Request)
|
||||
UpdateDrive(w http.ResponseWriter, r *http.Request)
|
||||
DeleteDrive(w http.ResponseWriter, r *http.Request)
|
||||
@@ -201,14 +203,24 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
r.Use(middleware.StripSlashes)
|
||||
r.Route("/v1beta1", func(r chi.Router) {
|
||||
r.Get("/me/drive/sharedByMe", svc.GetSharedByMe)
|
||||
r.Get("/me/drive/sharedWithMe", svc.ListSharedWithMe)
|
||||
r.Route("/drives/{driveID}/items/{itemID}", func(r chi.Router) {
|
||||
r.Post("/invite", svc.Invite)
|
||||
r.Get("/permissions", svc.ListPermissions)
|
||||
r.Delete("/permissions/{permissionID}", svc.DeletePermission)
|
||||
r.Post("/createLink", svc.CreateLink)
|
||||
r.Route("/me", func(r chi.Router) {
|
||||
r.Get("/drives", svc.GetDrives(APIVersion_1_Beta_1))
|
||||
r.Route("/drive", func(r chi.Router) {
|
||||
r.Get("/sharedByMe", svc.GetSharedByMe)
|
||||
r.Get("/sharedWithMe", svc.ListSharedWithMe)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/drives", func(r chi.Router) {
|
||||
r.Get("/", svc.GetAllDrives(APIVersion_1_Beta_1))
|
||||
r.Route("/{driveID}/items/{itemID}", func(r chi.Router) {
|
||||
r.Post("/invite", svc.Invite)
|
||||
r.Get("/permissions", svc.ListPermissions)
|
||||
r.Delete("/permissions/{permissionID}", svc.DeletePermission)
|
||||
r.Post("/createLink", svc.CreateLink)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/roleManagement/permissions/roleDefinitions", func(r chi.Router) {
|
||||
r.Get("/", svc.GetRoleDefinitions)
|
||||
r.Get("/{roleID}", svc.GetRoleDefinition)
|
||||
@@ -231,7 +243,7 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
r.Get("/", svc.GetUserDrive)
|
||||
r.Get("/root/children", svc.GetRootDriveChildren)
|
||||
})
|
||||
r.Get("/drives", svc.GetDrives)
|
||||
r.Get("/drives", svc.GetDrives(APIVersion_1))
|
||||
r.Post("/changePassword", svc.ChangeOwnPassword)
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
@@ -267,7 +279,7 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
})
|
||||
})
|
||||
r.Route("/drives", func(r chi.Router) {
|
||||
r.Get("/", svc.GetAllDrives)
|
||||
r.Get("/", svc.GetAllDrives(APIVersion_1))
|
||||
r.Post("/", svc.CreateDrive)
|
||||
r.Route("/{driveID}", func(r chi.Router) {
|
||||
r.Patch("/", svc.UpdateDrive)
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"slices"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/conversions"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/conversions"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -188,6 +189,10 @@ func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition {
|
||||
AllowedResourceActions: convert(r),
|
||||
Condition: proto.String(UnifiedRoleConditionGrantee),
|
||||
},
|
||||
{
|
||||
AllowedResourceActions: convert(r),
|
||||
Condition: proto.String(UnifiedRoleConditionOwner),
|
||||
},
|
||||
},
|
||||
LibreGraphWeight: proto.Int32(0),
|
||||
}
|
||||
@@ -225,23 +230,27 @@ func GetApplicableRoleDefinitionsForActions(actions []string, constraints string
|
||||
var definitions []*libregraph.UnifiedRoleDefinition
|
||||
|
||||
for _, definition := range GetBuiltinRoleDefinitionList(resharing) {
|
||||
match := true
|
||||
definitionMatch := true
|
||||
|
||||
for _, permission := range definition.GetRolePermissions() {
|
||||
if permission.GetCondition() != constraints {
|
||||
match = false
|
||||
break
|
||||
definitionMatch = false
|
||||
continue
|
||||
}
|
||||
|
||||
for _, action := range permission.GetAllowedResourceActions() {
|
||||
if !slices.Contains(actions, action) {
|
||||
match = false
|
||||
definitionMatch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if definitionMatch {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
if !definitionMatch {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,21 @@ import (
|
||||
|
||||
var _ = Describe("unifiedroles", func() {
|
||||
DescribeTable("CS3ResourcePermissionsToUnifiedRole",
|
||||
func(legacyRole *rConversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition) {
|
||||
func(legacyRole *rConversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition, constraints string) {
|
||||
cs3perm := legacyRole.CS3ResourcePermissions()
|
||||
|
||||
r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3perm, unifiedrole.UnifiedRoleConditionGrantee, true)
|
||||
r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3perm, constraints, true)
|
||||
Expect(r.GetId()).To(Equal(unifiedRole.GetId()))
|
||||
|
||||
},
|
||||
Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true)),
|
||||
Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true)),
|
||||
Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true)),
|
||||
Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole()),
|
||||
Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole()),
|
||||
Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
|
||||
Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
|
||||
Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee),
|
||||
Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
|
||||
Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee),
|
||||
Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
|
||||
Entry(rConversions.RoleSpaceViewer, rConversions.NewSpaceViewerRole(), unifiedrole.NewSpaceViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
|
||||
Entry(rConversions.RoleSpaceEditor, rConversions.NewSpaceEditorRole(), unifiedrole.NewSpaceEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner),
|
||||
)
|
||||
|
||||
DescribeTable("UnifiedRolePermissionsToCS3ResourcePermissions",
|
||||
|
||||
Reference in New Issue
Block a user