Skip to content

Commit

Permalink
feat(graph): add POST /drive/<id>/root/invite suppport
Browse files Browse the repository at this point in the history
Partial Fix: owncloud#8351
  • Loading branch information
rhafer committed Apr 3, 2024
1 parent 1369542 commit 01f0d50
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 5 deletions.
58 changes: 58 additions & 0 deletions services/graph/mocks/drive_item_permissions_provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 57 additions & 3 deletions services/graph/pkg/service/v0/api_driveitem_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
Expand All @@ -23,7 +23,8 @@ import (
)

type DriveItemPermissionsProvider interface {
Invite(ctx context.Context, resourceId provider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error)
Invite(ctx context.Context, resourceId storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error)
SpaceRootInvite(ctx context.Context, driveID storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error)
}

// DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items.
Expand All @@ -45,7 +46,7 @@ func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Sele
}

// Invite invites a user to a drive item.
func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId provider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return libregraph.Permission{}, err
Expand Down Expand Up @@ -167,6 +168,26 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId prov
return *permission, nil
}

// SpaceRootInvite handles invitation request on project spaces
func (s DriveItemPermissionsService) SpaceRootInvite(ctx context.Context, driveID storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return libregraph.Permission{}, err
}

space, err := utils.GetSpace(ctx, storagespace.FormatResourceID(driveID), gatewayClient)
if err != nil {
return libregraph.Permission{}, err
}

if space.SpaceType != "project" {
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "unsupported space type")
}

rootResourceID := space.GetRoot()
return s.Invite(ctx, *rootResourceID, invite)
}

// DriveItemPermissionsService is the api that registers the http endpoints which expose needed operation to the graph api.
// the business logic is delegated to the permissions service and further down to the cs3 client.
type DriveItemPermissionsApi struct {
Expand Down Expand Up @@ -215,3 +236,36 @@ func (api DriveItemPermissionsApi) Invite(w http.ResponseWriter, r *http.Request
render.Status(r, http.StatusOK)
render.JSON(w, r, &ListResponse{Value: []interface{}{permission}})
}

func (api DriveItemPermissionsApi) SpaceRootInvite(w http.ResponseWriter, r *http.Request) {
driveID, err := parseIDParam(r, "driveID")
if err != nil {
msg := "could not parse driveID"
api.logger.Debug().Err(err).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg)
return
}

driveItemInvite := &libregraph.DriveItemInvite{}
if err = StrictJSONUnmarshal(r.Body, driveItemInvite); err != nil {
api.logger.Debug().Err(err).Interface("Body", r.Body).Msg("failed unmarshalling request body")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid request body")
return
}

ctx := r.Context()
if err = validate.StructCtx(ctx, driveItemInvite); err != nil {
api.logger.Debug().Err(err).Interface("Body", r.Body).Msg("invalid request body")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
return
}
permission, err := api.driveItemPermissionsService.SpaceRootInvite(ctx, driveID, *driveItemInvite)

if err != nil {
errorcode.RenderError(w, r, err)
return
}

render.Status(r, http.StatusOK)
render.JSON(w, r, &ListResponse{Value: []interface{}{permission}})
}
134 changes: 133 additions & 1 deletion services/graph/pkg/service/v0/api_driveitem_permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,107 @@ var _ = Describe("DriveItemPermissionsService", func() {
Expect(permission.GetLibreGraphPermissionsActions()).To(HaveLen(1))
Expect(permission.GetLibreGraphPermissionsActions()[0]).To(Equal(unifiedrole.DriveItemContentRead))
})
It("fails with a missing driveritem", func() {
statResponse.Status = status.NewNotFound(context.Background(), "not found")
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(errorcode.New(errorcode.ItemNotFound, "not found")))
Expect(permission).To(BeZero())
})
})
Describe("SpaceRootInvite", func() {
var (
listSpacesResponse *provider.ListStorageSpacesResponse
createShareResponse *collaboration.CreateShareResponse
driveItemInvite libregraph.DriveItemInvite
driveId provider.ResourceId
statResponse *provider.StatResponse
getUserResponse *userpb.GetUserResponse
)

BeforeEach(func() {
driveId = provider.ResourceId{
StorageId: "1",
SpaceId: "2",
}
ctx := revactx.ContextSetUser(context.Background(), currentUser)

statResponse = &provider.StatResponse{
Status: status.NewOK(ctx),
}

listSpacesResponse = &provider.ListStorageSpacesResponse{
Status: status.NewOK(ctx),
StorageSpaces: []*provider.StorageSpace{
{
Id: &provider.StorageSpaceId{
OpaqueId: "2",
},
},
},
}

getUserResponse = &userpb.GetUserResponse{
Status: status.NewOK(ctx),
User: &userpb.User{
Id: &userpb.UserId{OpaqueId: "1"},
DisplayName: "Cem Kaner",
},
}

createShareResponse = &collaboration.CreateShareResponse{
Status: status.NewOK(ctx),
}
})

It("adds a user to a space as expected (happy path)", func() {
listSpacesResponse.StorageSpaces[0].SpaceType = "project"
listSpacesResponse.StorageSpaces[0].Root = &provider.ResourceId{
StorageId: "1",
SpaceId: "2",
OpaqueId: "3",
}

gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil)
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
driveItemInvite.Recipients = []libregraph.DriveRecipient{
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
}
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
createShareResponse.Share = &collaboration.Share{
Id: &collaboration.ShareId{OpaqueId: "123"},
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
}

permission, err := driveItemPermissionsService.SpaceRootInvite(context.Background(), driveId, driveItemInvite)
Expect(err).ToNot(HaveOccurred())
Expect(permission.GetId()).To(Equal("123"))
Expect(permission.GetExpirationDateTime().Equal(*driveItemInvite.ExpirationDateTime)).To(BeTrue())
Expect(permission.GrantedToV2.User.GetDisplayName()).To(Equal(getUserResponse.User.DisplayName))
Expect(permission.GrantedToV2.User.GetId()).To(Equal("1"))
})
It("rejects to add a user to a personal space", func() {
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil)
driveItemInvite.Recipients = []libregraph.DriveRecipient{
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
}
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
createShareResponse.Share = &collaboration.Share{
Id: &collaboration.ShareId{OpaqueId: "123"},
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
}

permission, err := driveItemPermissionsService.SpaceRootInvite(context.Background(), driveId, driveItemInvite)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "unsupported space type")))
Expect(permission).To(BeZero())
})
})
})

