From 56dd987adffec3c9f88c8faffca1b4ebdfce6da0 Mon Sep 17 00:00:00 2001 From: "v.scharf" Date: Mon, 14 Sep 2026 17:24:12 +0200 Subject: [PATCH 1/2] chore: bump reva to 2.50.0 --- go.mod | 4 +- go.sum | 4 +- pkg/version/version.go | 2 +- .../usershareprovider/usershareprovider.go | 15 +- .../v2/pkg/appauth/manager/jsoncs3/jsoncs3.go | 63 +++++- .../reva/v2/pkg/events/mocks/Stream.go | 186 ------------------ .../v2/pkg/share/manager/jsoncs3/jsoncs3.go | 50 +++-- .../receivedsharecache/receivedsharecache.go | 71 ++++--- .../manager/jsoncs3/sharecache/sharecache.go | 69 ++++--- .../storage/pkg/decomposedfs/decomposedfs.go | 7 + .../v2/pkg/storage/pkg/decomposedfs/grants.go | 15 +- .../metadata/prefixes/prefixes.go | 9 +- .../pkg/storage/pkg/decomposedfs/node/node.go | 17 +- .../v2/pkg/storage/pkg/decomposedfs/spaces.go | 59 +++++- .../reva/v2/pkg/storage/utils/ace/ace.go | 36 +++- .../metadata/prefixes/prefixes.go | 9 +- .../reva/v2/pkg/utils/filename_encoder.go | 62 ++++++ .../opencloud-eu/reva/v2/pkg/utils/utils.go | 23 ++- vendor/modules.txt | 3 +- 19 files changed, 386 insertions(+), 318 deletions(-) delete mode 100644 vendor/github.com/opencloud-eu/reva/v2/pkg/events/mocks/Stream.go create mode 100644 vendor/github.com/opencloud-eu/reva/v2/pkg/utils/filename_encoder.go diff --git a/go.mod b/go.mod index 80444f78c4..5cb4e9919c 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/nats-io/nats-server/v2 v2.14.5 github.com/nats-io/nats.go v1.53.1 + github.com/olekukonko/errors v1.2.0 github.com/olekukonko/tablewriter v1.1.4 github.com/onsi/ginkgo v1.16.5 github.com/onsi/ginkgo/v2 v2.32.1 @@ -63,7 +64,7 @@ require ( 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.20260902170011-45af3945a067 - github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774 + github.com/opencloud-eu/reva/v2 v2.50.0 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 @@ -308,7 +309,6 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.2.0 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect diff --git a/go.sum b/go.sum index 82de5825e3..57e25fc6fe 100644 --- a/go.sum +++ b/go.sum @@ -942,8 +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.20260902170011-45af3945a067 h1:UkNMKauyJAzY6RE6mmthz9bQZLYkbvBuApm7ZDCparE= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU= -github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774 h1:8N42Pwzyafdxq66zYP6kJ1CH/QBRpxovQXq/0Kt6bJY= -github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774/go.mod h1:Frg+UWnVcSy+412UB3l2LcD0KY8ZNu1samimwkNywbg= +github.com/opencloud-eu/reva/v2 v2.50.0 h1:N5e0AuqACiZdQZAlEk7JqrSQfqwQVt26xAsCGzzOxOo= +github.com/opencloud-eu/reva/v2 v2.50.0/go.mod h1:PZFZK90dVfh+pkelCbgtGjmUQots9x7JOd1I2tt2aIQ= 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/pkg/version/version.go b/pkg/version/version.go index e53031302f..d7b1c107ff 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -34,7 +34,7 @@ var ( // LatestTag is the latest released version plus the dev meta version. // Will be overwritten by the release pipeline // Needs a manual change for every tagged release - LatestTag = "7.5.0+dev" + LatestTag = "8.0.0+dev" // Date indicates the build date. // This has been removed, it looks like you can only replace static strings with recent go versions 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 c2f3f9db6c..466a500973 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,6 +20,7 @@ package usershareprovider import ( "context" + "net/mail" "path/filepath" "regexp" "slices" @@ -180,8 +181,18 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar Status: status.NewPermissionDenied(ctx, nil, "user tenantId does not match the target user tenantId"), }, nil } - } + 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 != "" { + return &collaboration.CreateShareResponse{ + Status: status.NewInvalidArg(ctx, "invalid mail address for guest grantee"), + }, nil + } + } + } gatewayClient, err := s.gatewaySelector.Next() if err != nil { return nil, err @@ -206,7 +217,7 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar UserId: &userpb.UserId{ OpaqueId: req.GetGrant().GetGrantee().GetUserId().GetOpaqueId(), Idp: user.GetId().GetIdp(), - Type: userpb.UserType_USER_TYPE_PRIMARY}, + Type: req.GetGrant().GetGrantee().GetUserId().GetType()}, } } // some for group grantees diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/appauth/manager/jsoncs3/jsoncs3.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/appauth/manager/jsoncs3/jsoncs3.go index f15852b2bf..98244e5182 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/appauth/manager/jsoncs3/jsoncs3.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/appauth/manager/jsoncs3/jsoncs3.go @@ -2,6 +2,8 @@ package jsoncs3 import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "strings" "sync" @@ -14,6 +16,7 @@ import ( typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/go-viper/mapstructure/v2" "github.com/google/uuid" + "github.com/hashicorp/golang-lru/v2/expirable" "github.com/opencloud-eu/reva/v2/pkg/appauth" "github.com/opencloud-eu/reva/v2/pkg/appauth/manager/registry" "github.com/opencloud-eu/reva/v2/pkg/appctx" @@ -43,6 +46,7 @@ type manager struct { store *metadatacache.Store[string, map[string]*apppb.AppPassword] generator PasswordGenerator uTimeUpdateInterval time.Duration + authCache *expirable.LRU[string, *apppb.AppPassword] initialized bool } @@ -59,7 +63,10 @@ type config struct { UpdateRetryCount int `mapstructure:"update_retry_count"` } -const tracerName = "jsoncs3" +const ( + tracerName = "jsoncs3" + defaultCacheTTL = 60 * time.Second +) func New(m map[string]any) (appauth.Manager, error) { c := &config{} @@ -131,11 +138,13 @@ func NewWithOptions(mds metadata.Storage, generator PasswordGenerator, uTimeUpda Retries: updateRetries, Init: func() map[string]*apppb.AppPassword { return map[string]*apppb.AppPassword{} }, }) + return &manager{ mds: mds, store: store, generator: generator, uTimeUpdateInterval: uTimeUpdateInterval, + authCache: expirable.NewLRU[string, *apppb.AppPassword](0, nil, defaultCacheTTL), }, nil } @@ -291,6 +300,8 @@ func (m *manager) InvalidateAppPassword(ctx context.Context, secretOrId string) log.Error().Err(err).Msg("store.Update failed") return errtypes.NotFound("password not found") } + + m.removeFromAuthCache(userID.GetOpaqueId(), secretOrId) return nil } @@ -310,6 +321,19 @@ func (m *manager) GetAppPassword(ctx context.Context, user *userpb.UserId, secre matchedID string ) + // check for a previously validated authentication result from memory first, to avoid + // recomputing the Argon2id hash for every stored password. + cacheKey := createAuthCacheKey(user.GetOpaqueId(), secret) + if cached, ok := m.authCache.Get(cacheKey); ok { + if isAppPasswordExpired(cached) { + m.authCache.Remove(cacheKey) + return nil, errtypes.NotFound("password not found") + } + result := proto.Clone(cached).(*apppb.AppPassword) + result.Password = cached.Password + return result, nil + } + err := m.store.Update(ctx, user.GetOpaqueId(), false, func(a map[string]*apppb.AppPassword) (map[string]*apppb.AppPassword, bool, error) { matchedPw = nil for id, pw := range a { @@ -319,7 +343,7 @@ func (m *manager) GetAppPassword(ctx context.Context, user *userpb.UserId, secre log.Debug().Err(err).Msg("Error comparing password and hash") case ok: // password found - if pw.Expiration != nil && pw.Expiration.Seconds != 0 && uint64(time.Now().Unix()) > pw.Expiration.Seconds { + if isAppPasswordExpired(pw) { log.Debug().Str("AppPasswordId", id).Msg("password expired") return nil, false, errtypes.NotFound("password not found") } @@ -328,8 +352,13 @@ func (m *manager) GetAppPassword(ctx context.Context, user *userpb.UserId, secre matchedID = id // Updating the Utime will cause an Upload for every single GetAppPassword request. We are limiting this to one // update per 'uTimeUpdateInterval' (default 5 min) otherwise this backend will become unusable. + persist := false if time.Since(utils.TSToTime(pw.Utime)) > m.uTimeUpdateInterval { a[id].Utime = utils.TSNow() + persist = true + } + + if persist { return a, true, nil } return a, false, nil @@ -345,9 +374,31 @@ func (m *manager) GetAppPassword(ctx context.Context, user *userpb.UserId, secre // is not corrupted. result := proto.Clone(matchedPw).(*apppb.AppPassword) result.Password = matchedID + m.authCache.Add(cacheKey, result) + return result, nil } +// removeFromAuthCache removes the cached entry matching the app password. +// secretOrIdis actually a password which is coming to the InvalidateAppPassword method +// and then is propagated here, just keeped the same naming +func (m *manager) removeFromAuthCache(userID, secretOrId string) { + key := createAuthCacheKey(userID, secretOrId) + if m.authCache.Remove(key) { + return + } + for _, k := range m.authCache.Keys() { + v, ok := m.authCache.Peek(k) + if ok && v.Password == secretOrId { + m.authCache.Remove(k) + } + } +} + +func isAppPasswordExpired(pw *apppb.AppPassword) bool { + return pw.Expiration != nil && pw.Expiration.Seconds != 0 && uint64(time.Now().Unix()) > pw.Expiration.Seconds +} + func (m *manager) initialize(ctx context.Context) error { _, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "initialize") logger := appctx.GetLogger(ctx) @@ -424,3 +475,11 @@ func (d dicewarePassword) GeneratePassword() (string, error) { } return strings.Join(token, " "), nil } + +func createAuthCacheKey(userID, secret string) string { + h := sha256.New() + _, _ = h.Write([]byte(userID)) + _, _ = h.Write([]byte{0}) + _, _ = h.Write([]byte(secret)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/events/mocks/Stream.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/events/mocks/Stream.go deleted file mode 100644 index 3279b30c38..0000000000 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/events/mocks/Stream.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2018-2022 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -// Code generated by mockery v2.53.2. DO NOT EDIT. - -package mocks - -import ( - mock "github.com/stretchr/testify/mock" - events "go-micro.dev/v4/events" -) - -// Stream is an autogenerated mock type for the Stream type -type Stream struct { - mock.Mock -} - -type Stream_Expecter struct { - mock *mock.Mock -} - -func (_m *Stream) EXPECT() *Stream_Expecter { - return &Stream_Expecter{mock: &_m.Mock} -} - -// Consume provides a mock function with given fields: _a0, _a1 -func (_m *Stream) Consume(_a0 string, _a1 ...events.ConsumeOption) (<-chan events.Event, error) { - var tmpRet mock.Arguments - if len(_a1) > 0 { - tmpRet = _m.Called(_a0, _a1) - } else { - tmpRet = _m.Called(_a0) - } - ret := tmpRet - - if len(ret) == 0 { - panic("no return value specified for Consume") - } - - var r0 <-chan events.Event - var r1 error - if rf, ok := ret.Get(0).(func(string, ...events.ConsumeOption) (<-chan events.Event, error)); ok { - return rf(_a0, _a1...) - } - if rf, ok := ret.Get(0).(func(string, ...events.ConsumeOption) <-chan events.Event); ok { - r0 = rf(_a0, _a1...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan events.Event) - } - } - - if rf, ok := ret.Get(1).(func(string, ...events.ConsumeOption) error); ok { - r1 = rf(_a0, _a1...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Stream_Consume_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Consume' -type Stream_Consume_Call struct { - *mock.Call -} - -// Consume is a helper method to define mock.On call -// - _a0 string -// - _a1 ...events.ConsumeOption -func (_e *Stream_Expecter) Consume(_a0 interface{}, _a1 ...interface{}) *Stream_Consume_Call { - return &Stream_Consume_Call{Call: _e.mock.On("Consume", - append([]interface{}{_a0}, _a1...)...)} -} - -func (_c *Stream_Consume_Call) Run(run func(_a0 string, _a1 ...events.ConsumeOption)) *Stream_Consume_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]events.ConsumeOption, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(events.ConsumeOption) - } - } - run(args[0].(string), variadicArgs...) - }) - return _c -} - -func (_c *Stream_Consume_Call) Return(_a0 <-chan events.Event, _a1 error) *Stream_Consume_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *Stream_Consume_Call) RunAndReturn(run func(string, ...events.ConsumeOption) (<-chan events.Event, error)) *Stream_Consume_Call { - _c.Call.Return(run) - return _c -} - -// Publish provides a mock function with given fields: _a0, _a1, _a2 -func (_m *Stream) Publish(_a0 string, _a1 interface{}, _a2 ...events.PublishOption) error { - var tmpRet mock.Arguments - if len(_a2) > 0 { - tmpRet = _m.Called(_a0, _a1, _a2) - } else { - tmpRet = _m.Called(_a0, _a1) - } - ret := tmpRet - - if len(ret) == 0 { - panic("no return value specified for Publish") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, interface{}, ...events.PublishOption) error); ok { - r0 = rf(_a0, _a1, _a2...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Stream_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish' -type Stream_Publish_Call struct { - *mock.Call -} - -// Publish is a helper method to define mock.On call -// - _a0 string -// - _a1 interface{} -// - _a2 ...events.PublishOption -func (_e *Stream_Expecter) Publish(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *Stream_Publish_Call { - return &Stream_Publish_Call{Call: _e.mock.On("Publish", - append([]interface{}{_a0, _a1}, _a2...)...)} -} - -func (_c *Stream_Publish_Call) Run(run func(_a0 string, _a1 interface{}, _a2 ...events.PublishOption)) *Stream_Publish_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]events.PublishOption, len(args)-2) - for i, a := range args[2:] { - if a != nil { - variadicArgs[i] = a.(events.PublishOption) - } - } - run(args[0].(string), args[1].(interface{}), variadicArgs...) - }) - return _c -} - -func (_c *Stream_Publish_Call) Return(_a0 error) *Stream_Publish_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Stream_Publish_Call) RunAndReturn(run func(string, interface{}, ...events.PublishOption) error) *Stream_Publish_Call { - _c.Call.Return(run) - return _c -} - -// NewStream creates a new instance of Stream. 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 NewStream(t interface { - mock.TestingT - Cleanup(func()) -}) *Stream { - mock := &Stream{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go index 021b541bd9..492ec38009 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/jsoncs3.go @@ -26,6 +26,7 @@ import ( "time" gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -340,6 +341,12 @@ func (m *Manager) initialize(ctx context.Context) error { return nil } +// Ready returns a channel that is closed once the background initialization +// goroutine has successfully connected to the metadata storage. +func (m *Manager) Ready() <-chan struct{} { + return m.ready +} + // waitForInit blocks until the background initialization goroutine has // successfully completed, or until ctx is cancelled. func (m *Manager) waitForInit(ctx context.Context) error { @@ -476,7 +483,7 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla }) eg.Go(func() error { - err := m.CreatedCache.Add(ctx, s.GetCreator().GetOpaqueId(), shareID) + err := m.CreatedCache.Add(ctx, utils.NewFSSafeUserID(s.GetCreator()), shareID) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -490,13 +497,12 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla switch g.Grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: eg.Go(func() error { - userid := g.Grantee.GetUserId().GetOpaqueId() rs := &collaboration.ReceivedShare{ Share: s, State: collaboration.ShareState_SHARE_STATE_PENDING, } - err := m.UserReceivedStates.Add(ctx, userid, spaceID, rs) + err := m.UserReceivedStates.Add(ctx, utils.NewFSSafeUserID(g.GetGrantee().GetUserId()), spaceID, rs) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -506,8 +512,7 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla }) case provider.GranteeType_GRANTEE_TYPE_GROUP: eg.Go(func() error { - groupid := g.Grantee.GetGroupId().GetOpaqueId() - err := m.GroupReceivedCache.Add(ctx, groupid, shareID) + err := m.GroupReceivedCache.Add(ctx, utils.FSSafeGroupID{ID: g.GetGrantee().GetGroupId()}, shareID) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -873,7 +878,7 @@ func (m *Manager) listCreatedShares(ctx context.Context, user *userv1beta1.User, defer span.End() sublog := appctx.GetLogger(ctx).With().Str("userid", user.GetId().GetOpaqueId()).Str("useridp", user.GetId().GetIdp()).Str("driver", "jsoncs3").Str("handler", "listCreatedShares").Logger() - list, err := m.CreatedCache.List(ctx, user.Id.OpaqueId) + list, err := m.CreatedCache.List(ctx, utils.NewFSSafeUserID(user.GetId())) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -1018,7 +1023,10 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati // first collect all spaceids the user has access to as a group member for _, group := range user.Groups { - list, err := m.GroupReceivedCache.List(ctx, group) + groupid := grouppb.GroupId{ + OpaqueId: group, + } + list, err := m.GroupReceivedCache.List(ctx, utils.FSSafeGroupID{ID: &groupid}) if err != nil { continue // ignore error, cache will be updated on next read } @@ -1043,7 +1051,7 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati } // add all spaces the user has receved shares for, this includes mount points and share state for groups - spaces, err := m.UserReceivedStates.List(ctx, user.Id.OpaqueId) + spaces, err := m.UserReceivedStates.List(ctx, utils.NewFSSafeUserID(user.GetId())) if err != nil { return nil, err } @@ -1106,7 +1114,7 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati } if s == nil { sublogr.Warn().Str("shareid", shareID).Msg("share not found. cleaning up") - _ = m.UserReceivedStates.Remove(ctx, user.Id.OpaqueId, w.ssid, shareID) + _ = m.UserReceivedStates.Remove(ctx, utils.NewFSSafeUserID(user.GetId()), w.ssid, shareID) continue } sublogr = sublogr.With().Str("shareid", shareID).Logger() @@ -1184,7 +1192,7 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati } // convert must be called in a lock-controlled block. -func (m *Manager) convert(ctx context.Context, userID string, s *collaboration.Share) *collaboration.ReceivedShare { +func (m *Manager) convert(ctx context.Context, userID *userv1beta1.UserId, s *collaboration.Share) *collaboration.ReceivedShare { ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "convert") defer span.End() @@ -1195,7 +1203,7 @@ func (m *Manager) convert(ctx context.Context, userID string, s *collaboration.S storageID, spaceID, _ := shareid.Decode(s.Id.OpaqueId) - state, err := m.UserReceivedStates.Get(ctx, userID, storageID+shareid.IDDelimiter+spaceID, s.Id.GetOpaqueId()) + state, err := m.UserReceivedStates.Get(ctx, utils.NewFSSafeUserID(userID), storageID+shareid.IDDelimiter+spaceID, s.Id.GetOpaqueId()) if err == nil && state != nil { rs.State = state.State rs.MountPoint = state.MountPoint @@ -1255,7 +1263,7 @@ func (m *Manager) getReceived(ctx context.Context, ref *collaboration.ShareRefer } } } - return m.convert(ctx, user.Id.GetOpaqueId(), s), nil + return m.convert(ctx, user.GetId(), s), nil } // UpdateReceivedShare updates the received share with share state. @@ -1290,12 +1298,12 @@ func (m *Manager) UpdateReceivedShare(ctx context.Context, receivedShare *collab // write back u := ctxpkg.ContextMustGetUser(ctx) - uid := u.GetId().GetOpaqueId() + uid := u.GetId() if u.GetId().GetType() == userv1beta1.UserType_USER_TYPE_SERVICE { - uid = forUser.GetOpaqueId() + uid = forUser } - err = m.UserReceivedStates.Add(ctx, uid, rs.Share.ResourceId.StorageId+shareid.IDDelimiter+rs.Share.ResourceId.SpaceId, rs) + err = m.UserReceivedStates.Add(ctx, utils.NewFSSafeUserID(uid), rs.Share.ResourceId.StorageId+shareid.IDDelimiter+rs.Share.ResourceId.SpaceId, rs) if err != nil { return nil, err } @@ -1333,7 +1341,7 @@ func (m *Manager) Load(ctx context.Context, shareChan <-chan *collaboration.Shar } else { l.Debug().Str("storageid", s.GetResourceId().GetStorageId()).Str("spaceid", s.GetResourceId().GetSpaceId()).Str("shareid", s.Id.OpaqueId).Msg("imported share") } - if err := m.CreatedCache.Add(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId); err != nil { + if err := m.CreatedCache.Add(ctx, utils.NewFSSafeUserID(s.GetCreator()), s.Id.OpaqueId); err != nil { l.Error().Err(err).Interface("share", s).Msg("error persisting created cache") } else { l.Debug().Str("creatorid", s.GetCreator().GetOpaqueId()).Str("shareid", s.Id.OpaqueId).Msg("updated created cache") @@ -1349,14 +1357,14 @@ func (m *Manager) Load(ctx context.Context, shareChan <-chan *collaboration.Shar } if s.UserID != nil { spaceid := s.ReceivedShare.GetShare().GetResourceId().GetStorageId() + shareid.IDDelimiter + s.ReceivedShare.GetShare().GetResourceId().GetSpaceId() - if err := m.UserReceivedStates.Add(context.Background(), s.UserID.GetOpaqueId(), spaceid, s.ReceivedShare); err != nil { + if err := m.UserReceivedStates.Add(context.Background(), utils.NewFSSafeUserID(s.UserID), spaceid, s.ReceivedShare); err != nil { l.Error().Err(err).Interface("received share", s).Msg("error persisting received share for user") } else { l.Debug().Str("userid", s.UserID.GetOpaqueId()).Str("spaceid", spaceid).Str("shareid", s.ReceivedShare.GetShare().Id.OpaqueId).Msg("updated received share userdata") } } if s.ReceivedShare.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP && s.UserID == nil { - if err := m.GroupReceivedCache.Add(context.Background(), s.ReceivedShare.GetShare().GetGrantee().GetGroupId().GetOpaqueId(), s.ReceivedShare.GetShare().GetId().GetOpaqueId()); err != nil { + if err := m.GroupReceivedCache.Add(context.Background(), utils.FSSafeGroupID{ID: s.ReceivedShare.GetShare().GetGrantee().GetGroupId()}, s.ReceivedShare.GetShare().GetId().GetOpaqueId()); err != nil { l.Error().Err(err).Interface("received share", s).Msg("error persisting received share to group cache") } else { l.Debug().Str("groupid", s.ReceivedShare.GetShare().GetGrantee().GetGroupId().GetOpaqueId()).Str("shareid", s.ReceivedShare.GetShare().Id.OpaqueId).Msg("updated received share group cache") @@ -1412,15 +1420,15 @@ func (m *Manager) removeShare(ctx context.Context, s *collaboration.Share, skipS eg.Go(func() error { // remove from created cache - return m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) + return m.CreatedCache.Remove(ctx, utils.NewFSSafeUserID(s.GetCreator()), s.Id.OpaqueId) }) eg.Go(func() error { // remove from user received states if s.GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_USER { - return m.UserReceivedStates.Remove(ctx, s.GetGrantee().GetUserId().GetOpaqueId(), s.GetResourceId().GetStorageId()+shareid.IDDelimiter+s.GetResourceId().GetSpaceId(), s.Id.OpaqueId) + return m.UserReceivedStates.Remove(ctx, utils.NewFSSafeUserID(s.GetGrantee().GetUserId()), s.GetResourceId().GetStorageId()+shareid.IDDelimiter+s.GetResourceId().GetSpaceId(), s.Id.OpaqueId) } else if s.GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - return m.GroupReceivedCache.Remove(ctx, s.GetGrantee().GetGroupId().GetOpaqueId(), s.Id.OpaqueId) + return m.GroupReceivedCache.Remove(ctx, utils.FSSafeGroupID{ID: s.GetGrantee().GetGroupId()}, s.Id.OpaqueId) } return nil }) diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go index a0e94361a9..b8e536b082 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/receivedsharecache/receivedsharecache.go @@ -34,6 +34,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/errtypes" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata" + "github.com/opencloud-eu/reva/v2/pkg/utils" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) @@ -82,8 +83,8 @@ func New(s metadata.Storage, ttl time.Duration) Cache { } } -func (c *Cache) lockUser(userID string) func() { - v, _ := c.lockMap.LoadOrStore(userID, &sync.Mutex{}) +func (c *Cache) lockUser(userID utils.FilenameEncoder) func() { + v, _ := c.lockMap.LoadOrStore(userID.SafeFilename(), &sync.Mutex{}) lock := v.(*sync.Mutex) lock.Lock() @@ -91,14 +92,15 @@ func (c *Cache) lockUser(userID string) func() { } // Add adds a new entry to the cache -func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaboration.ReceivedShare) error { +func (c *Cache) Add(ctx context.Context, userID utils.FilenameEncoder, spaceID string, rs *collaboration.ReceivedShare) error { + userIDKey := userID.SafeFilename() ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") unlock := c.lockUser(userID) span.End() - span.SetAttributes(attribute.String("cs3.userid", userID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey)) defer unlock() - if _, ok := c.ReceivedSpaces.Load(userID); !ok { + if _, ok := c.ReceivedSpaces.Load(userIDKey); !ok { err := c.syncWithLock(ctx, userID) if err != nil { return err @@ -107,12 +109,12 @@ func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaborati ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userID), attribute.String("cs3.spaceid", spaceID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey), attribute.String("cs3.spaceid", spaceID)) persistFunc := func() error { - c.initializeIfNeeded(userID, spaceID) + c.initializeIfNeeded(userIDKey, spaceID) - rss, _ := c.ReceivedSpaces.Load(userID) + rss, _ := c.ReceivedSpaces.Load(userIDKey) receivedSpace := rss.Spaces[spaceID] if receivedSpace.States == nil { receivedSpace.States = map[string]*State{} @@ -128,7 +130,7 @@ func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaborati log := appctx.GetLogger(ctx).With(). Str("hostname", os.Getenv("HOSTNAME")). - Str("userID", userID). + Str("userIDKey", userIDKey). Str("spaceID", spaceID).Logger() var err error @@ -167,18 +169,19 @@ func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaborati } // Get returns one entry from the cache -func (c *Cache) Get(ctx context.Context, userID, spaceID, shareID string) (*State, error) { +func (c *Cache) Get(ctx context.Context, userID utils.FilenameEncoder, spaceID, shareID string) (*State, error) { ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") + userIDKey := userID.SafeFilename() unlock := c.lockUser(userID) span.End() - span.SetAttributes(attribute.String("cs3.userid", userID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey)) defer unlock() err := c.syncWithLock(ctx, userID) if err != nil { return nil, err } - rss, ok := c.ReceivedSpaces.Load(userID) + rss, ok := c.ReceivedSpaces.Load(userIDKey) if !ok || rss.Spaces[spaceID] == nil { return nil, nil } @@ -186,21 +189,22 @@ func (c *Cache) Get(ctx context.Context, userID, spaceID, shareID string) (*Stat } // Remove removes an entry from the cache -func (c *Cache) Remove(ctx context.Context, userID, spaceID, shareID string) error { +func (c *Cache) Remove(ctx context.Context, userID utils.FilenameEncoder, spaceID, shareID string) error { ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") + userIDKey := userID.SafeFilename() unlock := c.lockUser(userID) span.End() - span.SetAttributes(attribute.String("cs3.userid", userID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey)) defer unlock() ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userID), attribute.String("cs3.spaceid", spaceID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey), attribute.String("cs3.spaceid", spaceID)) persistFunc := func() error { - c.initializeIfNeeded(userID, spaceID) + c.initializeIfNeeded(userIDKey, spaceID) - rss, _ := c.ReceivedSpaces.Load(userID) + rss, _ := c.ReceivedSpaces.Load(userIDKey) receivedSpace := rss.Spaces[spaceID] if receivedSpace.States == nil { receivedSpace.States = map[string]*State{} @@ -215,7 +219,7 @@ func (c *Cache) Remove(ctx context.Context, userID, spaceID, shareID string) err log := appctx.GetLogger(ctx).With(). Str("hostname", os.Getenv("HOSTNAME")). - Str("userID", userID). + Str("userIDKey", userIDKey). Str("spaceID", spaceID).Logger() var err error @@ -255,11 +259,12 @@ func (c *Cache) Remove(ctx context.Context, userID, spaceID, shareID string) err // List returns a list of received shares for a given user // The return list is guaranteed to be thread-safe -func (c *Cache) List(ctx context.Context, userID string) (map[string]*Space, error) { +func (c *Cache) List(ctx context.Context, userID utils.FilenameEncoder) (map[string]*Space, error) { + userIDKey := userID.SafeFilename() ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") unlock := c.lockUser(userID) span.End() - span.SetAttributes(attribute.String("cs3.userid", userID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey)) defer unlock() err := c.syncWithLock(ctx, userID) @@ -268,7 +273,7 @@ func (c *Cache) List(ctx context.Context, userID string) (map[string]*Space, err } spaces := map[string]*Space{} - rss, _ := c.ReceivedSpaces.Load(userID) + rss, _ := c.ReceivedSpaces.Load(userIDKey) for spaceID, space := range rss.Spaces { spaceCopy := &Space{ States: map[string]*State{}, @@ -285,19 +290,20 @@ func (c *Cache) List(ctx context.Context, userID string) (map[string]*Space, err return spaces, nil } -func (c *Cache) syncWithLock(ctx context.Context, userID string) error { +func (c *Cache) syncWithLock(ctx context.Context, userID utils.FilenameEncoder) error { + userIDKey := userID.SafeFilename() ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Sync") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey)) - log := appctx.GetLogger(ctx).With().Str("userID", userID).Logger() + log := appctx.GetLogger(ctx).With().Str("userIDKey", userIDKey).Logger() - c.initializeIfNeeded(userID, "") + c.initializeIfNeeded(userIDKey, "") - jsonPath := userJSONPath(userID) + jsonPath := userJSONPath(userIDKey) span.AddEvent("updating cache") // - update cached list of created shares for the user in memory if changed - rss, _ := c.ReceivedSpaces.Load(userID) + rss, _ := c.ReceivedSpaces.Load(userIDKey) dlres, err := c.storage.Download(ctx, metadata.DownloadRequest{ Path: jsonPath, IfNoneMatch: []string{rss.etag}, @@ -326,18 +332,19 @@ func (c *Cache) syncWithLock(ctx context.Context, userID string) error { } newSpaces.etag = dlres.Etag - c.ReceivedSpaces.Store(userID, newSpaces) + c.ReceivedSpaces.Store(userIDKey, newSpaces) span.SetStatus(codes.Ok, "") return nil } // persist persists the data for one user to the storage -func (c *Cache) persist(ctx context.Context, userID string) error { +func (c *Cache) persist(ctx context.Context, userID utils.FilenameEncoder) error { + userIDKey := userID.SafeFilename() ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Persist") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userID)) + span.SetAttributes(attribute.String("cs3.userid.key", userIDKey)) - rss, ok := c.ReceivedSpaces.Load(userID) + rss, ok := c.ReceivedSpaces.Load(userIDKey) if !ok { span.SetStatus(codes.Ok, "no received shares") return nil @@ -349,7 +356,7 @@ func (c *Cache) persist(ctx context.Context, userID string) error { span.SetStatus(codes.Error, err.Error()) return err } - jsonPath := userJSONPath(userID) + jsonPath := userJSONPath(userIDKey) if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go index d7a830594a..c5a812dc16 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/sharecache/sharecache.go @@ -37,6 +37,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3/shareid" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata" + "github.com/opencloud-eu/reva/v2/pkg/utils" ) // name is the Tracer name used to identify this instrumentation library. @@ -89,15 +90,17 @@ func New(s metadata.Storage, namespace, filename string, ttl time.Duration) Cach } // Add adds a share to the cache -func (c *Cache) Add(ctx context.Context, userid, shareID string) error { +func (c *Cache) Add(ctx context.Context, id utils.FilenameEncoder, shareID string) error { + key := id.SafeFilename() + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") - unlock := c.lockUser(userid) + unlock := c.lockUser(key) span.End() - span.SetAttributes(attribute.String("cs3.userid", userid)) + span.SetAttributes(attribute.String("cs3.userid", key)) defer unlock() - if _, ok := c.UserShares.Load(userid); !ok { - err := c.syncWithLock(ctx, userid) + if _, ok := c.UserShares.Load(key); !ok { + err := c.syncWithLock(ctx, key) if err != nil { return err } @@ -105,23 +108,23 @@ func (c *Cache) Add(ctx context.Context, userid, shareID string) error { ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userid), attribute.String("cs3.shareid", shareID)) + span.SetAttributes(attribute.String("cs3.userid", key), attribute.String("cs3.shareid", shareID)) storageid, spaceid, _ := shareid.Decode(shareID) ssid := storageid + shareid.IDDelimiter + spaceid persistFunc := func() error { - c.initializeIfNeeded(userid, ssid) + c.initializeIfNeeded(key, ssid) // add share id - us, _ := c.UserShares.Load(userid) + us, _ := c.UserShares.Load(key) us.UserShares[ssid].IDs[shareID] = struct{}{} - return c.Persist(ctx, userid) + return c.Persist(ctx, key) } log := appctx.GetLogger(ctx).With(). Str("hostname", os.Getenv("HOSTNAME")). - Str("userID", userid). + Str("userID", key). Str("shareID", shareID).Logger() var err error @@ -149,7 +152,7 @@ func (c *Cache) Add(ctx context.Context, userid, shareID string) error { log.Error().Err(err).Msg("persisting added share failed") return err } - if err := c.syncWithLock(ctx, userid); err != nil { + if err := c.syncWithLock(ctx, key); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) log.Error().Err(err).Msg("persisting added share failed. giving up.") @@ -160,15 +163,16 @@ func (c *Cache) Add(ctx context.Context, userid, shareID string) error { } // Remove removes a share for the given user -func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { +func (c *Cache) Remove(ctx context.Context, id utils.FilenameEncoder, shareID string) error { ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") - unlock := c.lockUser(userid) + key := id.SafeFilename() + unlock := c.lockUser(key) span.End() - span.SetAttributes(attribute.String("cs3.userid", userid)) + span.SetAttributes(attribute.String("cs3.userid", key)) defer unlock() - if _, ok := c.UserShares.Load(userid); ok { - err := c.syncWithLock(ctx, userid) + if _, ok := c.UserShares.Load(key); ok { + err := c.syncWithLock(ctx, key) if err != nil { return err } @@ -176,13 +180,13 @@ func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Remove") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userid), attribute.String("cs3.shareid", shareID)) + span.SetAttributes(attribute.String("cs3.userid", key), attribute.String("cs3.shareid", shareID)) storageid, spaceid, _ := shareid.Decode(shareID) ssid := storageid + shareid.IDDelimiter + spaceid persistFunc := func() error { - us, loaded := c.UserShares.LoadOrStore(userid, &UserShareCache{ + us, loaded := c.UserShares.LoadOrStore(key, &UserShareCache{ UserShares: map[string]*SpaceShareIDs{}, }) @@ -191,12 +195,12 @@ func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { delete(us.UserShares[ssid].IDs, shareID) } - return c.Persist(ctx, userid) + return c.Persist(ctx, key) } log := appctx.GetLogger(ctx).With(). Str("hostname", os.Getenv("HOSTNAME")). - Str("userID", userid). + Str("userID", key). Str("shareID", shareID).Logger() var err error @@ -224,7 +228,7 @@ func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { log.Error().Err(err).Msg("persisting removed share failed") return err } - if err := c.syncWithLock(ctx, userid); err != nil { + if err := c.syncWithLock(ctx, key); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err @@ -235,18 +239,19 @@ func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { } // List return the list of spaces/shares for the given user/group -func (c *Cache) List(ctx context.Context, userid string) (map[string]SpaceShareIDs, error) { +func (c *Cache) List(ctx context.Context, id utils.FilenameEncoder) (map[string]SpaceShareIDs, error) { ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") - unlock := c.lockUser(userid) + key := id.SafeFilename() + unlock := c.lockUser(key) span.End() - span.SetAttributes(attribute.String("cs3.userid", userid)) + span.SetAttributes(attribute.String("cs3.userid", key)) defer unlock() - if err := c.syncWithLock(ctx, userid); err != nil { + if err := c.syncWithLock(ctx, key); err != nil { return nil, err } r := map[string]SpaceShareIDs{} - us, ok := c.UserShares.Load(userid) + us, ok := c.UserShares.Load(key) if !ok { return r, nil } @@ -309,12 +314,12 @@ func (c *Cache) syncWithLock(ctx context.Context, userID string) error { } // Persist persists the data for one user/group to the storage -func (c *Cache) Persist(ctx context.Context, userid string) error { +func (c *Cache) Persist(ctx context.Context, key string) error { ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Persist") defer span.End() - span.SetAttributes(attribute.String("cs3.userid", userid)) + span.SetAttributes(attribute.String("cs3.userid", key)) - us, ok := c.UserShares.Load(userid) + us, ok := c.UserShares.Load(key) if !ok { span.SetStatus(codes.Ok, "no user shares") return nil @@ -325,7 +330,7 @@ func (c *Cache) Persist(ctx context.Context, userid string) error { span.SetStatus(codes.Error, err.Error()) return err } - jsonPath := c.userCreatedPath(userid) + jsonPath := c.userCreatedPath(key) if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -355,8 +360,8 @@ func (c *Cache) Persist(ctx context.Context, userid string) error { return nil } -func (c *Cache) userCreatedPath(userid string) string { - return filepath.Join("/", c.namespace, userid, c.filename) +func (c *Cache) userCreatedPath(key string) string { + return filepath.Join("/", c.namespace, key, c.filename) } func (c *Cache) initializeIfNeeded(userid, ssid string) { 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 ccd7f05d34..9ae5f28155 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 @@ -130,6 +130,7 @@ type Decomposedfs struct { UserCache *ttlcache.Cache userSpaceIndex *spaceidindex.Index groupSpaceIndex *spaceidindex.Index + mailSpaceIndex *spaceidindex.Index spaceTypeIndex *spaceidindex.Index log *zerolog.Logger @@ -218,6 +219,11 @@ func New(o *options.Options, aspects aspects.Aspects, log *zerolog.Logger) (stor if err != nil { return nil, err } + mailSpaceIndex := spaceidindex.New(filepath.Join(o.Root, lookup.IndexesDir), "by-mail") + err = mailSpaceIndex.Init() + if err != nil { + return nil, err + } spaceTypeIndex := spaceidindex.New(filepath.Join(o.Root, lookup.IndexesDir), "by-type") err = spaceTypeIndex.Init() if err != nil { @@ -244,6 +250,7 @@ func New(o *options.Options, aspects aspects.Aspects, log *zerolog.Logger) (stor UserCache: ttlcache.NewCache(), userSpaceIndex: userSpaceIndex, groupSpaceIndex: groupSpaceIndex, + mailSpaceIndex: mailSpaceIndex, spaceTypeIndex: spaceTypeIndex, log: log, } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/grants.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/grants.go index 94cc53c355..4a1eb82ed8 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/grants.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/grants.go @@ -23,6 +23,7 @@ import ( "path/filepath" "strings" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/opencloud-eu/reva/v2/internal/grpc/services/storageprovider" "github.com/opencloud-eu/reva/v2/pkg/appctx" @@ -234,9 +235,15 @@ func (fs *Decomposedfs) RemoveGrant(ctx context.Context, ref *provider.Reference // invalidate space grant switch g.Grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: - // remove from user index - if err := fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), grantNode.SpaceID); err != nil { - return err + filename := utils.NewFSSafeUserID(g.Grantee.GetUserId()).SafeFilename() + if g.Grantee.GetUserId().GetType() == userpb.UserType_USER_TYPE_GUEST { + if err := fs.mailSpaceIndex.Remove(filename, grantNode.SpaceID); err != nil { + return err + } + } else { + if err := fs.userSpaceIndex.Remove(filename, grantNode.SpaceID); err != nil { + return err + } } case provider.GranteeType_GRANTEE_TYPE_GROUP: // remove from group index @@ -320,7 +327,7 @@ func (fs *Decomposedfs) loadGrant(ctx context.Context, ref *provider.Reference, for _, grant := range grants { switch grant.Grantee.GetType() { case provider.GranteeType_GRANTEE_TYPE_USER: - if g.Grantee.GetUserId().GetOpaqueId() == grant.Grantee.GetUserId().GetOpaqueId() { + if utils.UserIDEqual(g.Grantee.GetUserId(), grant.Grantee.GetUserId()) { return n, unlockFunc, grant, nil } case provider.GranteeType_GRANTEE_TYPE_GROUP: 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 0c36497373..45851fa794 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 @@ -20,6 +20,7 @@ package prefixes import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace" ) // Declare a list of xattr keys @@ -53,8 +54,9 @@ const ( // grantPrefix is the prefix for sharing related extended attributes GrantPrefix string = OcPrefix + "grant." - GrantUserAcePrefix string = OcPrefix + "grant." + UserAcePrefix - GrantGroupAcePrefix string = OcPrefix + "grant." + GroupAcePrefix + GrantUserAcePrefix string = OcPrefix + "grant." + ace.UserAcePrefix + GrantGroupAcePrefix string = OcPrefix + "grant." + ace.GroupAcePrefix + GrantMailAcePrefix string = OcPrefix + "grant." + ace.MailAcePrefix MetadataPrefix string = OcPrefix + "md." // favorite flag, per user @@ -103,9 +105,6 @@ const ( SpaceAliasAttr string = OcPrefix + "space.alias" SpaceTenantIDAttr string = OcPrefix + "space.tenantid" SpaceContentTypeAttr string = OcPrefix + "space.contenttype" - - UserAcePrefix string = "u:" - GroupAcePrefix string = "g:" ) func FavoriteKey(uid *userpb.UserId) string { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go index dc03ad6b85..33516a6a64 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node/node.go @@ -1238,7 +1238,7 @@ func (n *Node) IsDenied(ctx context.Context) bool { isExecutant := func(g *provider.Grantee) bool { switch g.GetType() { case provider.GranteeType_GRANTEE_TYPE_USER: - return g.GetUserId().GetOpaqueId() == u.GetId().GetOpaqueId() + return utils.UserIDEqual(g.GetUserId(), u.GetId()) case provider.GranteeType_GRANTEE_TYPE_GROUP: // check gid gid := g.GetGroupId().GetOpaqueId() @@ -1300,14 +1300,19 @@ func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant return e.Grant(), nil } -// ReadGrant reads a CS3 grant +// DeleteGrant reads a CS3 grant func (n *Node) DeleteGrant(ctx context.Context, g *provider.Grant) (err error) { var attr string - if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + switch g.Grantee.Type { + case provider.GranteeType_GRANTEE_TYPE_GROUP: attr = prefixes.GrantGroupAcePrefix + g.Grantee.GetGroupId().OpaqueId - } else { - attr = prefixes.GrantUserAcePrefix + g.Grantee.GetUserId().OpaqueId + case provider.GranteeType_GRANTEE_TYPE_USER: + attr = prefixes.GrantPrefix + ace.UserAce(g.Grantee.GetUserId()) + default: + // Note: We shouldn't actually get here as this is already caught in the upper + // layers (e.g. the storage provider) + return errors.New("grantee type is invalid") } if err = n.RemoveXattr(ctx, attr); err != nil { @@ -1353,7 +1358,7 @@ func (n *Node) getGranteeTypes(ctx context.Context) []provider.GranteeType { hasUserShares, hasGroupShares := false, false for i := range g { switch { - case !hasUserShares && strings.HasPrefix(g[i], prefixes.GrantUserAcePrefix): + case !hasUserShares && (strings.HasPrefix(g[i], prefixes.GrantUserAcePrefix) || strings.HasPrefix(g[i], prefixes.GrantMailAcePrefix)): hasUserShares = true case !hasGroupShares && strings.HasPrefix(g[i], prefixes.GrantGroupAcePrefix): hasGroupShares = true diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go index 00dd5e5246..2421c3c074 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/spaces.go @@ -345,7 +345,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide var err error if requestedUserID != nil { - allMatches, err = fs.userSpaceIndex.Load(requestedUserID.GetOpaqueId()) + filename := utils.NewFSSafeUserID(requestedUserID).SafeFilename() + allMatches, err = fs.userSpaceIndex.Load(filename) // do not return an error if the user has no spaces if err != nil && !os.IsNotExist(err) { return nil, errors.Wrap(err, "error reading user index") @@ -357,6 +358,21 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide matches[allMatches[entry]] = allMatches[entry] } + // guests are granted shares via their mail address, search the mail index as well + if requestedUserID.GetType() == userv1beta1.UserType_USER_TYPE_GUEST { + allMatches, err = fs.mailSpaceIndex.Load(filename) + // do not return an error if the guest has no spaces + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrap(err, "error reading mail index") + } + + if entry == spaceIDAny { + maps.Copy(matches, allMatches) + } else { + matches[allMatches[entry]] = allMatches[entry] + } + } + // get Groups for userid user := ctxpkg.ContextMustGetUser(ctx) // TODO the user from context may not have groups populated @@ -788,9 +804,17 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De for _, g := range grants { switch g.Grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: - // remove from user index - if err := fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), spaceID); err != nil { - sublog.Error().Err(err).Str("grantee", g.Grantee.GetUserId().GetOpaqueId()).Msg("could not remove user from user index") + filename := utils.NewFSSafeUserID(g.Grantee.GetUserId()).SafeFilename() + if g.Grantee.GetUserId().GetType() == userv1beta1.UserType_USER_TYPE_GUEST { + // remove from mail index + if err := fs.mailSpaceIndex.Remove(filename, spaceID); err != nil { + sublog.Error().Err(err).Str("grantee", g.Grantee.GetUserId().GetOpaqueId()).Msg("could not remove guest from mail index") + } + } else { + // remove from user index + if err := fs.userSpaceIndex.Remove(filename, spaceID); err != nil { + sublog.Error().Err(err).Str("grantee", g.Grantee.GetUserId().GetOpaqueId()).Msg("could not remove user from user index") + } } case provider.GranteeType_GRANTEE_TYPE_GROUP: // remove from group index @@ -894,7 +918,11 @@ func (fs *Decomposedfs) updateIndexes(ctx context.Context, grantee *provider.Gra // create space grant index switch grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: - return fs.linkSpaceByUser(ctx, grantee.GetUserId().GetOpaqueId(), spaceID, target) + filename := utils.NewFSSafeUserID(grantee.GetUserId()).SafeFilename() + if grantee.GetUserId().GetType() == userv1beta1.UserType_USER_TYPE_GUEST { + return fs.linkSpaceByMail(ctx, filename, spaceID, target) + } + return fs.linkSpaceByUser(ctx, filename, spaceID, target) case provider.GranteeType_GRANTEE_TYPE_GROUP: return fs.linkSpaceByGroup(ctx, grantee.GetGroupId().GetOpaqueId(), spaceID, target) default: @@ -910,6 +938,10 @@ func (fs *Decomposedfs) linkSpaceByGroup(ctx context.Context, groupID, spaceID, return fs.groupSpaceIndex.Add(groupID, spaceID, target) } +func (fs *Decomposedfs) linkSpaceByMail(ctx context.Context, mail, spaceID, target string) error { + return fs.mailSpaceIndex.Add(mail, spaceID, target) +} + func (fs *Decomposedfs) linkStorageSpaceType(ctx context.Context, spaceType, spaceID, target string) error { return fs.spaceTypeIndex.Add(spaceType, spaceID, target) } @@ -983,10 +1015,19 @@ func (fs *Decomposedfs) StorageSpaceFromNode(ctx context.Context, n *node.Node, // invalidate space grant switch g.Grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: - // remove from user index - if err := fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), n.GetSpaceID()); err != nil { - sublog.Error().Err(err).Str("grantee", id). - Msg("failed to delete expired user space index") + filename := utils.NewFSSafeUserID(g.Grantee.GetUserId()).SafeFilename() + if g.Grantee.GetUserId().GetType() == userv1beta1.UserType_USER_TYPE_GUEST { + // remove from mail index + if err := fs.mailSpaceIndex.Remove(filename, n.GetSpaceID()); err != nil { + sublog.Error().Err(err).Str("grantee", id). + Msg("failed to delete expired mail space index") + } + } else { + // remove from user index + if err := fs.userSpaceIndex.Remove(filename, n.GetSpaceID()); err != nil { + sublog.Error().Err(err).Str("grantee", id). + Msg("failed to delete expired user space index") + } } case provider.GranteeType_GRANTEE_TYPE_GROUP: // remove from group index diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace/ace.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace/ace.go index ef3da2ee29..96f4fa180e 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace/ace.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace/ace.go @@ -31,6 +31,13 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/grants" + "github.com/opencloud-eu/reva/v2/pkg/utils" +) + +const ( + UserAcePrefix = "u:" + GroupAcePrefix = "g:" + MailAcePrefix = "m:" ) /* @@ -202,7 +209,7 @@ func FromGrant(g *provider.Grant) *ACE { } if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { e.flags = "g" - e.principal = "g:" + g.Grantee.GetGroupId().OpaqueId + e.principal = GroupAcePrefix + g.Grantee.GetGroupId().OpaqueId } else { e.principal = UserAce(g.Grantee.GetUserId()) } @@ -215,7 +222,13 @@ func FromGrant(g *provider.Grant) *ACE { } func UserAce(id *userpb.UserId) string { - return "u:" + id.OpaqueId + filename := utils.NewFSSafeUserID(id).SafeFilename() + switch id.GetType() { + case userpb.UserType_USER_TYPE_GUEST: + return MailAcePrefix + filename + default: + return UserAcePrefix + filename + } } // Principal returns the principal of the ACE, eg. `u:` or `g:` @@ -256,13 +269,19 @@ func Unmarshal(principal string, v []byte) (e *ACE, err error) { } // check consistency of Flags and principal type if strings.Contains(e.flags, "g") { - if principal[:1] != "g" { + if !strings.HasPrefix(principal, GroupAcePrefix) { return nil, fmt.Errorf("inconsistent ace: expected group") } } else { - if principal[:1] != "u" { + if !strings.HasPrefix(principal, UserAcePrefix) && !strings.HasPrefix(principal, MailAcePrefix) { return nil, fmt.Errorf("inconsistent ace: expected user") } + if strings.HasPrefix(principal, MailAcePrefix) { + id := &userpb.UserId{Type: userpb.UserType_USER_TYPE_GUEST} + if _, err := utils.NewFSSafeUserID(id).Decode(strings.TrimPrefix(principal, MailAcePrefix)); err != nil { + return nil, fmt.Errorf("invalid guest ace principal: %w", err) + } + } } default: return nil, fmt.Errorf("unknown ace encoding") @@ -288,7 +307,14 @@ func (e *ACE) Grant() *provider.Grant { if e.granteeType() == provider.GranteeType_GRANTEE_TYPE_GROUP { g.Grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: id}} } else if e.granteeType() == provider.GranteeType_GRANTEE_TYPE_USER { - g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: id}} + if strings.HasPrefix(e.principal, MailAcePrefix) { + // Guest principals are validated by Unmarshal or encoded by FromGrant, + // so decoding cannot fail for a valid ACE. + userID, _ := utils.NewFSSafeUserID(&userpb.UserId{Type: userpb.UserType_USER_TYPE_GUEST}).Decode(id) + g.Grantee.Id = &provider.Grantee_UserId{UserId: userID} + } else { + g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: id, Type: userpb.UserType_USER_TYPE_PRIMARY}} + } } if e.expires != 0 { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes/prefixes.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes/prefixes.go index fb0cbef28b..855b3998ef 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes/prefixes.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes/prefixes.go @@ -18,6 +18,8 @@ package prefixes +import "github.com/opencloud-eu/reva/v2/pkg/storage/utils/ace" + // Declare a list of xattr keys // TODO the below comment is currently copied from the owncloud driver, revisit // Currently,extended file attributes have four separated @@ -49,8 +51,8 @@ const ( // grantPrefix is the prefix for sharing related extended attributes GrantPrefix string = OcisPrefix + "grant." - GrantUserAcePrefix string = OcisPrefix + "grant." + UserAcePrefix - GrantGroupAcePrefix string = OcisPrefix + "grant." + GroupAcePrefix + GrantUserAcePrefix string = OcisPrefix + "grant." + ace.UserAcePrefix + GrantGroupAcePrefix string = OcisPrefix + "grant." + ace.GroupAcePrefix MetadataPrefix string = OcisPrefix + "md." // favorite flag, per user @@ -97,7 +99,4 @@ const ( SpaceReadmeAttr string = OcisPrefix + "space.readme" SpaceImageAttr string = OcisPrefix + "space.image" SpaceAliasAttr string = OcisPrefix + "space.alias" - - UserAcePrefix string = "u:" - GroupAcePrefix string = "g:" ) 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 new file mode 100644 index 0000000000..13fdf39257 --- /dev/null +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/utils/filename_encoder.go @@ -0,0 +1,62 @@ +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" + "google.golang.org/protobuf/proto" +) + +type FilenameEncoder interface { + SafeFilename() string +} + +type FSSafeUserID struct { + id *userpb.UserId +} + +func NewFSSafeUserID(id *userpb.UserId) FSSafeUserID { + return FSSafeUserID{id: id} +} + +func (id FSSafeUserID) SafeFilename() string { + opaqueID := id.id.GetOpaqueId() + if id.id.GetType() == userpb.UserType_USER_TYPE_GUEST { + // Guest opaqueID is an email address, which is why we choose to make + // 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 opaqueID +} + +// Decode returns a copy of the wrapped user ID with the filename restored as its opaque ID. +// The decoding decision is done based on the Type attribute of the Receiver id +func (id FSSafeUserID) Decode(filename string) (*userpb.UserId, error) { + opaqueID := filename + if id.id.GetType() == userpb.UserType_USER_TYPE_GUEST { + decoded, err := base64.RawURLEncoding.DecodeString(filename) + if err != nil { + return nil, err + } + opaqueID = string(decoded) + } + + decodedID := &userpb.UserId{} + if id.id != nil { + decodedID = proto.Clone(id.id).(*userpb.UserId) + } + decodedID.OpaqueId = opaqueID + return decodedID, nil +} + +type FSSafeGroupID struct { + ID *grouppb.GroupId +} + +func (id FSSafeGroupID) SafeFilename() string { + return id.ID.GetOpaqueId() +} 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 bc4833dd27..78a3c960b1 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 @@ -218,12 +218,31 @@ func ExtractGranteeID(grantee *provider.Grantee) (*userpb.UserId, *grouppb.Group // UserEqual returns whether two users have the same field values. func UserEqual(u, v *userpb.UserId) bool { - return u != nil && v != nil && u.Idp == v.Idp && u.OpaqueId == v.OpaqueId + return u != nil && v != nil && u.Idp == v.Idp && UserIDEqual(u, v) } // UserIDEqual returns whether two users have the same opaqueid values. The idp is ignored func UserIDEqual(u, v *userpb.UserId) bool { - return u != nil && v != nil && u.OpaqueId == v.OpaqueId + if u == nil || v == nil { + return false + } + return CanonicalUserID(u) == CanonicalUserID(v) +} + +// CanonicalUserID returns the stable representation of a UserId as e.g. used for user storage keys. +// Currently this is only relevant for UserId of the USER_TYPE_GUEST, which are matched case-insensitively. +func CanonicalUserID(id *userpb.UserId) string { + if id == nil { + return "" + } + if id.GetType() == userpb.UserType_USER_TYPE_GUEST { + // Guest user IDs are lowercased to enable case insensitive + // 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 id.GetOpaqueId() } // GroupEqual returns whether two groups have the same field values. diff --git a/vendor/modules.txt b/vendor/modules.txt index 4da1ac8c1b..3b86b0c8ad 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1367,7 +1367,7 @@ github.com/opencloud-eu/icap-client # github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067 ## explicit; go 1.23 github.com/opencloud-eu/libre-graph-api-go -# github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774 +# github.com/opencloud-eu/reva/v2 v2.50.0 ## explicit; go 1.25.8 github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace github.com/opencloud-eu/reva/v2/cmd/revad/runtime @@ -1504,7 +1504,6 @@ github.com/opencloud-eu/reva/v2/pkg/eosclient/eosgrpc github.com/opencloud-eu/reva/v2/pkg/eosclient/eosgrpc/eos_grpc github.com/opencloud-eu/reva/v2/pkg/errtypes github.com/opencloud-eu/reva/v2/pkg/events -github.com/opencloud-eu/reva/v2/pkg/events/mocks github.com/opencloud-eu/reva/v2/pkg/events/raw github.com/opencloud-eu/reva/v2/pkg/events/raw/mocks github.com/opencloud-eu/reva/v2/pkg/events/stream From ab65e0a2d37ed01598202927e191d09286e5a8ac Mon Sep 17 00:00:00 2001 From: "v.scharf" Date: Mon, 14 Sep 2026 17:25:48 +0200 Subject: [PATCH 2/2] increase timeout for extract properties tests --- tests/acceptance/bootstrap/SpacesContext.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/acceptance/bootstrap/SpacesContext.php b/tests/acceptance/bootstrap/SpacesContext.php index eacb5a322e..2d20b3ce66 100644 --- a/tests/acceptance/bootstrap/SpacesContext.php +++ b/tests/acceptance/bootstrap/SpacesContext.php @@ -3881,7 +3881,9 @@ function ($response) { } } return false; - } + }, + 2000, + 30 ); $this->featureContext->setResponse($response); }