mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-24 16:41:35 -04:00
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:
11
changelog/unreleased/graph-item-visibility-api.md
Normal file
11
changelog/unreleased/graph-item-visibility-api.md
Normal 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
|
||||
@@ -7,6 +7,7 @@ packages:
|
||||
config:
|
||||
dir: "mocks"
|
||||
interfaces:
|
||||
BaseGraphProvider:
|
||||
DrivesDriveItemProvider:
|
||||
DriveItemPermissionsProvider:
|
||||
HTTPClient:
|
||||
|
||||
99
services/graph/mocks/base_graph_provider.go
Normal file
99
services/graph/mocks/base_graph_provider.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user