var _ = Describe("DriveItemPermissionsApiApi", func() {
var _ = Describe("DriveItemPermissionsApi", func() {
var (
mockProvider *mocks.DriveItemPermissionsProvider
httpAPI svc.DriveItemPermissionsApi
Expand Down Expand Up @@ -286,4 +383,39 @@ var _ = Describe("DriveItemPermissionsApiApi", func() {
Expect(responseRecorder.Code).To(Equal(http.StatusOK))
})
})
Describe("SpaceRootInvite", func() {
It("call the Invite provider with the correct arguments", func() {
responseRecorder := httptest.NewRecorder()
inviteJson, err := json.Marshal(invite)
Expect(err).ToNot(HaveOccurred())

onInvite := mockProvider.On("SpaceRootInvite", mock.Anything, mock.Anything, mock.Anything)
onInvite.Return(func(ctx context.Context, driveID storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
Expect(storagespace.FormatResourceID(driveID)).To(Equal("1$2"))
return libregraph.Permission{}, nil
}).Once()

request := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(inviteJson)).
WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
)
httpAPI.SpaceRootInvite(responseRecorder, request)

Expect(responseRecorder.Code).To(Equal(http.StatusOK))
})
It("call the Invite provider with the correct arguments", func() {
rCTX.URLParams.Add("driveID", "")
responseRecorder := httptest.NewRecorder()
inviteJson, err := json.Marshal(invite)
Expect(err).ToNot(HaveOccurred())

request := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(inviteJson)).
WithContext(
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
)
httpAPI.SpaceRootInvite(responseRecorder, request)

Expect(responseRecorder.Code).To(Equal(http.StatusUnprocessableEntity))
})
})
})
5 changes: 4 additions & 1 deletion services/graph/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ func NewService(opts ...Option) (Graph, error) {
r.Route("/drives", func(r chi.Router) {
r.Get("/", svc.GetAllDrives(APIVersion_1_Beta_1))
r.Route("/{driveID}", func(r chi.Router) {
r.Post("/root/children", drivesDriveItemApi.CreateDriveItem)
r.Route("/root", func(r chi.Router) {
r.Post("/children", drivesDriveItemApi.CreateDriveItem)
r.Post("/invite", driveItemPermissionsApi.SpaceRootInvite)
})
r.Route("/items/{itemID}", func(r chi.Router) {
r.Delete("/", drivesDriveItemApi.DeleteDriveItem)
r.Post("/invite", driveItemPermissionsApi.Invite)
Expand Down

0 comments on commit 01f0d50

Please sign in to comment.