Merge branch 'origin/main' into 'next-release/main'

This commit is contained in:
oauth committed 2026-09-03 15:55:53 +00:00
commit 48ccc21801
6 files changed
+240 -7

No files matched your search

+1 -1
View File
@@ -62,7 +62,7 @@ require (
github.com/onsi/gomega v1.42.1
github.com/open-policy-agent/opa v1.19.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901070651-08a5330ce57d
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429
github.com/opencloud-eu/reva/v2 v2.49.0
github.com/opensearch-project/opensearch-go/v4 v4.7.3
github.com/orcaman/concurrent-map v1.0.0
+2
View File
@@ -942,6 +942,8 @@ github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+l
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901070651-08a5330ce57d h1:3zbb31655ZvqwLMWMIiUAhYer6YcFnv/oguujnX7aOU=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901070651-08a5330ce57d/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429 h1:i/C5/1I0eRkjOcdtqQboyPvV0bPyAOahe/wWHGupKVY=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/reva/v2 v2.49.0 h1:AwECMDDth3NUaihZRf9bI9HNpWutvaRijOCvQyQfTT4=
github.com/opencloud-eu/reva/v2 v2.49.0/go.mod h1:Frg+UWnVcSy+412UB3l2LcD0KY8ZNu1samimwkNywbg=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
+100 -5
View File
@@ -15,13 +15,16 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
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"
"golang.org/x/crypto/sha3"
"google.golang.org/protobuf/types/known/fieldmaskpb"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/tags"
"github.com/opencloud-eu/reva/v2/pkg/utils"
@@ -33,7 +36,13 @@ import (
)
// opt-in driveItem instance annotations, returned only when requested via $select
const _selectAllowedValues = "@libre.graph.permissions.actions.allowedValues"
const (
_selectAllowedValues = "@libre.graph.permissions.actions.allowedValues"
_selectShareTypes = "@libre.graph.shareTypes"
)
// without it the provider leaves the share-types opaque empty
var shareTypesFieldMask = &fieldmaskpb.FieldMask{Paths: []string{"share-types"}}
// opt-in driveItem relations, returned only when requested via $expand
const _expandChildren = "children"
@@ -210,9 +219,14 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
}
}
lRes, err := gatewayClient.ListContainer(ctx, &storageprovider.ListContainerRequest{
listRequest := &storageprovider.ListContainerRequest{
Ref: &storageprovider.Reference{ResourceId: space.GetRoot()},
})
}
if driveItemPropertySelected(r, _selectShareTypes) {
listRequest.FieldMask = shareTypesFieldMask
}
lRes, err := gatewayClient.ListContainer(ctx, listRequest)
switch {
case err != nil:
g.logger.Error().Err(err).Msg("error making ListContainer grpc call")
@@ -246,6 +260,10 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
}
}
if driveItemPropertySelected(r, _selectShareTypes) {
g.addShareTypes(ctx, files, lRes.GetInfos())
}
render.Status(r, http.StatusOK)
render.JSON(w, r, &ListResponse{Value: files})
}
@@ -323,6 +341,11 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
driveItem.Children = children
}
if driveItemPropertySelected(r, _selectShareTypes) {
infos := []*storageprovider.ResourceInfo{res.GetInfo()}
driveItem.LibreGraphShareTypes = shareTypesOf(res.GetInfo(), g.listLinkShares(ctx, infos))
}
render.Status(r, http.StatusOK)
render.JSON(w, r, &driveItem)
}
@@ -371,9 +394,14 @@ func (g Graph) listDriveItemChildren(w http.ResponseWriter, r *http.Request, dri
return nil, false
}
res, err := gatewayClient.ListContainer(r.Context(), &storageprovider.ListContainerRequest{
childrenRequest := &storageprovider.ListContainerRequest{
Ref: &storageprovider.Reference{ResourceId: driveItemID},
})
}
if driveItemPropertySelected(r, _selectShareTypes) {
childrenRequest.FieldMask = shareTypesFieldMask
}
res, err := gatewayClient.ListContainer(r.Context(), childrenRequest)
switch {
case err != nil:
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -400,6 +428,10 @@ func (g Graph) listDriveItemChildren(w http.ResponseWriter, r *http.Request, dri
return nil, false
}
if driveItemPropertySelected(r, _selectShareTypes) {
g.addShareTypes(r.Context(), files, res.GetInfos())
}
return files, true
}
@@ -532,6 +564,69 @@ func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *sto
return driveItem, nil
}
// addShareTypes reads user and group shares off the grants, links from the share
// manager. Links are not stored as grants, so they need one filter per item,
// which is why the whole annotation is only built when it is selected.
func (g Graph) addShareTypes(ctx context.Context, items []libregraph.DriveItem, infos []*storageprovider.ResourceInfo) {
linkShares := g.listLinkShares(ctx, infos)
for i, info := range infos {
if i >= len(items) {
break
}
items[i].LibreGraphShareTypes = shareTypesOf(info, linkShares)
}
}
// shareTypesOf maps the grants on a resource, plus a hit in the link lookup, to
// the annotation values.
func shareTypesOf(info *storageprovider.ResourceInfo, linkShares map[string]struct{}) []string {
var types []string
for _, grant := range strings.Split(utils.ReadPlainFromOpaque(info.GetOpaque(), "share-types"), ",") {
switch grant {
case strconv.Itoa(int(storageprovider.GranteeType_GRANTEE_TYPE_USER)):
types = append(types, "user")
case strconv.Itoa(int(storageprovider.GranteeType_GRANTEE_TYPE_GROUP)):
types = append(types, "group")
}
}
if _, ok := linkShares[info.GetId().GetOpaqueId()]; ok {
types = append(types, "link")
}
return types
}
// listLinkShares returns the resource ids that carry a public link. A failed
// lookup is logged and treated as "no links", same as the WebDAV PROPFIND.
func (g Graph) listLinkShares(ctx context.Context, infos []*storageprovider.ResourceInfo) map[string]struct{} {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
g.logger.Error().Err(err).Msg("could not select gateway client for public shares")
return nil
}
filters := make([]*link.ListPublicSharesRequest_Filter, 0, len(infos))
for _, info := range infos {
filters = append(filters, publicshare.ResourceIDFilter(info.GetId()))
}
res, err := gatewayClient.ListPublicShares(ctx, &link.ListPublicSharesRequest{Filters: filters})
if err != nil || res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
g.logger.Error().Err(err).Msg("could not list public shares")
return nil
}
linkShares := make(map[string]struct{}, len(res.GetShare()))
for _, share := range res.GetShare() {
linkShares[share.GetResourceId().GetOpaqueId()] = struct{}{}
}
return linkShares
}
// metadataToFacet builds a DriveItem facet *T from CS3 arbitrary metadata under
// the "libre.graph.<facet>." key prefix. Nil when no such keys are present.
func metadataToFacet[T any](metadata map[string]string, facet string) *T {
@@ -11,6 +11,7 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/go-chi/chi/v5"
. "github.com/onsi/ginkgo/v2"
@@ -436,6 +437,104 @@ var _ = Describe("Driveitems", func() {
Expect(res.Value[0].PendingOperations).To(BeNil())
})
It("omits share types unless they are selected", func() {
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Infos: []*provider.ResourceInfo{
{
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
Etag: "etag",
Mtime: utils.TimeToTS(mtime),
Opaque: utils.AppendPlainToOpaque(nil, "share-types", "1,2"),
},
},
}, nil)
res := assertItemsList(1)
Expect(res.Value[0].LibreGraphShareTypes).To(BeNil())
gatewayClient.AssertNotCalled(GinkgoT(), "ListPublicShares", mock.Anything, mock.Anything)
})
It("returns the share types of an item when selected", func() {
r = r.WithContext(r.Context())
q := r.URL.Query()
q.Add("$select", "@libre.graph.shareTypes")
r.URL.RawQuery = q.Encode()
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Infos: []*provider.ResourceInfo{
{
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
Etag: "etag",
Mtime: utils.TimeToTS(mtime),
Opaque: utils.AppendPlainToOpaque(nil, "share-types", "1,2"),
},
},
}, nil)
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(&link.ListPublicSharesResponse{
Status: status.NewOK(ctx),
Share: []*link.PublicShare{
{ResourceId: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}},
},
}, nil)
res := assertItemsList(1)
Expect(res.Value[0].LibreGraphShareTypes).To(ConsistOf("user", "group", "link"))
})
It("reports only the link when the item has no grants", func() {
q := r.URL.Query()
q.Add("$select", "@libre.graph.shareTypes")
r.URL.RawQuery = q.Encode()
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Infos: []*provider.ResourceInfo{
{
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
Etag: "etag",
Mtime: utils.TimeToTS(mtime),
},
},
}, nil)
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(&link.ListPublicSharesResponse{
Status: status.NewOK(ctx),
Share: []*link.PublicShare{
{ResourceId: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}},
},
}, nil)
res := assertItemsList(1)
Expect(res.Value[0].LibreGraphShareTypes).To(ConsistOf("link"))
})
It("keeps the grant types when the public share lookup fails", func() {
q := r.URL.Query()
q.Add("$select", "@libre.graph.shareTypes")
r.URL.RawQuery = q.Encode()
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
Infos: []*provider.ResourceInfo{
{
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
Etag: "etag",
Mtime: utils.TimeToTS(mtime),
Opaque: utils.AppendPlainToOpaque(nil, "share-types", "1"),
},
},
}, nil)
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(nil, errors.New("nope"))
res := assertItemsList(1)
Expect(res.Value[0].LibreGraphShareTypes).To(ConsistOf("user"))
})
It("reports a pending content update while the item is being processed", func() {
gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
Status: status.NewOK(ctx),
+37
View File
@@ -80,6 +80,8 @@ type DriveItem struct {
LibreGraphTags []string `json:"@libre.graph.tags,omitempty"`
// A list of actions the caller is allowed to perform on this item. Only returned when explicitly requested via `$select` on endpoints that support it. Mirrors the annotation of the same name on the `/permissions` endpoint, allowing clients to learn a caller's effective actions on an item without a separate round-trip.
LibreGraphPermissionsActionsAllowedValues []string `json:"@libre.graph.permissions.actions.allowedValues,omitempty"`
// The types of shares existing on this item, aggregated over all of its grants. Absent or empty if the item is not shared. This is a summary of the item's `permissions` collection. For the full grants use the permissions endpoints, for the caller's own capabilities use `@libre.graph.permissions.actions.allowedValues`. Only returned when explicitly requested via `$select`.
LibreGraphShareTypes []string `json:"@libre.graph.shareTypes,omitempty"`
}
// NewDriveItem instantiates a new DriveItem object
@@ -1347,6 +1349,38 @@ func (o *DriveItem) SetLibreGraphPermissionsActionsAllowedValues(v []string) {
o.LibreGraphPermissionsActionsAllowedValues = v
}
// GetLibreGraphShareTypes returns the LibreGraphShareTypes field value if set, zero value otherwise.
func (o *DriveItem) GetLibreGraphShareTypes() []string {
if o == nil || IsNil(o.LibreGraphShareTypes) {
var ret []string
return ret
}
return o.LibreGraphShareTypes
}
// GetLibreGraphShareTypesOk returns a tuple with the LibreGraphShareTypes field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *DriveItem) GetLibreGraphShareTypesOk() ([]string, bool) {
if o == nil || IsNil(o.LibreGraphShareTypes) {
return nil, false
}
return o.LibreGraphShareTypes, true
}
// HasLibreGraphShareTypes returns a boolean if a field has been set.
func (o *DriveItem) HasLibreGraphShareTypes() bool {
if o != nil && !IsNil(o.LibreGraphShareTypes) {
return true
}
return false
}
// SetLibreGraphShareTypes gets a reference to the given []string and assigns it to the LibreGraphShareTypes field.
func (o *DriveItem) SetLibreGraphShareTypes(v []string) {
o.LibreGraphShareTypes = v
}
func (o DriveItem) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -1474,6 +1508,9 @@ func (o DriveItem) ToMap() (map[string]interface{}, error) {
if !IsNil(o.LibreGraphPermissionsActionsAllowedValues) {
toSerialize["@libre.graph.permissions.actions.allowedValues"] = o.LibreGraphPermissionsActionsAllowedValues
}
if !IsNil(o.LibreGraphShareTypes) {
toSerialize["@libre.graph.shareTypes"] = o.LibreGraphShareTypes
}
return toSerialize, nil
}
+1 -1
View File
@@ -1364,7 +1364,7 @@ github.com/open-policy-agent/opa/v1/version
# github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
## explicit; go 1.24.6
github.com/opencloud-eu/icap-client
# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901070651-08a5330ce57d
# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260901115715-6c4a98340429
## explicit; go 1.23
github.com/opencloud-eu/libre-graph-api-go
# github.com/opencloud-eu/reva/v2 v2.49.0