From fdab4dd1746d523d51d1fd19408dcb4e80b6032b Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 3 Jun 2025 14:50:52 +0200 Subject: [PATCH 1/2] graph: Add support for counting permissions To just get the number of permissions set on a share use: `/drives/id/root/permissions?$count=true&$top=0` Related issue: #485 --- .../mocks/drive_item_permissions_provider.go | 63 +++++++------ .../service/v0/api_driveitem_permissions.go | 94 ++++++++++++++----- .../v0/api_driveitem_permissions_test.go | 13 ++- services/graph/pkg/service/v0/base.go | 24 +++-- services/graph/pkg/service/v0/drives.go | 4 +- 5 files changed, 125 insertions(+), 73 deletions(-) diff --git a/services/graph/mocks/drive_item_permissions_provider.go b/services/graph/mocks/drive_item_permissions_provider.go index afaf5ba5eb..cd2429b015 100644 --- a/services/graph/mocks/drive_item_permissions_provider.go +++ b/services/graph/mocks/drive_item_permissions_provider.go @@ -9,6 +9,8 @@ import ( mock "github.com/stretchr/testify/mock" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + + svc "github.com/opencloud-eu/opencloud/services/graph/pkg/service/v0" ) // DriveItemPermissionsProvider is an autogenerated mock type for the DriveItemPermissionsProvider type @@ -294,9 +296,9 @@ func (_c *DriveItemPermissionsProvider_Invite_Call) RunAndReturn(run func(contex return _c } -// ListPermissions provides a mock function with given fields: ctx, itemID, listFederatedRoles, selectedAttrs -func (_m *DriveItemPermissionsProvider) ListPermissions(ctx context.Context, itemID *providerv1beta1.ResourceId, listFederatedRoles bool, selectedAttrs []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { - ret := _m.Called(ctx, itemID, listFederatedRoles, selectedAttrs) +// ListPermissions provides a mock function with given fields: ctx, itemID, queryOptions +func (_m *DriveItemPermissionsProvider) ListPermissions(ctx context.Context, itemID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { + ret := _m.Called(ctx, itemID, queryOptions) if len(ret) == 0 { panic("no return value specified for ListPermissions") @@ -304,17 +306,17 @@ func (_m *DriveItemPermissionsProvider) ListPermissions(ctx context.Context, ite var r0 libregraph.CollectionOfPermissionsWithAllowedValues var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, bool, []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok { - return rf(ctx, itemID, listFederatedRoles, selectedAttrs) + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok { + return rf(ctx, itemID, queryOptions) } - if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, bool, []string) libregraph.CollectionOfPermissionsWithAllowedValues); ok { - r0 = rf(ctx, itemID, listFederatedRoles, selectedAttrs) + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) libregraph.CollectionOfPermissionsWithAllowedValues); ok { + r0 = rf(ctx, itemID, queryOptions) } else { r0 = ret.Get(0).(libregraph.CollectionOfPermissionsWithAllowedValues) } - if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, bool, []string) error); ok { - r1 = rf(ctx, itemID, listFederatedRoles, selectedAttrs) + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) error); ok { + r1 = rf(ctx, itemID, queryOptions) } else { r1 = ret.Error(1) } @@ -330,15 +332,14 @@ type DriveItemPermissionsProvider_ListPermissions_Call struct { // ListPermissions is a helper method to define mock.On call // - ctx context.Context // - itemID *providerv1beta1.ResourceId -// - listFederatedRoles bool -// - selectedAttrs []string -func (_e *DriveItemPermissionsProvider_Expecter) ListPermissions(ctx interface{}, itemID interface{}, listFederatedRoles interface{}, selectedAttrs interface{}) *DriveItemPermissionsProvider_ListPermissions_Call { - return &DriveItemPermissionsProvider_ListPermissions_Call{Call: _e.mock.On("ListPermissions", ctx, itemID, listFederatedRoles, selectedAttrs)} +// - queryOptions svc.ListPermissionsQueryOptions +func (_e *DriveItemPermissionsProvider_Expecter) ListPermissions(ctx interface{}, itemID interface{}, queryOptions interface{}) *DriveItemPermissionsProvider_ListPermissions_Call { + return &DriveItemPermissionsProvider_ListPermissions_Call{Call: _e.mock.On("ListPermissions", ctx, itemID, queryOptions)} } -func (_c *DriveItemPermissionsProvider_ListPermissions_Call) Run(run func(ctx context.Context, itemID *providerv1beta1.ResourceId, listFederatedRoles bool, selectedAttrs []string)) *DriveItemPermissionsProvider_ListPermissions_Call { +func (_c *DriveItemPermissionsProvider_ListPermissions_Call) Run(run func(ctx context.Context, itemID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions)) *DriveItemPermissionsProvider_ListPermissions_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(bool), args[3].([]string)) + run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(svc.ListPermissionsQueryOptions)) }) return _c } @@ -348,14 +349,14 @@ func (_c *DriveItemPermissionsProvider_ListPermissions_Call) Return(_a0 libregra return _c } -func (_c *DriveItemPermissionsProvider_ListPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, bool, []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListPermissions_Call { +func (_c *DriveItemPermissionsProvider_ListPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListPermissions_Call { _c.Call.Return(run) return _c } -// ListSpaceRootPermissions provides a mock function with given fields: ctx, driveID, selectedAttrs -func (_m *DriveItemPermissionsProvider) ListSpaceRootPermissions(ctx context.Context, driveID *providerv1beta1.ResourceId, selectedAttrs []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { - ret := _m.Called(ctx, driveID, selectedAttrs) +// ListSpaceRootPermissions provides a mock function with given fields: ctx, driveID, queryOptions +func (_m *DriveItemPermissionsProvider) ListSpaceRootPermissions(ctx context.Context, driveID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { + ret := _m.Called(ctx, driveID, queryOptions) if len(ret) == 0 { panic("no return value specified for ListSpaceRootPermissions") @@ -363,17 +364,17 @@ func (_m *DriveItemPermissionsProvider) ListSpaceRootPermissions(ctx context.Con var r0 libregraph.CollectionOfPermissionsWithAllowedValues var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok { - return rf(ctx, driveID, selectedAttrs) + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok { + return rf(ctx, driveID, queryOptions) } - if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, []string) libregraph.CollectionOfPermissionsWithAllowedValues); ok { - r0 = rf(ctx, driveID, selectedAttrs) + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) libregraph.CollectionOfPermissionsWithAllowedValues); ok { + r0 = rf(ctx, driveID, queryOptions) } else { r0 = ret.Get(0).(libregraph.CollectionOfPermissionsWithAllowedValues) } - if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, []string) error); ok { - r1 = rf(ctx, driveID, selectedAttrs) + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) error); ok { + r1 = rf(ctx, driveID, queryOptions) } else { r1 = ret.Error(1) } @@ -389,14 +390,14 @@ type DriveItemPermissionsProvider_ListSpaceRootPermissions_Call struct { // ListSpaceRootPermissions is a helper method to define mock.On call // - ctx context.Context // - driveID *providerv1beta1.ResourceId -// - selectedAttrs []string -func (_e *DriveItemPermissionsProvider_Expecter) ListSpaceRootPermissions(ctx interface{}, driveID interface{}, selectedAttrs interface{}) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call { - return &DriveItemPermissionsProvider_ListSpaceRootPermissions_Call{Call: _e.mock.On("ListSpaceRootPermissions", ctx, driveID, selectedAttrs)} +// - queryOptions svc.ListPermissionsQueryOptions +func (_e *DriveItemPermissionsProvider_Expecter) ListSpaceRootPermissions(ctx interface{}, driveID interface{}, queryOptions interface{}) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call { + return &DriveItemPermissionsProvider_ListSpaceRootPermissions_Call{Call: _e.mock.On("ListSpaceRootPermissions", ctx, driveID, queryOptions)} } -func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) Run(run func(ctx context.Context, driveID *providerv1beta1.ResourceId, selectedAttrs []string)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call { +func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) Run(run func(ctx context.Context, driveID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_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].(svc.ListPermissionsQueryOptions)) }) return _c } @@ -406,7 +407,7 @@ func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) Return(_a0 return _c } -func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call { +func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call { _c.Call.Return(run) return _c } diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index 4ef7ba8033..eac95fb3cf 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -52,8 +52,8 @@ const ( type DriveItemPermissionsProvider interface { Invite(ctx context.Context, resourceId *storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) SpaceRootInvite(ctx context.Context, driveID *storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) - ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, listFederatedRoles bool, selectedAttrs []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) - ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, selectedAttrs []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) + ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) + ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) DeletePermission(ctx context.Context, itemID *storageprovider.ResourceId, permissionID string) error DeleteSpaceRootPermission(ctx context.Context, driveID *storageprovider.ResourceId, permissionID string) error UpdatePermission(ctx context.Context, itemID *storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) @@ -79,6 +79,13 @@ const ( OCM ) +type ListPermissionsQueryOptions struct { + count bool + noValues bool + filterFederatedRoles bool + selectedAttrs []string +} + // NewDriveItemPermissionsService creates a new DriveItemPermissionsService func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, config *config.Config) (DriveItemPermissionsService, error) { return DriveItemPermissionsService{ @@ -345,7 +352,7 @@ func (s DriveItemPermissionsService) SpaceRootInvite(ctx context.Context, driveI } // ListPermissions lists the permissions of a driveItem -func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, listFederatedRoles bool, selectedAttrs []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { +func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { collectionOfPermissions := libregraph.CollectionOfPermissionsWithAllowedValues{} gatewayClient, err := s.gatewaySelector.Next() if err != nil { @@ -368,17 +375,17 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID collectionOfPermissions = libregraph.CollectionOfPermissionsWithAllowedValues{} - if len(selectedAttrs) == 0 || slices.Contains(selectedAttrs, "@libre.graph.permissions.actions.allowedValues") { + if len(queryOptions.selectedAttrs) == 0 || slices.Contains(queryOptions.selectedAttrs, "@libre.graph.permissions.actions.allowedValues") { collectionOfPermissions.LibreGraphPermissionsActionsAllowedValues = allowedActions } - if len(selectedAttrs) == 0 || slices.Contains(selectedAttrs, "@libre.graph.permissions.roles.allowedValues") { + if len(queryOptions.selectedAttrs) == 0 || slices.Contains(queryOptions.selectedAttrs, "@libre.graph.permissions.roles.allowedValues") { collectionOfPermissions.LibreGraphPermissionsRolesAllowedValues = conversions.ToValueSlice( unifiedrole.GetRolesByPermissions( unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(s.config.UnifiedRoles.AvailableRoles...)), allowedActions, condition, - listFederatedRoles, + queryOptions.filterFederatedRoles, false, ), ) @@ -390,7 +397,7 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID collectionOfPermissions.LibreGraphPermissionsRolesAllowedValues[i] = definition } - if len(selectedAttrs) > 0 { + if len(queryOptions.selectedAttrs) > 0 { // no need to fetch shares, we are only interested allowedActions and/or allowedRoles return collectionOfPermissions, nil } @@ -403,8 +410,11 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID } driveItems[storagespace.FormatResourceID(statResponse.GetInfo().GetId())] = *item + var permissionsCount int + if IsSpaceRoot(statResponse.GetInfo().GetId()) { - permissions, err := s.getSpaceRootPermissions(ctx, statResponse.GetInfo().GetSpace().GetId()) + var permissions []libregraph.Permission + permissions, permissionsCount, err = s.getSpaceRootPermissions(ctx, statResponse.GetInfo().GetSpace().GetId(), queryOptions.noValues) if err != nil { return collectionOfPermissions, err } @@ -438,14 +448,21 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID } for _, driveItem := range driveItems { - collectionOfPermissions.Value = append(collectionOfPermissions.Value, driveItem.Permissions...) + permissionsCount += len(driveItem.Permissions) + if !queryOptions.noValues { + collectionOfPermissions.Value = append(collectionOfPermissions.Value, driveItem.Permissions...) + } + } + + if queryOptions.count { + collectionOfPermissions.SetOdataCount(int32(permissionsCount)) } return collectionOfPermissions, nil } // ListSpaceRootPermissions handles ListPermissions request on project spaces -func (s DriveItemPermissionsService) ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, selectedAttrs []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { +func (s DriveItemPermissionsService) ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { collectionOfPermissions := libregraph.CollectionOfPermissionsWithAllowedValues{} gatewayClient, err := s.gatewaySelector.Next() if err != nil { @@ -463,7 +480,7 @@ func (s DriveItemPermissionsService) ListSpaceRootPermissions(ctx context.Contex } rootResourceID := space.GetRoot() - return s.ListPermissions(ctx, rootResourceID, false, selectedAttrs) // federated roles are not supported for spaces + return s.ListPermissions(ctx, rootResourceID, queryOptions) // federated roles are not supported for spaces } // DeletePermission deletes a permission from a drive item @@ -716,23 +733,17 @@ func (api DriveItemPermissionsApi) ListPermissions(w http.ResponseWriter, r *htt return } - var listFederatedRoles bool - if odataReq.Query.Filter != nil { - if odataReq.Query.Filter.RawValue == federatedRolesODataFilter { - listFederatedRoles = true - } - } - - selectAttrs, err := odata.GetSelectValues(odataReq.Query) + var queryOptions ListPermissionsQueryOptions + queryOptions, err = api.getListPermissionsQueryOptions(odataReq) if err != nil { - api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest: query error") + api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest query options") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) return } ctx := r.Context() - permissions, err := api.driveItemPermissionsService.ListPermissions(ctx, itemID, listFederatedRoles, selectAttrs) + permissions, err := api.driveItemPermissionsService.ListPermissions(ctx, itemID, queryOptions) if err != nil { errorcode.RenderError(w, r, err) return @@ -773,15 +784,16 @@ func (api DriveItemPermissionsApi) ListSpaceRootPermissions(w http.ResponseWrite return } - selected, err := odata.GetSelectValues(odataReq.Query) + var queryOptions ListPermissionsQueryOptions + queryOptions, err = api.getListPermissionsQueryOptions(odataReq) if err != nil { - api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest: query error") + api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest query options") errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) return } ctx := r.Context() - permissions, err := api.driveItemPermissionsService.ListSpaceRootPermissions(ctx, &driveID, selected) + permissions, err := api.driveItemPermissionsService.ListSpaceRootPermissions(ctx, &driveID, queryOptions) if err != nil { errorcode.RenderError(w, r, err) @@ -806,6 +818,40 @@ func (api DriveItemPermissionsApi) ListSpaceRootPermissions(w http.ResponseWrite render.JSON(w, r, permissions) } +func (api DriveItemPermissionsApi) getListPermissionsQueryOptions(odataReq *godata.GoDataRequest) (ListPermissionsQueryOptions, error) { + var listFederatedRoles bool + if odataReq.Query.Filter != nil { + if odataReq.Query.Filter.RawValue == federatedRolesODataFilter { + listFederatedRoles = true + } + } + + selectAttrs, err := odata.GetSelectValues(odataReq.Query) + if err != nil { + return ListPermissionsQueryOptions{}, err + } + + queryOptions := ListPermissionsQueryOptions{ + filterFederatedRoles: listFederatedRoles, + selectedAttrs: selectAttrs, + } + if odataReq.Query.Count != nil { + queryOptions.count = bool(*odataReq.Query.Count) + } + if odataReq.Query.Top != nil { + top := int(*odataReq.Query.Top) + switch { + case top != 0: + return ListPermissionsQueryOptions{}, err + case top == 0 && !queryOptions.count: + return ListPermissionsQueryOptions{}, err + default: + queryOptions.noValues = true + } + } + return queryOptions, nil +} + // DeletePermission handles DeletePermission requests func (api DriveItemPermissionsApi) DeletePermission(w http.ResponseWriter, r *http.Request) { _, itemID, err := GetDriveAndItemIDParam(r, &api.logger) diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go index 4a7c908b64..52603c51ff 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go @@ -385,7 +385,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil) gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) - permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, false, []string{}) + permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{}) Expect(err).ToNot(HaveOccurred()) Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero()) Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero()) @@ -433,7 +433,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil) gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) - permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, false, []string{}) + permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{}) Expect(err).ToNot(HaveOccurred()) Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero()) Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero()) @@ -472,7 +472,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil) gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) - permissions, err := service.ListPermissions(context.Background(), itemID, false, []string{}) + permissions, err := service.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{}) Expect(err).ToNot(HaveOccurred()) Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero()) Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero()) @@ -508,7 +508,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil) gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) - permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, false, []string{}) + permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{}) Expect(err).ToNot(HaveOccurred()) Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero()) Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero()) @@ -555,7 +555,7 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) - permissions, err := driveItemPermissionsService.ListSpaceRootPermissions(context.Background(), driveId, []string{}) + permissions, err := driveItemPermissionsService.ListSpaceRootPermissions(context.Background(), driveId, svc.ListPermissionsQueryOptions{}) Expect(err).ToNot(HaveOccurred()) Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero()) }) @@ -1268,8 +1268,7 @@ var _ = Describe("DriveItemPermissionsApi", func() { Expect(err).ToNot(HaveOccurred()) mockProvider.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(func(ctx context.Context, itemid *provider.ResourceId, listFederatedRoles bool, selected []string) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { - Expect(listFederatedRoles).To(Equal(false)) + Return(func(ctx context.Context, itemid *provider.ResourceId, opt svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) { Expect(storagespace.FormatResourceID(itemid)).To(Equal("1$2!3")) return libregraph.CollectionOfPermissionsWithAllowedValues{}, nil }).Once() diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go index 5578ee88b9..ba07bc9dd4 100644 --- a/services/graph/pkg/service/v0/base.go +++ b/services/graph/pkg/service/v0/base.go @@ -49,19 +49,20 @@ type BaseGraphService struct { availableRoles []*libregraph.UnifiedRoleDefinition } -func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId) ([]libregraph.Permission, error) { +func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId, countOnly bool) ([]libregraph.Permission, int, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - return nil, err + return nil, 0, err } space, err := utils.GetSpace(ctx, spaceID.GetOpaqueId(), gatewayClient) if err != nil { - return nil, errorcode.FromUtilsStatusCodeError(err) + return nil, 0, errorcode.FromUtilsStatusCodeError(err) } - return g.cs3SpacePermissionsToLibreGraph(ctx, space, APIVersion_1_Beta_1), nil + perm, count := g.cs3SpacePermissionsToLibreGraph(ctx, space, countOnly, APIVersion_1_Beta_1) + return perm, count, nil } func (g BaseGraphService) getDriveItem(ctx context.Context, ref *storageprovider.Reference) (*libregraph.DriveItem, error) { @@ -99,9 +100,9 @@ func (g BaseGraphService) CS3ReceivedOCMSharesToDriveItems(ctx context.Context, return cs3ReceivedOCMSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, receivedShares, g.availableRoles) } -func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission { +func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, countOnly bool, apiVersion APIVersion) ([]libregraph.Permission, int) { if space.Opaque == nil { - return nil + return nil, 0 } logger := g.logger.SubloggerWithRequestID(ctx) @@ -118,7 +119,12 @@ func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, s } } if len(permissionsMap) == 0 { - return nil + return nil, 0 + } + + if countOnly { + // If we only need the count, we can return early + return nil, len(permissionsMap) } var permissionsExpirations map[string]*types.Timestamp @@ -219,7 +225,7 @@ func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, s permissions = append(permissions, p) } - return permissions + return permissions, len(permissions) } func (g BaseGraphService) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicShare) (*libregraph.Permission, error) { @@ -1068,7 +1074,7 @@ func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID st return nil, nil, err } - perms, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId()) + perms, _, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId(), false) if err != nil { return nil, nil, err } diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 49eca13907..490bf13583 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -20,10 +20,10 @@ import ( storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/go-chi/render" + libregraph "github.com/opencloud-eu/libre-graph-api-go" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/storagespace" "github.com/opencloud-eu/reva/v2/pkg/utils" - libregraph "github.com/opencloud-eu/libre-graph-api-go" "github.com/pkg/errors" merrors "go-micro.dev/v4/errors" "golang.org/x/sync/errgroup" @@ -796,7 +796,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa }, } if expandPermissions { - drive.Root.Permissions = g.cs3SpacePermissionsToLibreGraph(ctx, space, apiVersion) + drive.Root.Permissions, _ = g.cs3SpacePermissionsToLibreGraph(ctx, space, false, apiVersion) } if space.SpaceType == _spaceTypeMountpoint { From daafbc9119ddbe57277f55388cc68ed00f281f63 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 3 Jun 2025 14:53:40 +0200 Subject: [PATCH 2/2] Bump libre-graph-api for $count support on the permissions endpoints --- go.mod | 2 +- go.sum | 4 +- .../api_drives_get_drives.go | 10 +++++ .../api_drives_permissions.go | 20 ++++++++++ .../libre-graph-api-go/api_drives_root.go | 20 ++++++++++ .../libre-graph-api-go/api_me_drives.go | 10 +++++ ...tion_of_permissions_with_allowed_values.go | 37 +++++++++++++++++++ vendor/modules.txt | 2 +- 8 files changed, 101 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 9643dd4f22..e0b88c8766 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/gomega v1.37.0 github.com/open-policy-agent/opa v1.5.0 - github.com/opencloud-eu/libre-graph-api-go v1.0.7 + github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9 github.com/orcaman/concurrent-map v1.0.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 58f3f47c91..5741de7f36 100644 --- a/go.sum +++ b/go.sum @@ -867,8 +867,8 @@ github.com/open-policy-agent/opa v1.5.0 h1:npsQMUZvafCLYHofoNrZ0cSWbvoDpasvWtrHX github.com/open-policy-agent/opa v1.5.0/go.mod h1:bYbS7u+uhTI+cxHQIpzvr5hxX0hV7urWtY+38ZtjMgk= github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M= github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY= -github.com/opencloud-eu/libre-graph-api-go v1.0.7 h1:xP8xlRc6z+gROmQYWohIXn1GtaORQKh8PN7grF3Z790= -github.com/opencloud-eu/libre-graph-api-go v1.0.7/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q= +github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 h1:QWn9G2f1R/EbyZSbkjtd9jqNq9X0NIphmmD4KYLNZtA= +github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q= github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9 h1:7y8gTqVQSXLyAqeUFesbI58OkgGcS5fmfq2f3e95XOI= github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9/go.mod h1:8S3B+GPFdGMcNL/pkSHI4K2/E0ICvR7qxllE7Ooydm8= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= diff --git a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_get_drives.go b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_get_drives.go index 4375d28d75..156bb36746 100644 --- a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_get_drives.go +++ b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_get_drives.go @@ -152,6 +152,7 @@ type ApiListAllDrivesBetaRequest struct { ApiService *DrivesGetDrivesApiService orderby *string filter *string + expand *string } // The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc. @@ -166,6 +167,12 @@ func (r ApiListAllDrivesBetaRequest) Filter(filter string) ApiListAllDrivesBetaR return r } +// Expand related entities +func (r ApiListAllDrivesBetaRequest) Expand(expand string) ApiListAllDrivesBetaRequest { + r.expand = &expand + return r +} + func (r ApiListAllDrivesBetaRequest) Execute() (*CollectionOfDrives1, *http.Response, error) { return r.ApiService.ListAllDrivesBetaExecute(r) } @@ -210,6 +217,9 @@ func (a *DrivesGetDrivesApiService) ListAllDrivesBetaExecute(r ApiListAllDrivesB if r.filter != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "$filter", r.filter, "form", "") } + if r.expand != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$expand", r.expand, "form", "") + } // to determine the Content-Type header localVarHTTPContentTypes := []string{} diff --git a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_permissions.go b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_permissions.go index 45365986c0..aa17b8e30d 100644 --- a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_permissions.go +++ b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_permissions.go @@ -542,6 +542,8 @@ type ApiListPermissionsRequest struct { itemId string filter *string select_ *[]string + count *bool + top *int32 } // Filter items by property values. By default all permissions are returned and the avalable sharing roles are limited to normal users. To get a list of sharing roles applicable to federated users use the example $select query and combine it with $filter to omit the list of permissions. @@ -556,6 +558,18 @@ func (r ApiListPermissionsRequest) Select_(select_ []string) ApiListPermissionsR return r } +// Include count of items +func (r ApiListPermissionsRequest) Count(count bool) ApiListPermissionsRequest { + r.count = &count + return r +} + +// Show only the first n items +func (r ApiListPermissionsRequest) Top(top int32) ApiListPermissionsRequest { + r.top = &top + return r +} + func (r ApiListPermissionsRequest) Execute() (*CollectionOfPermissionsWithAllowedValues, *http.Response, error) { return r.ApiService.ListPermissionsExecute(r) } @@ -617,6 +631,12 @@ func (a *DrivesPermissionsApiService) ListPermissionsExecute(r ApiListPermission if r.select_ != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "$select", r.select_, "form", "csv") } + if r.count != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$count", r.count, "form", "") + } + if r.top != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$top", r.top, "form", "") + } // to determine the Content-Type header localVarHTTPContentTypes := []string{} diff --git a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_root.go b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_root.go index 4eaa3a1b91..9c3ec31beb 100644 --- a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_root.go +++ b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_drives_root.go @@ -755,6 +755,8 @@ type ApiListPermissionsSpaceRootRequest struct { driveId string filter *string select_ *[]string + count *bool + top *int32 } // Filter items by property values. By default all permissions are returned and the avalable sharing roles are limited to normal users. To get a list of sharing roles applicable to federated users use the example $select query and combine it with $filter to omit the list of permissions. @@ -769,6 +771,18 @@ func (r ApiListPermissionsSpaceRootRequest) Select_(select_ []string) ApiListPer return r } +// Include count of items +func (r ApiListPermissionsSpaceRootRequest) Count(count bool) ApiListPermissionsSpaceRootRequest { + r.count = &count + return r +} + +// Show only the first n items +func (r ApiListPermissionsSpaceRootRequest) Top(top int32) ApiListPermissionsSpaceRootRequest { + r.top = &top + return r +} + func (r ApiListPermissionsSpaceRootRequest) Execute() (*CollectionOfPermissionsWithAllowedValues, *http.Response, error) { return r.ApiService.ListPermissionsSpaceRootExecute(r) } @@ -827,6 +841,12 @@ func (a *DrivesRootApiService) ListPermissionsSpaceRootExecute(r ApiListPermissi if r.select_ != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "$select", r.select_, "form", "csv") } + if r.count != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$count", r.count, "form", "") + } + if r.top != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$top", r.top, "form", "") + } // to determine the Content-Type header localVarHTTPContentTypes := []string{} diff --git a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_me_drives.go b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_me_drives.go index 4c05152689..5f34db7c8e 100644 --- a/vendor/github.com/opencloud-eu/libre-graph-api-go/api_me_drives.go +++ b/vendor/github.com/opencloud-eu/libre-graph-api-go/api_me_drives.go @@ -152,6 +152,7 @@ type ApiListMyDrivesBetaRequest struct { ApiService *MeDrivesApiService orderby *string filter *string + expand *string } // The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc. @@ -166,6 +167,12 @@ func (r ApiListMyDrivesBetaRequest) Filter(filter string) ApiListMyDrivesBetaReq return r } +// Expand related entities +func (r ApiListMyDrivesBetaRequest) Expand(expand string) ApiListMyDrivesBetaRequest { + r.expand = &expand + return r +} + func (r ApiListMyDrivesBetaRequest) Execute() (*CollectionOfDrives, *http.Response, error) { return r.ApiService.ListMyDrivesBetaExecute(r) } @@ -210,6 +217,9 @@ func (a *MeDrivesApiService) ListMyDrivesBetaExecute(r ApiListMyDrivesBetaReques if r.filter != nil { parameterAddToHeaderOrQuery(localVarQueryParams, "$filter", r.filter, "form", "") } + if r.expand != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$expand", r.expand, "form", "") + } // to determine the Content-Type header localVarHTTPContentTypes := []string{} diff --git a/vendor/github.com/opencloud-eu/libre-graph-api-go/model_collection_of_permissions_with_allowed_values.go b/vendor/github.com/opencloud-eu/libre-graph-api-go/model_collection_of_permissions_with_allowed_values.go index 339c79da1c..2293e7699b 100644 --- a/vendor/github.com/opencloud-eu/libre-graph-api-go/model_collection_of_permissions_with_allowed_values.go +++ b/vendor/github.com/opencloud-eu/libre-graph-api-go/model_collection_of_permissions_with_allowed_values.go @@ -24,6 +24,8 @@ type CollectionOfPermissionsWithAllowedValues struct { // A list of actions that can be chosen for a custom role. Following the CS3 API we can represent the CS3 permissions by mapping them to driveItem properties or relations like this: | [CS3 ResourcePermission](https://cs3org.github.io/cs3apis/#cs3.storage.provider.v1beta1.ResourcePermissions) | action | comment | | ------------------------------------------------------------------------------------------------------------ | ------ | ------- | | `stat` | `libre.graph/driveItem/basic/read` | `basic` because it does not include versions or trashed items | | `get_quota` | `libre.graph/driveItem/quota/read` | read only the `quota` property | | `get_path` | `libre.graph/driveItem/path/read` | read only the `path` property | | `move` | `libre.graph/driveItem/path/update` | allows updating the `path` property of a CS3 resource | | `delete` | `libre.graph/driveItem/standard/delete` | `standard` because deleting is a common update operation | | `list_container` | `libre.graph/driveItem/children/read` | | | `create_container` | `libre.graph/driveItem/children/create` | | | `initiate_file_download` | `libre.graph/driveItem/content/read` | `content` is the property read when initiating a download | | `initiate_file_upload` | `libre.graph/driveItem/upload/create` | `uploads` are a separate property. postprocessing creates the `content` | | `add_grant` | `libre.graph/driveItem/permissions/create` | | | `list_grant` | `libre.graph/driveItem/permissions/read` | | | `update_grant` | `libre.graph/driveItem/permissions/update` | | | `remove_grant` | `libre.graph/driveItem/permissions/delete` | | | `deny_grant` | `libre.graph/driveItem/permissions/deny` | uses a non CRUD action `deny` | | `list_file_versions` | `libre.graph/driveItem/versions/read` | `versions` is a `driveItemVersion` collection | | `restore_file_version` | `libre.graph/driveItem/versions/update` | the only `update` action is restore | | `list_recycle` | `libre.graph/driveItem/deleted/read` | reading a driveItem `deleted` property implies listing | | `restore_recycle_item` | `libre.graph/driveItem/deleted/update` | the only `update` action is restore | | `purge_recycle` | `libre.graph/driveItem/deleted/delete` | allows purging deleted `driveItems` | LibreGraphPermissionsActionsAllowedValues []string `json:"@libre.graph.permissions.actions.allowedValues,omitempty"` Value []Permission `json:"value,omitempty"` + // The total number of permissions available, only present if the `count` query parameter is set to true. + OdataCount *int32 `json:"@odata.count,omitempty"` } // NewCollectionOfPermissionsWithAllowedValues instantiates a new CollectionOfPermissionsWithAllowedValues object @@ -139,6 +141,38 @@ func (o *CollectionOfPermissionsWithAllowedValues) SetValue(v []Permission) { o.Value = v } +// GetOdataCount returns the OdataCount field value if set, zero value otherwise. +func (o *CollectionOfPermissionsWithAllowedValues) GetOdataCount() int32 { + if o == nil || IsNil(o.OdataCount) { + var ret int32 + return ret + } + return *o.OdataCount +} + +// GetOdataCountOk returns a tuple with the OdataCount field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CollectionOfPermissionsWithAllowedValues) GetOdataCountOk() (*int32, bool) { + if o == nil || IsNil(o.OdataCount) { + return nil, false + } + return o.OdataCount, true +} + +// HasOdataCount returns a boolean if a field has been set. +func (o *CollectionOfPermissionsWithAllowedValues) HasOdataCount() bool { + if o != nil && !IsNil(o.OdataCount) { + return true + } + + return false +} + +// SetOdataCount gets a reference to the given int32 and assigns it to the OdataCount field. +func (o *CollectionOfPermissionsWithAllowedValues) SetOdataCount(v int32) { + o.OdataCount = &v +} + func (o CollectionOfPermissionsWithAllowedValues) MarshalJSON() ([]byte, error) { toSerialize,err := o.ToMap() if err != nil { @@ -158,6 +192,9 @@ func (o CollectionOfPermissionsWithAllowedValues) ToMap() (map[string]interface{ if !IsNil(o.Value) { toSerialize["value"] = o.Value } + if !IsNil(o.OdataCount) { + toSerialize["@odata.count"] = o.OdataCount + } return toSerialize, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 405e5039d2..cd1c66f348 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1203,7 +1203,7 @@ github.com/open-policy-agent/opa/v1/types github.com/open-policy-agent/opa/v1/util github.com/open-policy-agent/opa/v1/util/decoding github.com/open-policy-agent/opa/v1/version -# github.com/opencloud-eu/libre-graph-api-go v1.0.7 +# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 ## explicit; go 1.18 github.com/opencloud-eu/libre-graph-api-go # github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9