Enhancement: Ability to Change Share Item Visibility in Graph API (#8750)

* Enhancement: Ability to Change Share Item Visibility in Graph API

Introduce the `PATCH /graph/v1beta1/drives/{driveID}/items/{itemID}` Graph API endpoint which allows updating individual Drive Items.

* fix: failing tests

* fix: consider siblings when updating shares

* fix: reduce sharing service provider interface
This commit is contained in:
Florian Schade
2024-04-22 17:24:45 +02:00
committed by GitHub
parent fa61be377b
commit b8ed049487
10 changed files with 1297 additions and 844 deletions

View File

@@ -0,0 +1,11 @@
Enhancement: Ability to Change Share Item Visibility in Graph API
Introduce the `PATCH /graph/v1beta1/drives/{driveID}/items/{itemID}` Graph API endpoint which allows updating individual Drive Items.
At the moment, only the share visibility is considered changeable, but in the future, more properties can be added to this endpoint.
This enhancement is needed for the user interface, allowing specific shares to be hidden or unhidden as needed,
thereby improving the user experience.
https://github.com/owncloud/ocis/pull/8750
https://github.com/owncloud/ocis/issues/8654

View File

@@ -7,6 +7,7 @@ packages:
config:
dir: "mocks"
interfaces:
BaseGraphProvider:
DrivesDriveItemProvider:
DriveItemPermissionsProvider:
HTTPClient:

View File

@@ -0,0 +1,99 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
context "context"
collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
libregraph "github.com/owncloud/libre-graph-api-go"
mock "github.com/stretchr/testify/mock"
)
// BaseGraphProvider is an autogenerated mock type for the BaseGraphProvider type
type BaseGraphProvider struct {
mock.Mock
}
type BaseGraphProvider_Expecter struct {
mock *mock.Mock
}
func (_m *BaseGraphProvider) EXPECT() *BaseGraphProvider_Expecter {
return &BaseGraphProvider_Expecter{mock: &_m.Mock}
}
// CS3ReceivedSharesToDriveItems provides a mock function with given fields: ctx, receivedShares
func (_m *BaseGraphProvider) CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaborationv1beta1.ReceivedShare) ([]libregraph.DriveItem, error) {
ret := _m.Called(ctx, receivedShares)
if len(ret) == 0 {
panic("no return value specified for CS3ReceivedSharesToDriveItems")
}
var r0 []libregraph.DriveItem
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []*collaborationv1beta1.ReceivedShare) ([]libregraph.DriveItem, error)); ok {
return rf(ctx, receivedShares)
}
if rf, ok := ret.Get(0).(func(context.Context, []*collaborationv1beta1.ReceivedShare) []libregraph.DriveItem); ok {
r0 = rf(ctx, receivedShares)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]libregraph.DriveItem)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []*collaborationv1beta1.ReceivedShare) error); ok {
r1 = rf(ctx, receivedShares)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CS3ReceivedSharesToDriveItems'
type BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call struct {
*mock.Call
}
// CS3ReceivedSharesToDriveItems is a helper method to define mock.On call
// - ctx context.Context
// - receivedShares []*collaborationv1beta1.ReceivedShare
func (_e *BaseGraphProvider_Expecter) CS3ReceivedSharesToDriveItems(ctx interface{}, receivedShares interface{}) *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call {
return &BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call{Call: _e.mock.On("CS3ReceivedSharesToDriveItems", ctx, receivedShares)}
}
func (_c *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call) Run(run func(ctx context.Context, receivedShares []*collaborationv1beta1.ReceivedShare)) *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([]*collaborationv1beta1.ReceivedShare))
})
return _c
}
func (_c *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call) Return(_a0 []libregraph.DriveItem, _a1 error) *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call) RunAndReturn(run func(context.Context, []*collaborationv1beta1.ReceivedShare) ([]libregraph.DriveItem, error)) *BaseGraphProvider_CS3ReceivedSharesToDriveItems_Call {
_c.Call.Return(run)
return _c
}
// NewBaseGraphProvider creates a new instance of BaseGraphProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewBaseGraphProvider(t interface {
mock.TestingT
Cleanup(func())
}) *BaseGraphProvider {
mock := &BaseGraphProvider{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -5,10 +5,13 @@ package mocks
import (
context "context"
libregraph "github.com/owncloud/libre-graph-api-go"
collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
mock "github.com/stretchr/testify/mock"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
)
// DrivesDriveItemProvider is an autogenerated mock type for the DrivesDriveItemProvider type
@@ -24,26 +27,88 @@ func (_m *DrivesDriveItemProvider) EXPECT() *DrivesDriveItemProvider_Expecter {
return &DrivesDriveItemProvider_Expecter{mock: &_m.Mock}
}
// GetShareAndSiblings provides a mock function with given fields: ctx, shareID, filters
func (_m *DrivesDriveItemProvider) GetShareAndSiblings(ctx context.Context, shareID *collaborationv1beta1.ShareId, filters []*collaborationv1beta1.Filter) ([]*collaborationv1beta1.ReceivedShare, error) {
ret := _m.Called(ctx, shareID, filters)
if len(ret) == 0 {
panic("no return value specified for GetShareAndSiblings")
}
var r0 []*collaborationv1beta1.ReceivedShare
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareId, []*collaborationv1beta1.Filter) ([]*collaborationv1beta1.ReceivedShare, error)); ok {
return rf(ctx, shareID, filters)
}
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareId, []*collaborationv1beta1.Filter) []*collaborationv1beta1.ReceivedShare); ok {
r0 = rf(ctx, shareID, filters)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*collaborationv1beta1.ReceivedShare)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareId, []*collaborationv1beta1.Filter) error); ok {
r1 = rf(ctx, shareID, filters)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DrivesDriveItemProvider_GetShareAndSiblings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetShareAndSiblings'
type DrivesDriveItemProvider_GetShareAndSiblings_Call struct {
*mock.Call
}
// GetShareAndSiblings is a helper method to define mock.On call
// - ctx context.Context
// - shareID *collaborationv1beta1.ShareId
// - filters []*collaborationv1beta1.Filter
func (_e *DrivesDriveItemProvider_Expecter) GetShareAndSiblings(ctx interface{}, shareID interface{}, filters interface{}) *DrivesDriveItemProvider_GetShareAndSiblings_Call {
return &DrivesDriveItemProvider_GetShareAndSiblings_Call{Call: _e.mock.On("GetShareAndSiblings", ctx, shareID, filters)}
}
func (_c *DrivesDriveItemProvider_GetShareAndSiblings_Call) Run(run func(ctx context.Context, shareID *collaborationv1beta1.ShareId, filters []*collaborationv1beta1.Filter)) *DrivesDriveItemProvider_GetShareAndSiblings_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*collaborationv1beta1.ShareId), args[2].([]*collaborationv1beta1.Filter))
})
return _c
}
func (_c *DrivesDriveItemProvider_GetShareAndSiblings_Call) Return(_a0 []*collaborationv1beta1.ReceivedShare, _a1 error) *DrivesDriveItemProvider_GetShareAndSiblings_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *DrivesDriveItemProvider_GetShareAndSiblings_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.ShareId, []*collaborationv1beta1.Filter) ([]*collaborationv1beta1.ReceivedShare, error)) *DrivesDriveItemProvider_GetShareAndSiblings_Call {
_c.Call.Return(run)
return _c
}
// MountShare provides a mock function with given fields: ctx, resourceID, name
func (_m *DrivesDriveItemProvider) MountShare(ctx context.Context, resourceID providerv1beta1.ResourceId, name string) (libregraph.DriveItem, error) {
func (_m *DrivesDriveItemProvider) MountShare(ctx context.Context, resourceID *providerv1beta1.ResourceId, name string) ([]*collaborationv1beta1.ReceivedShare, error) {
ret := _m.Called(ctx, resourceID, name)
if len(ret) == 0 {
panic("no return value specified for MountShare")
}
var r0 libregraph.DriveItem
var r0 []*collaborationv1beta1.ReceivedShare
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string) (libregraph.DriveItem, error)); ok {
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, string) ([]*collaborationv1beta1.ReceivedShare, error)); ok {
return rf(ctx, resourceID, name)
}
if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string) libregraph.DriveItem); ok {
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, string) []*collaborationv1beta1.ReceivedShare); ok {
r0 = rf(ctx, resourceID, name)
} else {
r0 = ret.Get(0).(libregraph.DriveItem)
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*collaborationv1beta1.ReceivedShare)
}
}
if rf, ok := ret.Get(1).(func(context.Context, providerv1beta1.ResourceId, string) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, string) error); ok {
r1 = rf(ctx, resourceID, name)
} else {
r1 = ret.Error(1)
@@ -59,40 +124,40 @@ type DrivesDriveItemProvider_MountShare_Call struct {
// MountShare is a helper method to define mock.On call
// - ctx context.Context
// - resourceID providerv1beta1.ResourceId
// - resourceID *providerv1beta1.ResourceId
// - name string
func (_e *DrivesDriveItemProvider_Expecter) MountShare(ctx interface{}, resourceID interface{}, name interface{}) *DrivesDriveItemProvider_MountShare_Call {
return &DrivesDriveItemProvider_MountShare_Call{Call: _e.mock.On("MountShare", ctx, resourceID, name)}
}
func (_c *DrivesDriveItemProvider_MountShare_Call) Run(run func(ctx context.Context, resourceID providerv1beta1.ResourceId, name string)) *DrivesDriveItemProvider_MountShare_Call {
func (_c *DrivesDriveItemProvider_MountShare_Call) Run(run func(ctx context.Context, resourceID *providerv1beta1.ResourceId, name string)) *DrivesDriveItemProvider_MountShare_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(string))
run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(string))
})
return _c
}
func (_c *DrivesDriveItemProvider_MountShare_Call) Return(_a0 libregraph.DriveItem, _a1 error) *DrivesDriveItemProvider_MountShare_Call {
func (_c *DrivesDriveItemProvider_MountShare_Call) Return(_a0 []*collaborationv1beta1.ReceivedShare, _a1 error) *DrivesDriveItemProvider_MountShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *DrivesDriveItemProvider_MountShare_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string) (libregraph.DriveItem, error)) *DrivesDriveItemProvider_MountShare_Call {
func (_c *DrivesDriveItemProvider_MountShare_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, string) ([]*collaborationv1beta1.ReceivedShare, error)) *DrivesDriveItemProvider_MountShare_Call {
_c.Call.Return(run)
return _c
}
// UnmountShare provides a mock function with given fields: ctx, resourceID
func (_m *DrivesDriveItemProvider) UnmountShare(ctx context.Context, resourceID providerv1beta1.ResourceId) error {
ret := _m.Called(ctx, resourceID)
// UnmountShare provides a mock function with given fields: ctx, shareID
func (_m *DrivesDriveItemProvider) UnmountShare(ctx context.Context, shareID *collaborationv1beta1.ShareId) error {
ret := _m.Called(ctx, shareID)
if len(ret) == 0 {
panic("no return value specified for UnmountShare")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId) error); ok {
r0 = rf(ctx, resourceID)
if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareId) error); ok {
r0 = rf(ctx, shareID)
} else {
r0 = ret.Error(0)
}
@@ -107,14 +172,14 @@ type DrivesDriveItemProvider_UnmountShare_Call struct {
// UnmountShare is a helper method to define mock.On call
// - ctx context.Context
// - resourceID providerv1beta1.ResourceId
func (_e *DrivesDriveItemProvider_Expecter) UnmountShare(ctx interface{}, resourceID interface{}) *DrivesDriveItemProvider_UnmountShare_Call {
return &DrivesDriveItemProvider_UnmountShare_Call{Call: _e.mock.On("UnmountShare", ctx, resourceID)}
// - shareID *collaborationv1beta1.ShareId
func (_e *DrivesDriveItemProvider_Expecter) UnmountShare(ctx interface{}, shareID interface{}) *DrivesDriveItemProvider_UnmountShare_Call {
return &DrivesDriveItemProvider_UnmountShare_Call{Call: _e.mock.On("UnmountShare", ctx, shareID)}
}
func (_c *DrivesDriveItemProvider_UnmountShare_Call) Run(run func(ctx context.Context, resourceID providerv1beta1.ResourceId)) *DrivesDriveItemProvider_UnmountShare_Call {
func (_c *DrivesDriveItemProvider_UnmountShare_Call) Run(run func(ctx context.Context, shareID *collaborationv1beta1.ShareId)) *DrivesDriveItemProvider_UnmountShare_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId))
run(args[0].(context.Context), args[1].(*collaborationv1beta1.ShareId))
})
return _c
}
@@ -124,7 +189,67 @@ func (_c *DrivesDriveItemProvider_UnmountShare_Call) Return(_a0 error) *DrivesDr
return _c
}
func (_c *DrivesDriveItemProvider_UnmountShare_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId) error) *DrivesDriveItemProvider_UnmountShare_Call {
func (_c *DrivesDriveItemProvider_UnmountShare_Call) RunAndReturn(run func(context.Context, *collaborationv1beta1.ShareId) error) *DrivesDriveItemProvider_UnmountShare_Call {
_c.Call.Return(run)
return _c
}
// UpdateShares provides a mock function with given fields: ctx, shares, updater
func (_m *DrivesDriveItemProvider) UpdateShares(ctx context.Context, shares []*collaborationv1beta1.ReceivedShare, updater svc.UpdateShareClosure) ([]*collaborationv1beta1.ReceivedShare, error) {
ret := _m.Called(ctx, shares, updater)
if len(ret) == 0 {
panic("no return value specified for UpdateShares")
}
var r0 []*collaborationv1beta1.ReceivedShare
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []*collaborationv1beta1.ReceivedShare, svc.UpdateShareClosure) ([]*collaborationv1beta1.ReceivedShare, error)); ok {
return rf(ctx, shares, updater)
}
if rf, ok := ret.Get(0).(func(context.Context, []*collaborationv1beta1.ReceivedShare, svc.UpdateShareClosure) []*collaborationv1beta1.ReceivedShare); ok {
r0 = rf(ctx, shares, updater)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*collaborationv1beta1.ReceivedShare)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []*collaborationv1beta1.ReceivedShare, svc.UpdateShareClosure) error); ok {
r1 = rf(ctx, shares, updater)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DrivesDriveItemProvider_UpdateShares_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateShares'
type DrivesDriveItemProvider_UpdateShares_Call struct {
*mock.Call
}
// UpdateShares is a helper method to define mock.On call
// - ctx context.Context
// - shares []*collaborationv1beta1.ReceivedShare
// - updater svc.UpdateShareClosure
func (_e *DrivesDriveItemProvider_Expecter) UpdateShares(ctx interface{}, shares interface{}, updater interface{}) *DrivesDriveItemProvider_UpdateShares_Call {
return &DrivesDriveItemProvider_UpdateShares_Call{Call: _e.mock.On("UpdateShares", ctx, shares, updater)}
}
func (_c *DrivesDriveItemProvider_UpdateShares_Call) Run(run func(ctx context.Context, shares []*collaborationv1beta1.ReceivedShare, updater svc.UpdateShareClosure)) *DrivesDriveItemProvider_UpdateShares_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([]*collaborationv1beta1.ReceivedShare), args[2].(svc.UpdateShareClosure))
})
return _c
}
func (_c *DrivesDriveItemProvider_UpdateShares_Call) Return(_a0 []*collaborationv1beta1.ReceivedShare, _a1 error) *DrivesDriveItemProvider_UpdateShares_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *DrivesDriveItemProvider_UpdateShares_Call) RunAndReturn(run func(context.Context, []*collaborationv1beta1.ReceivedShare, svc.UpdateShareClosure) ([]*collaborationv1beta1.ReceivedShare, error)) *DrivesDriveItemProvider_UpdateShares_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/config"

