mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-07 21:00:30 -06:00
graph: Initial implementation of /me/drives/sharedByMe endpoint
This adds a still incomplete implementation of the new /me/drives/sharedByMe endpoint. For now it only sets the driveItem Id property. The 'permissions' relation is not supported yet. This endpoint is added to the /v1beta1 route, to indicate that it is still imcomplete and might require changes.
This commit is contained in:
committed by
Ralf Haferkamp
parent
86b061421e
commit
f18775b1cb
@@ -96,6 +96,7 @@ type Service interface {
|
||||
GetDrives(w http.ResponseWriter, r *http.Request)
|
||||
GetSingleDrive(w http.ResponseWriter, r *http.Request)
|
||||
GetAllDrives(w http.ResponseWriter, r *http.Request)
|
||||
GetSharedByMe(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)
|
||||
@@ -199,6 +200,9 @@ 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/drives/sharedByMe", svc.GetSharedByMe)
|
||||
})
|
||||
r.Route("/v1.0", func(r chi.Router) {
|
||||
r.Route("/extensions/org.libregraph", func(r chi.Router) {
|
||||
r.Get("/tags", svc.GetTags)
|
||||
@@ -212,7 +216,9 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
r.Route("/me", func(r chi.Router) {
|
||||
r.Get("/", svc.GetMe)
|
||||
r.Get("/drive", svc.GetUserDrive)
|
||||
r.Get("/drives", svc.GetDrives)
|
||||
r.Route("/drives", func(r chi.Router) {
|
||||
r.Get("/", svc.GetDrives)
|
||||
})
|
||||
r.Get("/drive/root/children", svc.GetRootDriveChildren)
|
||||
r.Post("/changePassword", svc.ChangeOwnPassword)
|
||||
})
|
||||
|
||||
149
services/graph/pkg/service/v0/sharedbyme.go
Normal file
149
services/graph/pkg/service/v0/sharedbyme.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/share"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
|
||||
)
|
||||
|
||||
type driveItemsByResourceID map[string]libregraph.DriveItem
|
||||
|
||||
// GetSharedByMe implements the Service interface (/me/drives/sharedByMe endpoint)
|
||||
func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) {
|
||||
g.logger.Debug().Msg("Calling GetRootDriveChildren")
|
||||
ctx := r.Context()
|
||||
|
||||
driveItems := make(driveItemsByResourceID)
|
||||
var err error
|
||||
driveItems, err = g.listUserShares(ctx, driveItems)
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = g.listPublicShares(ctx, driveItems)
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := make([]libregraph.DriveItem, 0, len(driveItems))
|
||||
for _, v := range driveItems {
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{Value: res})
|
||||
}
|
||||
|
||||
func (g Graph) listUserShares(ctx context.Context, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("could not select next gateway client")
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
|
||||
filters := []*collaboration.Filter{
|
||||
share.UserGranteeFilter(),
|
||||
share.GroupGranteeFilter(),
|
||||
}
|
||||
lsUserSharesRequest := collaboration.ListSharesRequest{
|
||||
Filters: filters,
|
||||
}
|
||||
|
||||
lsUserSharesResponse, err := gatewayClient.ListShares(ctx, &lsUserSharesRequest)
|
||||
if err != nil {
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
if statusCode := lsUserSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK {
|
||||
return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsUserSharesResponse.Status.Message)
|
||||
}
|
||||
driveItems, err = g.cs3UserSharesToDriveItems(ctx, lsUserSharesResponse.Shares, driveItems)
|
||||
if err != nil {
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
return driveItems, nil
|
||||
}
|
||||
|
||||
func (g Graph) listPublicShares(ctx context.Context, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
||||
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("could not select next gateway client")
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
|
||||
filters := []*link.ListPublicSharesRequest_Filter{}
|
||||
|
||||
req := link.ListPublicSharesRequest{
|
||||
Filters: filters,
|
||||
}
|
||||
|
||||
lsPublicSharesResponse, err := gatewayClient.ListPublicShares(ctx, &req)
|
||||
if err != nil {
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
if statusCode := lsPublicSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK {
|
||||
return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsPublicSharesResponse.Status.Message)
|
||||
}
|
||||
driveItems, err = g.cs3PublicSharesToDriveItems(ctx, lsPublicSharesResponse.Share, driveItems)
|
||||
if err != nil {
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
return driveItems, nil
|
||||
|
||||
}
|
||||
|
||||
func (g Graph) cs3UserSharesToDriveItems(ctx context.Context, shares []*collaboration.Share, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
||||
for _, s := range shares {
|
||||
g.logger.Debug().Interface("CS3 UserShare", s).Msg("Got Share")
|
||||
resIDStr := storagespace.FormatResourceID(*s.ResourceId)
|
||||
item, ok := driveItems[resIDStr]
|
||||
if !ok {
|
||||
item = libregraph.DriveItem{
|
||||
Id: libregraph.PtrString(resIDStr),
|
||||
}
|
||||
}
|
||||
driveItems[resIDStr] = item
|
||||
}
|
||||
|
||||
return driveItems, nil
|
||||
}
|
||||
|
||||
func (g Graph) cs3PublicSharesToDriveItems(ctx context.Context, shares []*link.PublicShare, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
||||
for _, s := range shares {
|
||||
g.logger.Debug().Interface("CS3 PublicShare", s).Msg("Got Share")
|
||||
resIDStr := storagespace.FormatResourceID(*s.ResourceId)
|
||||
item, ok := driveItems[resIDStr]
|
||||
if !ok {
|
||||
item = libregraph.DriveItem{
|
||||
Id: libregraph.PtrString(resIDStr),
|
||||
}
|
||||
}
|
||||
driveItems[resIDStr] = item
|
||||
}
|
||||
|
||||
return driveItems, nil
|
||||
}
|
||||
|
||||
func cs3StatusToErrCode(code rpc.Code) (errcode errorcode.ErrorCode) {
|
||||
switch code {
|
||||
case rpc.Code_CODE_UNAUTHENTICATED:
|
||||
errcode = errorcode.Unauthenticated
|
||||
case rpc.Code_CODE_PERMISSION_DENIED:
|
||||
errcode = errorcode.AccessDenied
|
||||
case rpc.Code_CODE_NOT_FOUND:
|
||||
errcode = errorcode.ItemNotFound
|
||||
default:
|
||||
errcode = errorcode.GeneralException
|
||||
}
|
||||
return errcode
|
||||
}
|
||||
304
services/graph/pkg/service/v0/sharedbyme_test.go
Normal file
304
services/graph/pkg/service/v0/sharedbyme_test.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package svc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
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"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||
. "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"
|
||||
service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var _ = Describe("Driveitems", 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
|
||||
|
||||
rr *httptest.ResponseRecorder
|
||||
|
||||
newGroup *libregraph.Group
|
||||
)
|
||||
|
||||
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{}
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(
|
||||
&link.ListPublicSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Share: []*link.PublicShare{},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient](
|
||||
"GatewaySelector",
|
||||
"com.owncloud.api.gateway",
|
||||
func(cc *grpc.ClientConn) gateway.GatewayAPIClient {
|
||||
return gatewayClient
|
||||
},
|
||||
)
|
||||
|
||||
identityBackend = &identitymocks.Backend{}
|
||||
newGroup = libregraph.NewGroup()
|
||||
newGroup.SetMembersodataBind([]string{"/users/user1"})
|
||||
newGroup.SetId("group1")
|
||||
|
||||
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{}
|
||||
|
||||
svc, _ = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
)
|
||||
})
|
||||
|
||||
Describe("GetSharedByMe", func() {
|
||||
expiration := time.Now()
|
||||
userShare := collaboration.Share{
|
||||
Id: &collaboration.ShareId{
|
||||
OpaqueId: "share-id",
|
||||
},
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
SpaceId: "spaceid",
|
||||
OpaqueId: "opaqueid",
|
||||
},
|
||||
Grantee: &provider.Grantee{
|
||||
Type: provider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &provider.Grantee_UserId{
|
||||
UserId: &userpb.UserId{
|
||||
OpaqueId: "user-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
groupShare := collaboration.Share{
|
||||
Id: &collaboration.ShareId{
|
||||
OpaqueId: "share-id",
|
||||
},
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
SpaceId: "spaceid",
|
||||
OpaqueId: "opaqueid",
|
||||
},
|
||||
Grantee: &provider.Grantee{
|
||||
Type: provider.GranteeType_GRANTEE_TYPE_GROUP,
|
||||
Id: &provider.Grantee_GroupId{
|
||||
GroupId: &grouppb.GroupId{
|
||||
OpaqueId: "group-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
userShareWithExpiration := collaboration.Share{
|
||||
Id: &collaboration.ShareId{
|
||||
OpaqueId: "expire-share-id",
|
||||
},
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
SpaceId: "spaceid",
|
||||
OpaqueId: "expire-opaqueid",
|
||||
},
|
||||
Grantee: &provider.Grantee{
|
||||
Type: provider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &provider.Grantee_UserId{
|
||||
UserId: &userpb.UserId{
|
||||
OpaqueId: "user-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
Expiration: utils.TimeToTS(expiration),
|
||||
}
|
||||
|
||||
It("handles a failing ListShares", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(nil, errors.New("some error"))
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
|
||||
})
|
||||
|
||||
It("handles ListShares returning an error status", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(
|
||||
&collaboration.ListSharesResponse{Status: status.NewInternal(ctx, "error listing shares")},
|
||||
nil,
|
||||
)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
|
||||
})
|
||||
|
||||
It("succeeds, when no shares are returned", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(
|
||||
&collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: []*collaboration.Share{},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := itemsList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(0))
|
||||
})
|
||||
|
||||
It("returns a proper driveItem, when a single user share is returned", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(
|
||||
&collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: []*collaboration.Share{
|
||||
&userShare,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := itemsList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(1))
|
||||
|
||||
di := res.Value[0]
|
||||
Expect(di.GetId()).To(Equal(storagespace.FormatResourceID(*userShare.GetResourceId())))
|
||||
})
|
||||
|
||||
It("returns a proper driveItem, when a single group share is returned", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(
|
||||
&collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: []*collaboration.Share{
|
||||
&groupShare,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := itemsList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(1))
|
||||
|
||||
di := res.Value[0]
|
||||
Expect(di.GetId()).To(Equal(storagespace.FormatResourceID(*groupShare.GetResourceId())))
|
||||
})
|
||||
|
||||
It("returns a single driveItem, when a mulitple shares for the same resource are returned", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(
|
||||
&collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: []*collaboration.Share{
|
||||
&groupShare,
|
||||
&userShare,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := itemsList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(1))
|
||||
|
||||
di := res.Value[0]
|
||||
Expect(di.GetId()).To(Equal(storagespace.FormatResourceID(*groupShare.GetResourceId())))
|
||||
})
|
||||
|
||||
It("return a driveItem with the expiration date set, for expiring shares", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(
|
||||
&collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: []*collaboration.Share{
|
||||
&userShareWithExpiration,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives/sharedByMe", nil)
|
||||
svc.GetSharedByMe(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := itemsList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(res.Value)).To(Equal(1))
|
||||
|
||||
di := res.Value[0]
|
||||
Expect(di.GetId()).To(Equal(storagespace.FormatResourceID(*userShareWithExpiration.GetResourceId())))
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user