From 5f70d8a303b6c6afae1269e73e62987ee1271772 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Sat, 25 Nov 2023 10:32:23 +0100 Subject: [PATCH] test: add integration tests for create link handler --- services/graph/pkg/service/v0/links_test.go | 329 ++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 services/graph/pkg/service/v0/links_test.go diff --git a/services/graph/pkg/service/v0/links_test.go b/services/graph/pkg/service/v0/links_test.go new file mode 100644 index 000000000..bca2259dc --- /dev/null +++ b/services/graph/pkg/service/v0/links_test.go @@ -0,0 +1,329 @@ +package svc_test + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + 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" + "github.com/owncloud/ocis/v2/services/graph/mocks" + "github.com/owncloud/ocis/v2/services/graph/pkg/config" + "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" + identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" + "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" + service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +var _ = Describe("createLinkTests", func() { + var ( + svc service.Service + ctx context.Context + cfg *config.Config + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + eventsPublisher mocks.Publisher + identityBackend *identitymocks.Backend + currentUser = &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: "user", + }, + } + + rr *httptest.ResponseRecorder + ) + + BeforeEach(func() { + eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + rr = httptest.NewRecorder() + + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + gatewayClient = &cs3mocks.GatewayAPIClient{} + gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + + identityBackend = &identitymocks.Backend{} + + rr = httptest.NewRecorder() + ctx = context.Background() + + cfg = defaults.FullDefaultConfig() + cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests + cfg.TokenManager.JWTSecret = "loremipsum" + cfg.Commons = &shared.Commons{} + cfg.GRPCClientTLS = &shared.GRPCClientTLS{} + cfg.FilesSharing.EnableResharing = true + + svc, _ = service.NewService( + service.Config(cfg), + service.WithGatewaySelector(gatewaySelector), + service.EventsPublisher(&eventsPublisher), + service.WithIdentityBackend(identityBackend), + ) + }) + + Describe("CreateLink", func() { + var ( + itemID string + driveItemCreateLink *libregraph.DriveItemCreateLink + statMock *mock.Call + statResponse *provider.StatResponse + createLinkResponse *link.CreatePublicShareResponse + createLinkMock *mock.Call + ) + + BeforeEach(func() { + itemID = "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!1177add3-b4eb-434e-a2e8-1859b31b17bf" + rctx := chi.NewRouteContext() + rctx.URLParams.Add("driveID", "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!cd621428-dfbe-44c1-9393-65bf0dd440a6") + rctx.URLParams.Add("itemID", itemID) + + ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) + ctx = revactx.ContextSetUser(ctx, currentUser) + + driveItemCreateLink = &libregraph.DriveItemCreateLink{ + Type: nil, + ExpirationDateTime: nil, + Password: nil, + DisplayName: nil, + LibreGraphQuickLink: nil, + } + + statMock = gatewayClient.On("Stat", mock.Anything, mock.Anything) + statResponse = &provider.StatResponse{ + Status: status.NewOK(ctx), + } + statMock.Return(statResponse, nil) + + createLinkMock = gatewayClient.On("CreatePublicShare", mock.Anything, mock.Anything) + createLinkResponse = &link.CreatePublicShareResponse{ + Status: status.NewOK(ctx), + } + createLinkMock.Return(createLinkResponse, nil) + + linkType, err := libregraph.NewSharingLinkTypeFromValue("view") + Expect(err).ToNot(HaveOccurred()) + driveItemCreateLink.Type = linkType + driveItemCreateLink.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour)) + permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(*driveItemCreateLink, provider.ResourceType_RESOURCE_TYPE_CONTAINER) + Expect(err).ToNot(HaveOccurred()) + createLinkResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "123"}, + Expiration: utils.TimeToTS(*driveItemCreateLink.ExpirationDateTime), + PasswordProtected: false, + DisplayName: "Viewer Link", + Token: "SomeGOODCoffee", + Permissions: &link.PublicSharePermissions{Permissions: permissions}, + } + + statResponse.Info = &provider.ResourceInfo{Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER} + }) + + toJSONReader := func(v any) *strings.Reader { + driveItemInviteBytes, err := json.Marshal(v) + Expect(err).ToNot(HaveOccurred()) + + return strings.NewReader(string(driveItemInviteBytes)) + } + + // Public Shares / "links" in graph terms + It("creates a public link as expected (happy path)", func() { + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). + WithContext(ctx), + ) + + Expect(rr.Code).To(Equal(http.StatusCreated)) + + var createLinkResponseBody []*libregraph.Permission + err := json.Unmarshal(rr.Body.Bytes(), &createLinkResponseBody) + Expect(err).ToNot(HaveOccurred()) + Expect(len(createLinkResponseBody)).To(Equal(1)) + Expect(createLinkResponseBody[0].GetId()).To(Equal("123")) + Expect(createLinkResponseBody[0].GetExpirationDateTime().Unix()).To(Equal(driveItemCreateLink.ExpirationDateTime.Unix())) + Expect(createLinkResponseBody[0].GetHasPassword()).To(Equal(false)) + Expect(createLinkResponseBody[0].GetLink().LibreGraphDisplayName).To(Equal(libregraph.PtrString("Viewer Link"))) + link := createLinkResponseBody[0].GetLink() + respLinkType := link.GetType() + expected, err := libregraph.NewSharingLinkTypeFromValue("view") + Expect(err).ToNot(HaveOccurred()) + Expect(&respLinkType).To(Equal(expected)) + }) + + It("handles a failing CreateLink", func() { + linkType, err := libregraph.NewSharingLinkTypeFromValue("edit") + Expect(err).ToNot(HaveOccurred()) + driveItemCreateLink.Type = linkType + permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(*driveItemCreateLink, provider.ResourceType_RESOURCE_TYPE_CONTAINER) + Expect(err).ToNot(HaveOccurred()) + createLinkResponse.Status = status.NewInternal(ctx, "transport error") + createLinkResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "123"}, + Permissions: &link.PublicSharePermissions{Permissions: permissions}, + } + + statResponse.Info = &provider.ResourceInfo{Type: provider.ResourceType_RESOURCE_TYPE_FILE} + + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). + WithContext(ctx), + ) + Expect(rr.Code).To(Equal(http.StatusInternalServerError)) + var odataError libregraph.OdataError + err = json.Unmarshal(rr.Body.Bytes(), &odataError) + Expect(err).ToNot(HaveOccurred()) + getError := odataError.GetError() + Expect(getError.GetCode()).To(Equal("generalException")) + Expect(getError.GetMessage()).To(Equal("transport error")) + }) + + It("fails due to an invalid spaceID", func() { + driveItemCreateLink = libregraph.NewDriveItemCreateLink() + + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/graph/v1beta1/drives/space-id/items/item-id/createShare", nil), + ) + Expect(rr.Code).To(Equal(http.StatusBadRequest)) + var odataError libregraph.OdataError + err := json.Unmarshal(rr.Body.Bytes(), &odataError) + Expect(err).ToNot(HaveOccurred()) + getError := odataError.GetError() + Expect(getError.GetCode()).To(Equal("invalidRequest")) + Expect(getError.GetMessage()).To(Equal("can't split empty storage space ID: invalid storage space id")) + }) + + // Public Shares / "links" in graph terms + It("fails when creating a public link with an empty request body", func() { + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/", nil). + WithContext(ctx), + ) + + Expect(rr.Code).To(Equal(http.StatusBadRequest)) + + var odataError libregraph.OdataError + err := json.Unmarshal(rr.Body.Bytes(), &odataError) + Expect(err).ToNot(HaveOccurred()) + getError := odataError.GetError() + Expect(getError.GetCode()).To(Equal("invalidRequest")) + Expect(getError.GetMessage()).To(Equal("invalid body schema definition")) + + }) + + It("fails when the stat returns access denied", func() { + err := errors.New("no permission to stat the file") + statResponse.Status = status.NewPermissionDenied(ctx, err, err.Error()) + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). + WithContext(ctx), + ) + + Expect(rr.Code).To(Equal(http.StatusForbidden)) + + var odataError libregraph.OdataError + err = json.Unmarshal(rr.Body.Bytes(), &odataError) + Expect(err).ToNot(HaveOccurred()) + getError := odataError.GetError() + Expect(getError.GetCode()).To(Equal("accessDenied")) + Expect(getError.GetMessage()).To(Equal("no permission to stat the file")) + }) + + It("fails when the stat returns resource is locked", func() { + err := errors.New("the resource is locked") + statResponse.Status = status.NewLocked(ctx, err.Error()) + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). + WithContext(ctx), + ) + + Expect(rr.Code).To(Equal(http.StatusLocked)) + + var odataError libregraph.OdataError + err = json.Unmarshal(rr.Body.Bytes(), &odataError) + Expect(err).ToNot(HaveOccurred()) + getError := odataError.GetError() + Expect(getError.GetCode()).To(Equal("itemIsLocked")) + Expect(getError.GetMessage()).To(Equal("the resource is locked")) + }) + + It("succeeds when the link type mapping is not successful", func() { + // we need to send a valid link type + linkType, err := libregraph.NewSharingLinkTypeFromValue("edit") + Expect(err).ToNot(HaveOccurred()) + driveItemCreateLink.Type = linkType + permissions := &provider.ResourcePermissions{ + CreateContainer: true, + InitiateFileUpload: true, + Move: true, + } + // return different permissions which do not match a link type + createLinkResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "123"}, + Expiration: utils.TimeToTS(*driveItemCreateLink.ExpirationDateTime), + PasswordProtected: false, + DisplayName: "Viewer Link", + Token: "SomeGOODCoffee", + Permissions: &link.PublicSharePermissions{Permissions: permissions}, + } + svc.CreateLink( + rr, + httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). + WithContext(ctx), + ) + + Expect(rr.Code).To(Equal(http.StatusCreated)) + + var createLinkResponseBody []*libregraph.Permission + err = json.Unmarshal(rr.Body.Bytes(), &createLinkResponseBody) + Expect(err).ToNot(HaveOccurred()) + Expect(len(createLinkResponseBody)).To(Equal(1)) + Expect(createLinkResponseBody[0].GetId()).To(Equal("123")) + Expect(createLinkResponseBody[0].GetExpirationDateTime().Unix()).To(Equal(driveItemCreateLink.ExpirationDateTime.Unix())) + Expect(createLinkResponseBody[0].GetHasPassword()).To(Equal(false)) + Expect(createLinkResponseBody[0].GetLink().LibreGraphDisplayName).To(Equal(libregraph.PtrString("Viewer Link"))) + respLink := createLinkResponseBody[0].GetLink() + // some conversion gymnastics + respLinkType := respLink.GetType() + Expect(err).ToNot(HaveOccurred()) + mockLink := libregraph.SharingLink{} + lt, _ := linktype.SharingLinkTypeFromCS3Permissions(&link.PublicSharePermissions{Permissions: permissions}) + mockLink.Type = lt + expectedType := mockLink.GetType() + Expect(&respLinkType).To(Equal(&expectedType)) + libreGraphActions := createLinkResponseBody[0].LibreGraphPermissionsActions + Expect(libreGraphActions[0]).To(Equal("libre.graph/driveItem/children/create")) + Expect(libreGraphActions[1]).To(Equal("libre.graph/driveItem/upload/create")) + Expect(libreGraphActions[2]).To(Equal("libre.graph/driveItem/path/update")) + }) + }) +})