View File

@@ -18,223 +18,276 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
)
const (
_fieldMaskPathState = "state"
_fieldMaskPathMountPoint = "mount_point"
_fieldMaskPathHidden = "hidden"
)
// DrivesDriveItemProvider is the interface that needs to be implemented by the individual space service
type DrivesDriveItemProvider interface {
MountShare(ctx context.Context, resourceID storageprovider.ResourceId, name string) (libregraph.DriveItem, error)
UnmountShare(ctx context.Context, resourceID storageprovider.ResourceId) error
}
var (
// ErrNoUpdates is returned when no updates are provided
ErrNoUpdates = errors.New("no updates")
// ErrNoUpdater is returned when no updater is provided
ErrNoUpdater = errors.New("no updater")
// ErrNoShares is returned when no shares are found
ErrNoShares = errors.New("no shares found")
// ErrAbsoluteNamePath is returned when the name is an absolute path
ErrAbsoluteNamePath = errors.New("name cannot be an absolute path")
// ErrNotAShareJail is returned when the driveID does not belong to a share jail
ErrNotAShareJail = errors.New("id does not belong to a share jail")
// ErrInvalidDriveIDOrItemID is returned when the driveID or itemID is invalid
ErrInvalidDriveIDOrItemID = errors.New("invalid driveID or itemID")
// ErrInvalidRequestBody is returned when the request body is invalid
ErrInvalidRequestBody = errors.New("invalid request body")
// ErrUnmountShare is returned when unmounting a share fails
ErrUnmountShare = errors.New("unmounting share failed")
// ErrMountShare is returned when mounting a share fails
ErrMountShare = errors.New("mounting share failed")
// ErrGetShareAndSiblings is returned when getting the share and siblings fails
ErrGetShareAndSiblings = errors.New("failed to get share and siblings")
// ErrUpdateShares is returned when updating shares fails
ErrUpdateShares = errors.New("failed to update share")
// ErrInvalidID is returned when the id is invalid
ErrInvalidID = errors.New("invalid id")
// ErrDriveItemConversion is returned when converting to drive items fails
ErrDriveItemConversion = errors.New("converting to drive items failed")
)
type (
// UpdateShareClosure is a closure that injects required updates into the update request
UpdateShareClosure func(share *collaboration.ReceivedShare, request *collaboration.UpdateReceivedShareRequest)
// DrivesDriveItemProvider is the interface that needs to be implemented by the individual space service
DrivesDriveItemProvider interface {
// MountShare mounts a share
MountShare(ctx context.Context, resourceID *storageprovider.ResourceId, name string) ([]*collaboration.ReceivedShare, error)
// UnmountShare unmounts a share
UnmountShare(ctx context.Context, shareID *collaboration.ShareId) error
// UpdateShares updates multiple shares
UpdateShares(ctx context.Context, shares []*collaboration.ReceivedShare, updater UpdateShareClosure) ([]*collaboration.ReceivedShare, error)
// GetShareAndSiblings returns the share and all its siblings
GetShareAndSiblings(ctx context.Context, shareID *collaboration.ShareId, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error)
}
)
// DrivesDriveItemService contains the production business logic for everything that relates to drives
type DrivesDriveItemService struct {
logger log.Logger
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
identityCache identity.IdentityCache
}
// NewDrivesDriveItemService creates a new DrivesDriveItemService
func NewDrivesDriveItemService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache) (DrivesDriveItemService, error) {
func NewDrivesDriveItemService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (DrivesDriveItemService, error) {
return DrivesDriveItemService{
logger: log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()},
gatewaySelector: gatewaySelector,
identityCache: identityCache,
}, nil
}
// UnmountShare unmounts a share from the share-jail
func (s DrivesDriveItemService) UnmountShare(ctx context.Context, resourceID storageprovider.ResourceId) error {
// GetShareAndSiblings returns the share and all its siblings
func (s DrivesDriveItemService) GetShareAndSiblings(ctx context.Context, shareID *collaboration.ShareId, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) {
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return err
return nil, err
}
// This is a bit of a hack. We should not rely on a specific format of the item id.
// But currently there is no other way to get the ShareID.
shareId := resourceID.GetOpaqueId()
// Now, find out the resourceID of the shared resource
getReceivedShareResponse, err := gatewayClient.GetReceivedShare(ctx,
&collaboration.GetReceivedShareRequest{
Ref: &collaboration.ShareReference{
Spec: &collaboration.ShareReference_Id{
Id: &collaboration.ShareId{
OpaqueId: shareId,
},
Id: shareID,
},
},
},
)
if err := errorcode.FromCS3Status(getReceivedShareResponse.GetStatus(), err); err != nil {
s.logger.Debug().Err(err).
Str("shareid", shareId).
Msg("failed to read share")
return err
return nil, err
}
return s.GetSharesByResourceID(ctx, getReceivedShareResponse.GetShare().GetShare().GetResourceId(), filters)
}
// GetSharesByResourceID returns all shares for a given resourceID
func (s DrivesDriveItemService) GetSharesByResourceID(ctx context.Context, resourceID *storageprovider.ResourceId, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) {
// Find all accepted shares for this resource
gatewayClient, err = s.gatewaySelector.Next()
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return err
return nil, err
}
receivedSharesResponse, err := gatewayClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{
Filters: []*collaboration.Filter{
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_ACCEPTED,
},
},
Filters: append([]*collaboration.Filter{
{
Type: collaboration.Filter_TYPE_RESOURCE_ID,
Term: &collaboration.Filter_ResourceId{
ResourceId: getReceivedShareResponse.GetShare().GetShare().GetResourceId(),
ResourceId: resourceID,
},
},
},
}, filters...),
})
if err != nil {
return err
}
if len(receivedSharesResponse.GetShares()) == 0 {
return errorcode.New(errorcode.InvalidRequest, "invalid itemID")
switch {
case err != nil:
return nil, err
case len(receivedSharesResponse.GetShares()) == 0:
return nil, ErrNoShares
default:
return receivedSharesResponse.GetShares(), errorcode.FromCS3Status(receivedSharesResponse.GetStatus(), err)
}
}
var errs []error
// UpdateShares updates multiple shares;
// it could happen that some shares are updated and some are not,
// this will return a list of updated shares and a list of errors;
// there is no guarantee that all updates are successful
func (s DrivesDriveItemService) UpdateShares(ctx context.Context, shares []*collaboration.ReceivedShare, updater UpdateShareClosure) ([]*collaboration.ReceivedShare, error) {
errs := make([]error, 0, len(shares))
updatedShares := make([]*collaboration.ReceivedShare, 0, len(shares))
// Reject all the shares for this resource
for _, receivedShare := range receivedSharesResponse.GetShares() {
receivedShare.State = collaboration.ShareState_SHARE_STATE_REJECTED
updateReceivedShareRequest := &collaboration.UpdateReceivedShareRequest{
Share: receivedShare,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{_fieldMaskPathState}},
}
_, err := gatewayClient.UpdateReceivedShare(ctx, updateReceivedShareRequest)
for _, share := range shares {
updatedShare, err := s.UpdateShare(
ctx,
share,
updater,
)
if err != nil {
errs = append(errs, err)
continue
}
updatedShares = append(updatedShares, updatedShare)
}
return errors.Join(errs...)
return updatedShares, errors.Join(errs...)
}
// MountShare mounts a share
func (s DrivesDriveItemService) MountShare(ctx context.Context, resourceID storageprovider.ResourceId, name string) (libregraph.DriveItem, error) {
if filepath.IsAbs(name) {
return libregraph.DriveItem{}, errorcode.New(errorcode.InvalidRequest, "name cannot be an absolute path")
}
if name != "" {
name = filepath.Clean(name)
}
// UpdateShare updates a single share
func (s DrivesDriveItemService) UpdateShare(ctx context.Context, share *collaboration.ReceivedShare, updater UpdateShareClosure) (*collaboration.ReceivedShare, error) {
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return libregraph.DriveItem{}, err
return nil, err
}
// Get all shares that the user has received for this resource. There might be multiple
receivedSharesResponse, err := gatewayClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{
Filters: []*collaboration.Filter{
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_PENDING,
},
updateReceivedShareRequest := &collaboration.UpdateReceivedShareRequest{
Share: &collaboration.ReceivedShare{
Share: &collaboration.Share{
Id: share.GetShare().GetId(),
},
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_REJECTED,
},
},
{
Type: collaboration.Filter_TYPE_RESOURCE_ID,
Term: &collaboration.Filter_ResourceId{
ResourceId: &resourceID,
},
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{}},
}
switch updater {
case nil:
return nil, ErrNoUpdater
default:
updater(share, updateReceivedShareRequest)
}
if len(updateReceivedShareRequest.GetUpdateMask().GetPaths()) == 0 {
return nil, ErrNoUpdates
}
updateReceivedShareResponse, err := gatewayClient.UpdateReceivedShare(ctx, updateReceivedShareRequest)
return updateReceivedShareResponse.GetShare(), errorcode.FromCS3Status(updateReceivedShareResponse.GetStatus(), err)
}
// UnmountShare unmounts a share
func (s DrivesDriveItemService) UnmountShare(ctx context.Context, shareID *collaboration.ShareId) error {
availableShares, err := s.GetShareAndSiblings(ctx, shareID, []*collaboration.Filter{
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_ACCEPTED,
},
},
})
if err != nil {
return libregraph.DriveItem{}, err
}
if len(receivedSharesResponse.GetShares()) == 0 {
return libregraph.DriveItem{}, errorcode.New(errorcode.InvalidRequest, "invalid itemID")
return err
}
var errs []error
_, err = s.UpdateShares(ctx, availableShares, func(_ *collaboration.ReceivedShare, request *collaboration.UpdateReceivedShareRequest) {
request.Share.State = collaboration.ShareState_SHARE_STATE_REJECTED
request.UpdateMask.Paths = append(request.UpdateMask.Paths, _fieldMaskPathState)
})
var acceptedShares []*collaboration.ReceivedShare
return err
}
// try to accept all the received shares for this resource. So that the stat is in sync across all
// shares
// MountShare mounts a share, there is no guarantee that all siblings will be mounted
// in some rare cases it could happen that none of the siblings could be mounted,
// then the error will be returned
func (s DrivesDriveItemService) MountShare(ctx context.Context, resourceID *storageprovider.ResourceId, name string) ([]*collaboration.ReceivedShare, error) {
if filepath.IsAbs(name) {
return nil, ErrAbsoluteNamePath
}
for _, receivedShare := range receivedSharesResponse.GetShares() {
updateMask := &fieldmaskpb.FieldMask{Paths: []string{_fieldMaskPathState}}
receivedShare.State = collaboration.ShareState_SHARE_STATE_ACCEPTED
if name != "" {
name = filepath.Clean(name)
}
availableShares, err := s.GetSharesByResourceID(ctx, resourceID, []*collaboration.Filter{
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_PENDING,
},
},
{
Type: collaboration.Filter_TYPE_STATE,
Term: &collaboration.Filter_State{
State: collaboration.ShareState_SHARE_STATE_REJECTED,
},
},
})
if err != nil {
return nil, err
}
updatedShares, err := s.UpdateShares(ctx, availableShares, func(share *collaboration.ReceivedShare, request *collaboration.UpdateReceivedShareRequest) {
request.Share.State = collaboration.ShareState_SHARE_STATE_ACCEPTED
request.UpdateMask.Paths = append(request.UpdateMask.Paths, _fieldMaskPathState)
// only update if mountPoint name is not empty and the path has changed
if name != "" {
mountPoint := receivedShare.GetMountPoint()
mountPoint := share.GetMountPoint()
if mountPoint == nil {
mountPoint = &storageprovider.Reference{}
}
if filepath.Clean(mountPoint.GetPath()) != name {
mountPoint.Path = name
receivedShare.MountPoint = mountPoint
updateMask.Paths = append(updateMask.Paths, _fieldMaskPathMountPoint)
request.Share.MountPoint = mountPoint
request.UpdateMask.Paths = append(request.UpdateMask.Paths, _fieldMaskPathMountPoint)
}
}
})
updateReceivedShareRequest := &collaboration.UpdateReceivedShareRequest{
Share: receivedShare,
UpdateMask: updateMask,
}
gatewayClient, err = s.gatewaySelector.Next()
if err != nil {
return libregraph.DriveItem{}, err
}
updateReceivedShareResponse, err := gatewayClient.UpdateReceivedShare(ctx, updateReceivedShareRequest)
switch errCode := errorcode.FromCS3Status(updateReceivedShareResponse.GetStatus(), err); {
case errCode == nil:
acceptedShares = append(acceptedShares, updateReceivedShareResponse.GetShare())
default:
// Just log at debug level here. If a single accept for any of the received shares failed this
// is not a critical problem. We mainly need to handle the case where all accepts fail. (Outside
// the loop)
s.logger.Debug().Err(errCode).
Str("shareid", receivedShare.GetShare().GetId().String()).
Str("resourceid", receivedShare.GetShare().GetResourceId().String()).
Msg("failed to accept share")
errs = append(errs, errCode)
}
errs, ok := err.(interface{ Unwrap() []error })
if ok && len(errs.Unwrap()) == len(availableShares) {
// none of the received shares could be accepted.
// this is an error, return it.
return nil, err
}
if len(receivedSharesResponse.GetShares()) == len(errs) {
// none of the received shares could be accepted. This is an error. Return it.
return libregraph.DriveItem{}, errors.Join(errs...)
}
// As the accepted shares are all for the same resource they should collapse to a single driveitem
items, err := cs3ReceivedSharesToDriveItems(ctx, &s.logger, gatewayClient, s.identityCache, acceptedShares)
switch {
case err != nil:
return libregraph.DriveItem{}, err
case len(items) != 1:
return libregraph.DriveItem{}, errorcode.New(errorcode.GeneralException, "failed to convert accepted shares into drive-item")
}
return items[0], nil
return updatedShares, nil
}
// DrivesDriveItemApi is the api that registers the http endpoints which expose needed operation to the graph api.
@@ -242,13 +295,15 @@ func (s DrivesDriveItemService) MountShare(ctx context.Context, resourceID stora
type DrivesDriveItemApi struct {
logger log.Logger
drivesDriveItemService DrivesDriveItemProvider
baseGraphService BaseGraphProvider
}
// NewDrivesDriveItemApi creates a new DrivesDriveItemApi
func NewDrivesDriveItemApi(drivesDriveItemService DrivesDriveItemProvider, logger log.Logger) (DrivesDriveItemApi, error) {
func NewDrivesDriveItemApi(drivesDriveItemService DrivesDriveItemProvider, baseGraphService BaseGraphProvider, logger log.Logger) (DrivesDriveItemApi, error) {
return DrivesDriveItemApi{
logger: log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemApi").Logger()},
drivesDriveItemService: drivesDriveItemService,
baseGraphService: baseGraphService,
}, nil
}
@@ -257,23 +312,21 @@ func (api DrivesDriveItemApi) DeleteDriveItem(w http.ResponseWriter, r *http.Req
ctx := r.Context()
driveID, itemID, err := GetDriveAndItemIDParam(r, &api.logger)
if err != nil {
msg := "invalid driveID or itemID"
api.logger.Debug().Err(err).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg)
api.logger.Debug().Err(err).Msg(ErrInvalidDriveIDOrItemID.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrInvalidDriveIDOrItemID.Error())
return
}
if !IsShareJail(driveID) {
msg := "invalid driveID, must be share jail"
api.logger.Debug().Interface("driveID", driveID).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg)
api.logger.Debug().Interface("driveID", driveID).Msg(ErrNotAShareJail.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrNotAShareJail.Error())
return
}
if err := api.drivesDriveItemService.UnmountShare(ctx, itemID); err != nil {
msg := "unmounting share failed"
api.logger.Debug().Err(err).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusFailedDependency, msg)
shareID := ExtractShareIdFromResourceId(itemID)
if err := api.drivesDriveItemService.UnmountShare(ctx, shareID); err != nil {
api.logger.Debug().Err(err).Msg(ErrUnmountShare.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusFailedDependency, ErrUnmountShare.Error())
return
}
@@ -281,49 +334,112 @@ func (api DrivesDriveItemApi) DeleteDriveItem(w http.ResponseWriter, r *http.Req
render.NoContent(w, r)
}
// UpdateDriveItem updates a drive item, currently only the visibility of the share is updated
func (api DrivesDriveItemApi) UpdateDriveItem(w http.ResponseWriter, r *http.Request) {
driveID, itemID, err := GetDriveAndItemIDParam(r, &api.logger)
if err != nil {
api.logger.Debug().Err(err).Msg(ErrInvalidDriveIDOrItemID.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrInvalidDriveIDOrItemID.Error())
return
}
if !IsShareJail(driveID) {
api.logger.Debug().Interface("driveID", driveID).Msg(ErrNotAShareJail.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrNotAShareJail.Error())
return
}
shareID := ExtractShareIdFromResourceId(itemID)
requestDriveItem := libregraph.DriveItem{}
if err := StrictJSONUnmarshal(r.Body, &requestDriveItem); err != nil {
api.logger.Debug().Err(err).Msg(ErrInvalidRequestBody.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrInvalidRequestBody.Error())
return
}
availableShares, err := api.drivesDriveItemService.GetShareAndSiblings(r.Context(), shareID, nil)
if err != nil {
api.logger.Debug().Err(err).Msg(ErrGetShareAndSiblings.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusFailedDependency, ErrGetShareAndSiblings.Error())
return
}
updatedShares, err := api.drivesDriveItemService.UpdateShares(
r.Context(),
availableShares,
func(_ *collaboration.ReceivedShare, request *collaboration.UpdateReceivedShareRequest) {
request.GetShare().Hidden = requestDriveItem.GetUIHidden()
request.UpdateMask.Paths = append(request.UpdateMask.Paths, _fieldMaskPathHidden)
},
)
switch {
case err != nil:
break
case len(updatedShares) == 0:
err = ErrNoShares
}
if err != nil {
api.logger.Debug().Err(err).Msg(ErrUpdateShares.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusFailedDependency, ErrUpdateShares.Error())
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, updatedShares[0])
}
// CreateDriveItem creates a drive item
func (api DrivesDriveItemApi) CreateDriveItem(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
driveID, err := parseIDParam(r, "driveID")
if err != nil {
api.logger.Debug().Err(err).Msg("invalid driveID")
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, "invalid driveID")
api.logger.Debug().Err(err).Msg(ErrInvalidDriveIDOrItemID.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrInvalidDriveIDOrItemID.Error())
return
}
if !IsShareJail(driveID) {
msg := "invalid driveID, must be share jail"
api.logger.Debug().Interface("driveID", driveID).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg)
api.logger.Debug().Interface("driveID", driveID).Msg(ErrNotAShareJail.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrNotAShareJail.Error())
return
}
requestDriveItem := libregraph.DriveItem{}
if err := StrictJSONUnmarshal(r.Body, &requestDriveItem); err != nil {
msg := "invalid request body"
api.logger.Debug().Err(err).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, msg)
api.logger.Debug().Err(err).Msg(ErrInvalidRequestBody.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, ErrInvalidRequestBody.Error())
return
}
remoteItem := requestDriveItem.GetRemoteItem()
resourceId, err := storagespace.ParseID(remoteItem.GetId())
if err != nil {
msg := "invalid remote item id"
api.logger.Debug().Err(err).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, msg)
api.logger.Debug().Err(err).Msg(ErrInvalidID.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, ErrInvalidID.Error())
return
}
mountShareResponse, err := api.drivesDriveItemService.
MountShare(ctx, resourceId, requestDriveItem.GetName())
mountedShares, err := api.drivesDriveItemService.
MountShare(ctx, &resourceId, requestDriveItem.GetName())
if err != nil {
msg := "mounting share failed"
api.logger.Debug().Err(err).Msg(msg)
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, msg)
api.logger.Debug().Err(err).Msg(ErrMountShare.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, ErrMountShare.Error())
return
}
driveItems, err := api.baseGraphService.CS3ReceivedSharesToDriveItems(ctx, mountedShares)
switch {
case err != nil:
break
case len(driveItems) != 1:
err = ErrDriveItemConversion
}
if err != nil {
api.logger.Debug().Err(err).Msg(ErrDriveItemConversion.Error())
errorcode.InvalidRequest.Render(w, r, http.StatusFailedDependency, ErrDriveItemConversion.Error())
return
}
render.Status(r, http.StatusCreated)
render.JSON(w, r, mountShareResponse)
render.JSON(w, r, driveItems[0])
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,11 @@ import (
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
)
// BaseGraphProvider is the interface that wraps shared methods between the different graph providers
type BaseGraphProvider interface {
CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error)
}
// BaseGraphService implements a couple of helper functions that are
// shared between the different graph services
type BaseGraphService struct {
@@ -72,6 +77,15 @@ func (g BaseGraphService) getDriveItem(ctx context.Context, ref storageprovider.
return cs3ResourceToDriveItem(g.logger, res.GetInfo())
}
func (g BaseGraphService) CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error) {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
return nil, err
}
return cs3ReceivedSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, receivedShares)
}
func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission {
if space.Opaque == nil {
return nil

View File

@@ -36,58 +36,58 @@ const (
)
// Service defines the service handlers.
type Service interface {
ServeHTTP(http.ResponseWriter, *http.Request)
type Service interface { //nolint:interfacebloat
ServeHTTP(w http.ResponseWriter, r *http.Request)
ListApplications(w http.ResponseWriter, r *http.Request)
GetApplication(http.ResponseWriter, *http.Request)
GetApplication(w http.ResponseWriter, r *http.Request)
GetMe(http.ResponseWriter, *http.Request)
GetUsers(http.ResponseWriter, *http.Request)
GetUser(http.ResponseWriter, *http.Request)
PostUser(http.ResponseWriter, *http.Request)
DeleteUser(http.ResponseWriter, *http.Request)
PatchUser(http.ResponseWriter, *http.Request)
ChangeOwnPassword(http.ResponseWriter, *http.Request)
GetMe(w http.ResponseWriter, r *http.Request)
GetUsers(w http.ResponseWriter, r *http.Request)
GetUser(w http.ResponseWriter, r *http.Request)
PostUser(w http.ResponseWriter, r *http.Request)
DeleteUser(w http.ResponseWriter, r *http.Request)
PatchUser(w http.ResponseWriter, r *http.Request)
ChangeOwnPassword(w http.ResponseWriter, r *http.Request)
ListAppRoleAssignments(http.ResponseWriter, *http.Request)
CreateAppRoleAssignment(http.ResponseWriter, *http.Request)
DeleteAppRoleAssignment(http.ResponseWriter, *http.Request)
ListAppRoleAssignments(w http.ResponseWriter, r *http.Request)
CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request)
DeleteAppRoleAssignment(w http.ResponseWriter, r *http.Request)
GetGroups(http.ResponseWriter, *http.Request)
GetGroup(http.ResponseWriter, *http.Request)
PostGroup(http.ResponseWriter, *http.Request)
PatchGroup(http.ResponseWriter, *http.Request)
DeleteGroup(http.ResponseWriter, *http.Request)
GetGroupMembers(http.ResponseWriter, *http.Request)
PostGroupMember(http.ResponseWriter, *http.Request)
DeleteGroupMember(http.ResponseWriter, *http.Request)
GetGroups(w http.ResponseWriter, r *http.Request)
GetGroup(w http.ResponseWriter, r *http.Request)
PostGroup(w http.ResponseWriter, r *http.Request)
PatchGroup(w http.ResponseWriter, r *http.Request)
DeleteGroup(w http.ResponseWriter, r *http.Request)
GetGroupMembers(w http.ResponseWriter, r *http.Request)
PostGroupMember(w http.ResponseWriter, r *http.Request)
DeleteGroupMember(w http.ResponseWriter, r *http.Request)
GetEducationSchools(http.ResponseWriter, *http.Request)
GetEducationSchool(http.ResponseWriter, *http.Request)
PostEducationSchool(http.ResponseWriter, *http.Request)
PatchEducationSchool(http.ResponseWriter, *http.Request)
DeleteEducationSchool(http.ResponseWriter, *http.Request)
GetEducationSchoolUsers(http.ResponseWriter, *http.Request)
PostEducationSchoolUser(http.ResponseWriter, *http.Request)
DeleteEducationSchoolUser(http.ResponseWriter, *http.Request)
GetEducationSchoolClasses(http.ResponseWriter, *http.Request)
PostEducationSchoolClass(http.ResponseWriter, *http.Request)
DeleteEducationSchoolClass(http.ResponseWriter, *http.Request)
GetEducationSchools(w http.ResponseWriter, r *http.Request)
GetEducationSchool(w http.ResponseWriter, r *http.Request)
PostEducationSchool(w http.ResponseWriter, r *http.Request)
PatchEducationSchool(w http.ResponseWriter, r *http.Request)
DeleteEducationSchool(w http.ResponseWriter, r *http.Request)
GetEducationSchoolUsers(w http.ResponseWriter, r *http.Request)
PostEducationSchoolUser(w http.ResponseWriter, r *http.Request)
DeleteEducationSchoolUser(w http.ResponseWriter, r *http.Request)
GetEducationSchoolClasses(w http.ResponseWriter, r *http.Request)
PostEducationSchoolClass(w http.ResponseWriter, r *http.Request)
DeleteEducationSchoolClass(w http.ResponseWriter, r *http.Request)
GetEducationClasses(http.ResponseWriter, *http.Request)
GetEducationClass(http.ResponseWriter, *http.Request)
PostEducationClass(http.ResponseWriter, *http.Request)
PatchEducationClass(http.ResponseWriter, *http.Request)
GetEducationClasses(w http.ResponseWriter, r *http.Request)
GetEducationClass(w http.ResponseWriter, r *http.Request)
PostEducationClass(w http.ResponseWriter, r *http.Request)
PatchEducationClass(w http.ResponseWriter, r *http.Request)
DeleteEducationClass(w http.ResponseWriter, r *http.Request)
GetEducationClassMembers(w http.ResponseWriter, r *http.Request)
PostEducationClassMember(w http.ResponseWriter, r *http.Request)
GetEducationUsers(http.ResponseWriter, *http.Request)
GetEducationUser(http.ResponseWriter, *http.Request)
PostEducationUser(http.ResponseWriter, *http.Request)
DeleteEducationUser(http.ResponseWriter, *http.Request)
PatchEducationUser(http.ResponseWriter, *http.Request)
GetEducationUsers(w http.ResponseWriter, r *http.Request)
GetEducationUser(w http.ResponseWriter, r *http.Request)
PostEducationUser(w http.ResponseWriter, r *http.Request)
DeleteEducationUser(w http.ResponseWriter, r *http.Request)
PatchEducationUser(w http.ResponseWriter, r *http.Request)
DeleteEducationClassMember(w http.ResponseWriter, r *http.Request)
GetEducationClassTeachers(w http.ResponseWriter, r *http.Request)
@@ -118,7 +118,7 @@ type Service interface {
}
// NewService returns a service implementation for Service.
func NewService(opts ...Option) (Graph, error) {
func NewService(opts ...Option) (Graph, error) { //nolint:maintidx
options := newOptions(opts...)
m := chi.NewMux()
@@ -199,12 +199,12 @@ func NewService(opts ...Option) (Graph, error) {
requireAdmin = options.RequireAdminMiddleware
}
drivesDriveItemService, err := NewDrivesDriveItemService(options.Logger, options.GatewaySelector, identityCache)
drivesDriveItemService, err := NewDrivesDriveItemService(options.Logger, options.GatewaySelector)
if err != nil {
return svc, err
}
drivesDriveItemApi, err := NewDrivesDriveItemApi(drivesDriveItemService, options.Logger)
drivesDriveItemApi, err := NewDrivesDriveItemApi(drivesDriveItemService, svc.BaseGraphService, options.Logger)
if err != nil {
return svc, err
}
@@ -247,6 +247,7 @@ func NewService(opts ...Option) (Graph, error) {
})
})
r.Route("/items/{itemID}", func(r chi.Router) {
r.Patch("/", drivesDriveItemApi.UpdateDriveItem)
r.Delete("/", drivesDriveItemApi.DeleteDriveItem)
r.Post("/invite", driveItemPermissionsApi.Invite)
r.Post("/createLink", driveItemPermissionsApi.CreateLink)

View File

@@ -12,9 +12,10 @@ import (
cs3User "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"golang.org/x/sync/errgroup"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"golang.org/x/sync/errgroup"
libregraph "github.com/owncloud/libre-graph-api-go"
@@ -153,6 +154,10 @@ func cs3ReceivedSharesToDriveItems(ctx context.Context,
receivedSharesByResourceID := make(map[string][]*collaboration.ReceivedShare, len(receivedShares))
for _, receivedShare := range receivedShares {
if receivedShare == nil {
continue
}
rIDStr := storagespace.FormatResourceID(*receivedShare.GetShare().GetResourceId())
receivedSharesByResourceID[rIDStr] = append(receivedSharesByResourceID[rIDStr], receivedShare)
}
@@ -454,3 +459,12 @@ func roleConditionForResourceType(ri *storageprovider.ResourceInfo) (string, err
return "", errorcode.New(errorcode.InvalidRequest, "unsupported resource type")
}
}
// ExtractShareIdFromResourceId is a bit of a hack.
// We should not rely on a specific format of the item id.
// But currently there is no other way to get the ShareID.
func ExtractShareIdFromResourceId(rid storageprovider.ResourceId) *collaboration.ShareId {
return &collaboration.ShareId{
OpaqueId: rid.GetOpaqueId(),
}
}