diff --git a/go.mod b/go.mod index 7062f68617..c5a5b9b644 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( github.com/open-policy-agent/opa v1.21.1 github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 github.com/opencloud-eu/libre-graph-api-go v1.0.8 - github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 + github.com/opencloud-eu/reva/v2 v2.51.1-0.20261007142032-7148a82e0e45 github.com/opensearch-project/opensearch-go/v4 v4.7.3 github.com/orcaman/concurrent-map v1.0.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 3ed91fa9b5..a0fb3186f1 100644 --- a/go.sum +++ b/go.sum @@ -761,8 +761,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 h1:VTkFLkLNna+T4RN6JUZnVQ1bqVAfPtMeORXbFzEYGKs= github.com/opencloud-eu/libre-graph-api-go v1.0.8/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU= -github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 h1:rvopdGQTsI0PT7P3MRamrDsO1yYIKJBOxJAbbYmc4P8= -github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424/go.mod h1:ngLsDakKnt8zCFCeULhuSZFESGy5NIuSq5/JGeOhPdU= +github.com/opencloud-eu/reva/v2 v2.51.1-0.20261007142032-7148a82e0e45 h1:rrZBLsznQDL6YFjtbZF+Z8WCHbGSWn0RsD0ddwaVY8c= +github.com/opencloud-eu/reva/v2 v2.51.1-0.20261007142032-7148a82e0e45/go.mod h1:rAwuI3PX8n3RxBXHfpMtAKqfwxTIKhZol1Bi3Qj39p8= github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI= github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index e19eec5aa8..7d43726744 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -182,7 +182,8 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto cTime = createShareResponse.GetShare().GetCtime() expiration = createShareResponse.GetShare().GetExpiration() - guestIdentity, err := cs3UserIdToIdentity(ctx, s.identityCache, createShareRequest.GetGrant().GetGrantee().GetUserId()) + // the share provider stores the guest's mail address in its canonical form + guestIdentity, err := cs3UserIdToIdentity(ctx, s.identityCache, createShareResponse.GetShare().GetGrantee().GetUserId()) if err != nil { s.logger.Debug().Err(err).Msg("failed to convert guest user id to identity") return libregraph.Permission{}, err 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 5c386b69f6..cf6ecd44c7 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go @@ -176,7 +176,8 @@ var _ = Describe("DriveItemPermissionsService", func() { {Email: libregraph.PtrString("guest@example.com")}, } createShareResponse.Share = &collaboration.Share{ - Id: &collaboration.ShareId{OpaqueId: "guest123"}, + Id: &collaboration.ShareId{OpaqueId: "guest123"}, + Grantee: guestGrantee("guest@example.com"), } permission, err := driveItemPermissionsService.Invite(ctx, driveItemId, driveItemInvite) @@ -187,6 +188,23 @@ var _ = Describe("DriveItemPermissionsService", func() { Expect(permission.GrantedToV2.User.GetLibreGraphUserType()).To(Equal("Guest")) }) + It("returns the guest id as stored by the share provider", func() { + cfg.Commons = &shared.Commons{EnableGuestLinks: true} + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil) + driveItemInvite.Recipients = []libregraph.DriveRecipient{ + {Email: libregraph.PtrString("Guest@\u0130nfocorp.com")}, + } + createShareResponse.Share = &collaboration.Share{ + Id: &collaboration.ShareId{OpaqueId: "guest123"}, + Grantee: guestGrantee("guest@xn--infocorp-o0e.com"), + } + + permission, err := driveItemPermissionsService.Invite(ctx, driveItemId, driveItemInvite) + Expect(err).ToNot(HaveOccurred()) + Expect(permission.GrantedToV2.User.GetId()).To(Equal("guest@xn--infocorp-o0e.com")) + }) + It("rejects guest shares when guest invites are disabled by default", func() { driveItemInvite.Recipients = []libregraph.DriveRecipient{ {Email: libregraph.PtrString("guest@example.com")}, @@ -1477,3 +1495,12 @@ var _ = Describe("DriveItemPermissionsApi", func() { }) }) }) + +func guestGrantee(mail string) *provider.Grantee { + return &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{Type: userpb.UserType_USER_TYPE_GUEST, OpaqueId: mail}, + }, + } +} diff --git a/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/usershareprovider/usershareprovider.go b/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/usershareprovider/usershareprovider.go index 1b769774bc..560cf9df31 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/usershareprovider/usershareprovider.go @@ -20,7 +20,6 @@ package usershareprovider import ( "context" - "net/mail" "path/filepath" "regexp" "slices" @@ -184,13 +183,15 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar if req.GetGrant().GetGrantee().GetUserId().GetType() == userpb.UserType_USER_TYPE_GUEST { // guests are identified by their mail address, lets be strict here and only accept bare mail addresses - // and reject the "Mailbox"-Format that also contains a Display name - addr, err := mail.ParseAddress(req.GetGrant().GetGrantee().GetUserId().GetOpaqueId()) - if err != nil || addr.Name != "" { + // and store them in their canonical form + guestID := req.GetGrant().GetGrantee().GetUserId() + canonical, err := utils.CanonicalMail(guestID.GetOpaqueId()) + if err != nil { return &collaboration.CreateShareResponse{ - Status: status.NewInvalidArg(ctx, "invalid mail address for guest grantee"), + Status: status.NewInvalidArg(ctx, "invalid mail address for guest grantee: "+err.Error()), }, nil } + guestID.OpaqueId = canonical } } gatewayClient, err := s.gatewaySelector.Next() diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/auth/manager/guestlinks/guestlinks.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/auth/manager/guestlinks/guestlinks.go index a8c63a233d..837ec63504 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/auth/manager/guestlinks/guestlinks.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/auth/manager/guestlinks/guestlinks.go @@ -271,6 +271,10 @@ func guestUserFromShare(s *collaboration.Share) (*userpb.User, error) { return nil, errors.New("guestlinks: grantee is not a guest user") } + if !utils.IsASCII(uid.GetOpaqueId()) { + return nil, errors.New("guestlinks: grantee opaque id is not ASCII") + } + addr, err := mail.ParseAddress(uid.GetOpaqueId()) if err != nil || addr.Name != "" || addr.Address != uid.GetOpaqueId() { return nil, errors.New("guestlinks: grantee opaque id is not a bare email address") diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/events/postprocessing.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/events/postprocessing.go index b2694dd524..e443f82947 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/events/postprocessing.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/events/postprocessing.go @@ -183,12 +183,21 @@ type UploadReady struct { SpaceOwner *user.UserId ExecutingUser *user.User ImpersonatingUser *user.User - FileRef *provider.Reference - ParentID *provider.ResourceId - Timestamp *types.Timestamp - Failed bool - IsVersion bool - // add reference here? We could use it to inform client pp is finished + // FileRef is a legacy reference to the uploaded file and its shape depends on + // the producer: decomposedfs anchors it at the space root + // (ResourceId.OpaqueId == SpaceId, with a Path relative to that anchor), while + // the posix assimilation emits the file node ID with an empty Path. Neither + // OpaqueId nor Path can be relied on universally, so treat Path as optional + // and use ResourceID to identify the file. + FileRef *provider.Reference + ParentID *provider.ResourceId + // ResourceID identifies the uploaded file: OpaqueId is the node ID. It mirrors + // BytesReceived and is the only file reference every producer sets the same + // way, so id based consumers (Graph API, fileid lookups) should use it. + ResourceID *provider.ResourceId + Timestamp *types.Timestamp + Failed bool + IsVersion bool } // Unmarshal to fulfill umarshaller interface diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go index 43a5f488ac..3588b93719 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/posix.go @@ -46,6 +46,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/aspects" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata" + "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/permissions" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/upload" @@ -105,6 +106,9 @@ func New(o *options.Options, stream events.Stream, cache, historyCache *idcache. if o.FileMetadataCache.Store == "noop" { return nil, fmt.Errorf("the posix driver requires a file metadata cache") } + if err := prefixes.SetOcPrefix(o.MetadataPrefix); err != nil { + return nil, err + } var err error if log == nil { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go index 53d3d2e6f8..2815dc07b2 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/tree/assimilation.go @@ -67,7 +67,7 @@ type queueItem struct { timer *time.Timer } -const dirtyFlag = prefixes.OcPrefix + "dirty" +func dirtyFlag() string { return prefixes.OcPrefix + "dirty" } type assimilationNode struct { path string @@ -601,10 +601,11 @@ func (t *Tree) assimilate(item scanItem) error { }) } else { t.PublishEvent(events.UploadReady{ - FileRef: ref, - ParentID: parentResourceID, - Timestamp: utils.TSNow(), - IsVersion: true, + FileRef: ref, + ParentID: parentResourceID, + ResourceID: ref.ResourceId, + Timestamp: utils.TSNow(), + IsVersion: true, }) } } @@ -671,10 +672,11 @@ func (t *Tree) assimilate(item scanItem) error { }) } else { t.PublishEvent(events.UploadReady{ - FileRef: ref, - ParentID: parentId, - Timestamp: utils.TSNow(), - IsVersion: false, + FileRef: ref, + ParentID: parentId, + ResourceID: ref.ResourceId, + Timestamp: utils.TSNow(), + IsVersion: false, }) } } @@ -1088,11 +1090,11 @@ func (t *Tree) propagateSizeDiff(n *node.Node, size int64) error { } func (t *Tree) setDirty(path string, dirty bool) error { - return xattr.Set(path, dirtyFlag, []byte(fmt.Sprintf("%t", dirty))) + return xattr.Set(path, dirtyFlag(), []byte(fmt.Sprintf("%t", dirty))) } func (t *Tree) isDirty(path string) (bool, error) { - dirtyAttr, err := xattr.Get(path, dirtyFlag) + dirtyAttr, err := xattr.Get(path, dirtyFlag()) if err != nil { if metadata.IsAttrUnset(err) { return true, nil diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go index 78c8afa7b9..367708c520 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/decomposedfs.go @@ -51,6 +51,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/aspects" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/lookup" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata" + "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options" "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/permissions" @@ -147,6 +148,10 @@ func NewDefault(m map[string]interface{}, bs node.Blobstore, es events.Stream, l return nil, err } + if err := prefixes.SetOcPrefix(o.MetadataPrefix); err != nil { + return nil, err + } + var lu *lookup.Lookup switch o.MetadataBackend { case "xattrs": @@ -415,6 +420,11 @@ func (fs *Decomposedfs) handlePostprocessingEvent(ctx context.Context, event eve }, Path: utils.MakeRelativePath(filepath.Join(session.Dir(), session.Filename())), }, + ResourceID: &provider.ResourceId{ + StorageId: session.ProviderID(), + SpaceId: session.SpaceID(), + OpaqueId: session.NodeID(), + }, ParentID: parentId, Timestamp: utils.TimeToTS(now), SpaceOwner: n.SpaceOwnerOrManager(ctx), diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/hybrid_backend.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/hybrid_backend.go index cc8989d2d8..be006150b3 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/hybrid_backend.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/hybrid_backend.go @@ -21,7 +21,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes" ) -var _metadataOffloadedAttr = prefixes.OcPrefix + "metadata_offloaded" +func metadataOffloadedAttr() string { return prefixes.OcPrefix + "metadata_offloaded" } type MetadataPathFunc func(MetadataNode) string @@ -72,7 +72,7 @@ func (b HybridBackend) Get(ctx context.Context, n MetadataNode, key string) ([]b if isOffloadingAttribute(key) { // check if key is offloaded - offloaded, err := xattr.Get(n.InternalPath(), _metadataOffloadedAttr) + offloaded, err := xattr.Get(n.InternalPath(), metadataOffloadedAttr()) if err == nil && string(offloaded) == "1" { msgpackAttribs := map[string][]byte{} msgBytes, err := os.ReadFile(b.MetadataPath(n)) @@ -174,7 +174,7 @@ func (b HybridBackend) getAll(ctx context.Context, n MetadataNode, skipOffloaded } // merge the attributes from the offload file - offloaded, err := xattr.Get(path, _metadataOffloadedAttr) + offloaded, err := xattr.Get(path, metadataOffloadedAttr()) if err != nil && !IsAttrUnset(err) { return nil, err } @@ -217,7 +217,7 @@ func (b HybridBackend) SetMultiple(ctx context.Context, n MetadataNode, attribs defer func() { _ = unlock() }() } - offloadAttr, err := xattr.Get(path, _metadataOffloadedAttr) + offloadAttr, err := xattr.Get(path, metadataOffloadedAttr()) offloaded := err == nil && string(offloadAttr) == "1" // offload if the offloading metadata size exceeds the limit @@ -346,7 +346,7 @@ func (b HybridBackend) offloadMetadata(ctx context.Context, n MetadataNode) erro } // set the metadata offloaded attribute - err = xattr.Set(path, _metadataOffloadedAttr, []byte("1")) + err = xattr.Set(path, metadataOffloadedAttr(), []byte("1")) if err != nil { return err } @@ -376,7 +376,7 @@ func (b HybridBackend) Remove(ctx context.Context, n MetadataNode, key string) e } if isOffloadingAttribute(key) { - offloadAttr, err := xattr.Get(path, _metadataOffloadedAttr) + offloadAttr, err := xattr.Get(path, metadataOffloadedAttr()) offloaded := err == nil && string(offloadAttr) == "1" if offloaded { // remove from offloaded metadata diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix.go index d6133594a1..7af5cc4e8f 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix.go @@ -24,6 +24,6 @@ package prefixes // manipulate the user. namespace, which is what is used to store decomposedfs // specific metadata. To prevent name collisions with other apps, we are going // to introduce a sub namespace "user.oc." -const ( +var ( OcPrefix string = "user.oc." ) diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix_freebsd.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix_freebsd.go index cc16a696ec..daf8ffd401 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix_freebsd.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/oc_prefix_freebsd.go @@ -23,6 +23,6 @@ package prefixes // On FreeBSD the `user` namespace is implied through a separate syscall argument // and will fail with invalid argument when you try to start an xattr name with user. or system. // For that reason we drop the superfluous user. prefix for FreeBSD specifically. -const ( +var ( OcPrefix string = "oc." ) diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go index 45851fa794..0cdf8f4173 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go @@ -19,6 +19,9 @@ package prefixes import ( + "fmt" + "sync" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace" ) @@ -31,7 +34,7 @@ import ( // we will use to store decomposedfs specific metadata. To prevent name // collisions with other apps We are going to introduce a sub namespace // "user.oc." in the xattrs_prefix*.go files. -const ( +var ( TypeAttr string = OcPrefix + "type" IDAttr string = OcPrefix + "id" ParentidAttr string = OcPrefix + "parentid" @@ -111,3 +114,76 @@ func FavoriteKey(uid *userpb.UserId) string { // the favorite flag is specific to the user, so we need to incorporate the userid return FavPrefix + uid.OpaqueId } + +var ( + defaultOcPrefix = OcPrefix + + mu sync.Mutex + fixed bool +) + +// SetOcPrefix sets the key prefix for the whole process, empty means the +// default. The first call fixes it, a later call with another prefix fails. +// It has to run before any key is used. +func SetOcPrefix(prefix string) error { + if prefix == "" { + prefix = defaultOcPrefix + } + + mu.Lock() + defer mu.Unlock() + + if fixed { + if prefix != OcPrefix { + return fmt.Errorf("metadata prefix is already set to %q, cannot change it to %q", OcPrefix, prefix) + } + return nil + } + + fixed = true + if prefix != OcPrefix { + setKeys(prefix) + } + return nil +} + +func setKeys(p string) { + OcPrefix = p + TypeAttr = p + "type" + IDAttr = p + "id" + ParentidAttr = p + "parentid" + OwnerIDAttr = p + "owner.id" + OwnerIDPAttr = p + "owner.idp" + OwnerTypeAttr = p + "owner.type" + NameAttr = p + "name" + BlobIDAttr = p + "blobid" + BlobsizeAttr = p + "blobsize" + StatusPrefix = p + "nodestatus" + ScanStatusPrefix = p + "scanstatus" + ScanDatePrefix = p + "scandate" + GrantPrefix = p + "grant." + GrantUserAcePrefix = p + "grant." + ace.UserAcePrefix + GrantGroupAcePrefix = p + "grant." + ace.GroupAcePrefix + GrantMailAcePrefix = p + "grant." + ace.MailAcePrefix + MetadataPrefix = p + "md." + FavPrefix = p + "fav." + TmpEtagAttr = p + "tmp.etag" + ReferenceAttr = p + "cs3.ref" + ChecksumPrefix = p + "cs." + TrashOriginAttr = p + "trash.origin" + PropagationAttr = p + "propagation" + MTimeAttr = p + "mtime" + TreeMTimeAttr = p + "tmtime" + DTimeAttr = p + "dtime" + TreesizeAttr = p + "treesize" + QuotaAttr = p + "quota" + SpaceIDAttr = p + "space.id" + SpaceNameAttr = p + "space.name" + SpaceTypeAttr = p + "space.type" + SpaceDescriptionAttr = p + "space.description" + SpaceReadmeAttr = p + "space.readme" + SpaceImageAttr = p + "space.image" + SpaceAliasAttr = p + "space.alias" + SpaceTenantIDAttr = p + "space.tenantid" + SpaceContentTypeAttr = p + "space.contenttype" +} diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options/options.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options/options.go index 32b4c67c95..f2827eccc2 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options/options.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/options/options.go @@ -42,6 +42,9 @@ type Options struct { // the metadata backend to use, currently supports `xattr` or `ini` MetadataBackend string `mapstructure:"metadata_backend"` + // the attribute key prefix of the metadata on disk, process wide, see prefixes.SetOcPrefix + MetadataPrefix string `mapstructure:"metadata_prefix"` + // the propagator to use for this fs. currently only `sync` is fully supported, `async` is available as an experimental feature Propagator string `mapstructure:"propagator"` // Options specific to the async propagator diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go index fd79d52f23..135ed0de42 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -404,6 +404,11 @@ func (fs *Decomposedfs) handlePostprocessingEvent(ctx context.Context, event eve }, Path: utils.MakeRelativePath(filepath.Join(session.Dir(), session.Filename())), }, + ResourceID: &provider.ResourceId{ + StorageId: session.ProviderID(), + SpaceId: session.SpaceID(), + OpaqueId: session.NodeID(), + }, Timestamp: utils.TimeToTS(now), SpaceOwner: n.SpaceOwnerOrManager(ctx), IsVersion: isVersion, diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/filename_encoder.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/filename_encoder.go index 13fdf39257..80e332fef5 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/filename_encoder.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/filename_encoder.go @@ -2,7 +2,6 @@ package utils import ( "encoding/base64" - "strings" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -28,7 +27,7 @@ func (id FSSafeUserID) SafeFilename() string { // it lowercase: RFC 5321 does specify that email address local-parts // are case sensitive but, in practice, it's a de-facto standard that // email providers consider them to be case insensitive: - return base64.RawURLEncoding.EncodeToString([]byte(strings.ToLower(opaqueID))) + return base64.RawURLEncoding.EncodeToString([]byte(LowerASCII(opaqueID))) } return opaqueID } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/mail.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/mail.go new file mode 100644 index 0000000000..9cef18faf3 --- /dev/null +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/mail.go @@ -0,0 +1,37 @@ +// Copyright 2026 OpenCloud GmbH +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "errors" + "net/mail" + "strings" + + "golang.org/x/net/idna" +) + +// CanonicalMail returns the canonical form of an email address. The result +// is always plain ASCII, so it can't contain look-alike characters, and two +// addresses that mail delivery treats the same end up equal: +// - only a plain address is accepted (no display name, no quoting) +// - the local part must be ASCII +// - international domains are converted to punycode +// - the result is lowercased +func CanonicalMail(email string) (string, error) { + addr, err := mail.ParseAddress(email) + if err != nil || addr.Name != "" || addr.Address != email { + return "", errors.New("invalid email address") + } + at := strings.LastIndex(email, "@") + local, domain := email[:at], email[at+1:] + if !IsASCII(local) { + return "", errors.New("email addresses with non-ASCII characters before the @ are not supported") + } + if !IsASCII(domain) { + if domain, err = idna.Lookup.ToASCII(domain); err != nil { + return "", errors.New("invalid email domain") + } + } + return strings.ToLower(local + "@" + domain), nil +} diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/utils.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/utils.go index 78a3c960b1..ee085b6161 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/utils.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/utils.go @@ -32,6 +32,7 @@ import ( "strconv" "strings" "time" + "unicode/utf8" appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -240,11 +241,34 @@ func CanonicalUserID(id *userpb.UserId) string { // comparisons: those are email addresses, and while RFC 5321 states // that the local-part is case sensitive, in practice, it's a de facto // standard that email providers consider them to be case insensitive. - return strings.ToLower(id.GetOpaqueId()) + return LowerASCII(id.GetOpaqueId()) } return id.GetOpaqueId() } +// LowerASCII lowercases only the ASCII letters A-Z in s and leaves all other +// characters untouched. Unlike strings.ToLower it never maps a non-ASCII +// character to an ASCII one (e.g. 'İ' -> 'i' or the Kelvin sign 'K' -> 'k'), +// so two different email addresses can't end up with the same canonical form. +func LowerASCII(s string) string { + return strings.Map(func(r rune) rune { + if r >= 'A' && r <= 'Z' { + return r + ('a' - 'A') + } + return r + }, s) +} + +// IsASCII returns true if s contains only ASCII characters. +func IsASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} + // GroupEqual returns whether two groups have the same field values. func GroupEqual(u, v *grouppb.GroupId) bool { return u != nil && v != nil && u.Idp == v.Idp && u.OpaqueId == v.OpaqueId diff --git a/vendor/modules.txt b/vendor/modules.txt index b5750a70e0..a538cb6a79 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1360,7 +1360,7 @@ github.com/opencloud-eu/icap-client # github.com/opencloud-eu/libre-graph-api-go v1.0.8 ## explicit; go 1.23 github.com/opencloud-eu/libre-graph-api-go -# github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 +# github.com/opencloud-eu/reva/v2 v2.51.1-0.20261007142032-7148a82e0e45 ## explicit; go 1.26.0 github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace github.com/opencloud-eu/reva/v2/cmd/revad/runtime