diff --git a/go.mod b/go.mod index f928d3d37d..6d4eb7fc45 100644 --- a/go.mod +++ b/go.mod @@ -89,7 +89,7 @@ require ( github.com/thejerf/suture/v4 v4.0.6 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 - github.com/tus/tusd/v2 v2.8.0 + github.com/tus/tusd/v2 v2.9.2 github.com/unrolled/secure v1.16.0 github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/xhit/go-simple-mail/v2 v2.16.0 @@ -392,7 +392,7 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.41.0 // indirect - google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect @@ -414,3 +414,7 @@ replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud- // to get the logger injection (https://github.com/pablodz/inotifywaitgo/pull/11) replace github.com/pablodz/inotifywaitgo v0.0.9 => github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 + +replace github.com/cs3org/go-cs3apis => github.com/rhafer/go-cs3apis v0.0.0-20260312152349-0b5ca0d4b142 + +replace github.com/opencloud-eu/reva/v2 => github.com/rhafer/reva/v2 v2.0.0-20260317140355-5ba923b2b4a0 diff --git a/go.sum b/go.sum index 104e2b1705..8466099bf9 100644 --- a/go.sum +++ b/go.sum @@ -265,8 +265,6 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= -github.com/cs3org/go-cs3apis v0.0.0-20260310080202-fb97596763d6 h1:Akwn9gHJugKd8M48LyV+WeIQ6yMXoxZdgZabR53I9q4= -github.com/cs3org/go-cs3apis v0.0.0-20260310080202-fb97596763d6/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48= github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= @@ -959,8 +957,6 @@ github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIft github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d h1:JcqGDiyrcaQwVyV861TUyQgO7uEmsjkhfm7aQd84dOw= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q= -github.com/opencloud-eu/reva/v2 v2.42.6-0.20260311175421-d77bc89ffe35 h1:Qfx8AelrxsyPk3Lacj3feWULGD7BD2Vsg1X6rve7bHU= -github.com/opencloud-eu/reva/v2 v2.42.6-0.20260311175421-d77bc89ffe35/go.mod h1:0k9+Qits/aemqVdCXs5hSEgTppPx9RZuxutuxqZKQF0= 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= @@ -1072,6 +1068,10 @@ github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKc github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rhafer/go-cs3apis v0.0.0-20260312152349-0b5ca0d4b142 h1:ZDS0bPCVjH8XaEYHlWMi/QUpuJabYsRAM91HL8Lvjlw= +github.com/rhafer/go-cs3apis v0.0.0-20260312152349-0b5ca0d4b142/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ= +github.com/rhafer/reva/v2 v2.0.0-20260317140355-5ba923b2b4a0 h1:+Nep6ECoUTf6HByta891FFsl7AIJJP1VposCKmvpAws= +github.com/rhafer/reva/v2 v2.0.0-20260317140355-5ba923b2b4a0/go.mod h1:hB/Guu2yI14TcH3EIWW+KMfy4iakcfH9WcSfG5/v0K4= github.com/riandyrn/otelchi v0.12.2 h1:6QhGv0LVw/dwjtPd12mnNrl0oEQF4ZAlmHcnlTYbeAg= github.com/riandyrn/otelchi v0.12.2/go.mod h1:weZZeUJURvtCcbWsdb7Y6F8KFZGedJlSrgUjq9VirV8= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1230,8 +1230,8 @@ github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXz github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc= github.com/trustelem/zxcvbn v1.0.1/go.mod h1:zonUyKeh7sw6psPf/e3DtRqkRyZvAbOfjNz/aO7YQ5s= -github.com/tus/tusd/v2 v2.8.0 h1:X2jGxQ05jAW4inDd2ogmOKqwnb4c/D0lw2yhgHayWyU= -github.com/tus/tusd/v2 v2.8.0/go.mod h1:3/zEOVQQIwmJhvNam8phV4x/UQt68ZmZiTzeuJUNhVo= +github.com/tus/tusd/v2 v2.9.2 h1:Dd/Dh0CG7+/wom4lDQnnhca+1p5qVwgnbyEBacj1v7c= +github.com/tus/tusd/v2 v2.9.2/go.mod h1:+a9uNLru2Qy+CUu7QUIshmQ+X0fLNw77eu8voKVxmgA= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= @@ -1716,8 +1716,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= diff --git a/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go index 8bd8e20806..f851d31992 100644 --- a/services/gateway/pkg/revaconfig/config.go +++ b/services/gateway/pkg/revaconfig/config.go @@ -51,7 +51,8 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i "ocminvitemanagersvc": cfg.OCMEndpoint, "ocmproviderauthorizersvc": cfg.OCMEndpoint, "ocmcoresvc": cfg.OCMEndpoint, - "commit_share_to_storage_grant": cfg.CommitShareToStorageGrant, + "use_common_space_root_share_logic": true, + "commit_share_to_storage_grant": cfg.CommitShareToStorageGrant, "share_folder": cfg.ShareFolder, // ShareFolder is the location where to create shares in the recipient's storage provider. // other "disable_home_creation_on_login": cfg.DisableHomeCreationOnLogin, diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index aa68e8d0fe..f55ad2717d 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -245,10 +245,6 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto if shareid != "" { permission.Id = conversions.ToPointer(shareid) - } else if IsSpaceRoot(statResponse.GetInfo().GetId()) { - // permissions on a space root are not handled by a share manager so - // they don't get a share-id - permission.SetId(identitySetToSpacePermissionID(permission.GetGrantedToV2())) } if expiration != nil { @@ -414,12 +410,12 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID var permissionsCount int if IsSpaceRoot(statResponse.GetInfo().GetId()) { - var permissions []libregraph.Permission - permissions, permissionsCount, err = s.getSpaceRootPermissions(ctx, statResponse.GetInfo().GetSpace().GetId(), queryOptions.NoValues) + driveItems, err = s.listSpaceRootUserShares(ctx, []*collaboration.Filter{ + share.ResourceIDFilter(itemID), + }, driveItems) if err != nil { return collectionOfPermissions, err } - collectionOfPermissions.Value = permissions } else { // "normal" driveItem, populate user permissions via share providers driveItems, err = s.listUserShares(ctx, []*collaboration.Filter{ @@ -535,12 +531,10 @@ func (s DriveItemPermissionsService) DeletePermission(ctx context.Context, itemI } switch permissionType { - case User: + case User, Space: return s.removeUserShare(ctx, permissionID) case Public: return s.removePublicShare(ctx, permissionID) - case Space: - return s.removeSpacePermission(ctx, permissionID, sharedResourceID) case OCM: return s.removeOCMPermission(ctx, permissionID) default: 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 3adb5b2dc3..4a088664dd 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go @@ -24,7 +24,6 @@ import ( "github.com/opencloud-eu/opencloud/services/graph/pkg/config" "github.com/stretchr/testify/mock" "github.com/tidwall/gjson" - "google.golang.org/grpc" roleconversions "github.com/opencloud-eu/reva/v2/pkg/conversions" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" @@ -391,6 +390,25 @@ var _ = Describe("DriveItemPermissionsService", func() { Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero()) Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero()) }) + It("sends SpaceRootFilter(false) when listing shares for a non-space-root item", func() { + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) + gatewayClient.On("ListShares", + mock.Anything, + mock.MatchedBy(func(req *collaboration.ListSharesRequest) bool { + for _, f := range req.Filters { + if f.Type == collaboration.Filter_TYPE_SPACE_ROOT && !f.GetSpaceRoot() { + return true + } + } + return false + }), + ).Return(listSharesResponse, nil) + + _, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{}) + Expect(err).ToNot(HaveOccurred()) + gatewayClient.AssertExpectations(GinkgoT()) + }) It("returns one permission per share", func() { statResponse.Info.PermissionSet = roleconversions.NewEditorRole().CS3ResourcePermissions() listSharesResponse.Shares = []*collaboration.Share{ @@ -442,6 +460,8 @@ var _ = Describe("DriveItemPermissionsService", func() { }) It("returns role denied", func() { statResponse.Info.PermissionSet = roleconversions.NewManagerRole().CS3ResourcePermissions() + statResponse.Info.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + listSharesResponse.Shares = []*collaboration.Share{ { Id: &collaboration.ShareId{OpaqueId: "1"}, @@ -666,6 +686,7 @@ var _ = Describe("DriveItemPermissionsService", func() { } gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil) + gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(&collaboration.ListSharesResponse{Status: status.NewOK(ctx)}, nil) gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil) statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) @@ -765,12 +786,10 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewayClient.On("RemoveShare", mock.Anything, - mock.Anything, - ).Return(func(ctx context.Context, in *collaboration.RemoveShareRequest, opts ...grpc.CallOption) (*collaboration.RemoveShareResponse, error) { - Expect(in.Ref.GetKey()).ToNot(BeNil()) - Expect(in.Ref.GetKey().GetGrantee().GetUserId().GetOpaqueId()).To(Equal("userid")) - return &collaboration.RemoveShareResponse{Status: status.NewOK(ctx)}, nil - }) + mock.MatchedBy(func(req *collaboration.RemoveShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "shareid" + }), + ).Return(&collaboration.RemoveShareResponse{Status: status.NewOK(ctx)}, nil) err := driveItemPermissionsService.DeletePermission(context.Background(), &provider.ResourceId{ @@ -778,7 +797,7 @@ var _ = Describe("DriveItemPermissionsService", func() { SpaceId: "2", OpaqueId: "2", }, - "u:userid", + "shareid", ) Expect(err).ToNot(HaveOccurred()) }) @@ -1045,24 +1064,40 @@ var _ = Describe("DriveItemPermissionsService", func() { Expect(res).To(BeZero()) }) It("fails to update the space permissions for a space share when setting a file specific role", func() { - gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) getPublicShareMockResponse.Share = nil getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil) - gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil) - - statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root - gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) - gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - - driveItemPermission.SetRoles([]string{unifiedrole.UnifiedRoleFileEditorID}) spaceId := &provider.ResourceId{ StorageId: "1", SpaceId: "2", OpaqueId: "2", } - res, err := driveItemPermissionsService.UpdatePermission(ctx, spaceId, "u:userid", driveItemPermission) + statResponse.Info.Id = spaceId + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + + gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(&collaboration.ListSharesResponse{ + Status: status.NewOK(ctx), + Shares: []*collaboration.Share{ + { + Id: &collaboration.ShareId{OpaqueId: "spaceShareId"}, + ResourceId: spaceId, + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{OpaqueId: "userid"}, + }, + }, + Permissions: &collaboration.SharePermissions{ + Permissions: roleconversions.NewSpaceViewerRole().CS3ResourcePermissions(), + }, + }, + }, + }, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + + driveItemPermission.SetRoles([]string{unifiedrole.UnifiedRoleFileEditorID}) + res, err := driveItemPermissionsService.UpdatePermission(ctx, spaceId, "spaceShareId", driveItemPermission) Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) Expect(res).To(BeZero()) }) diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go index 4b2c8edd2c..fbbfa1edfe 100644 --- a/services/graph/pkg/service/v0/base.go +++ b/services/graph/pkg/service/v0/base.go @@ -51,22 +51,6 @@ type BaseGraphService struct { availableRoles []*libregraph.UnifiedRoleDefinition } -func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId, countOnly bool) ([]libregraph.Permission, int, error) { - gatewayClient, err := g.gatewaySelector.Next() - - if err != nil { - g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - return nil, 0, err - } - space, err := utils.GetSpace(ctx, spaceID.GetOpaqueId(), gatewayClient) - if err != nil { - return nil, 0, errorcode.FromUtilsStatusCodeError(err) - } - - perm, count := g.cs3SpacePermissionsToLibreGraph(ctx, space, countOnly, APIVersion_1_Beta_1) - return perm, count, nil -} - func (g BaseGraphService) getDriveItem(ctx context.Context, ref *storageprovider.Reference) (*libregraph.DriveItem, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { @@ -269,6 +253,17 @@ func (g BaseGraphService) libreGraphPermissionFromCS3PublicShare(createdLink *li } func (g BaseGraphService) listUserShares(ctx context.Context, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { + return g.listSharesWithSpaceRootFilter(ctx, false, filters, driveItems) +} + +// listSpaceRootUserShares lists user/group shares whose resource is a space root (i.e. space +// memberships). It uses SpaceRootFilter(true) so only space-root shares are returned, mirroring +// how listUserShares works for regular file/folder shares. +func (g BaseGraphService) listSpaceRootUserShares(ctx context.Context, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { + return g.listSharesWithSpaceRootFilter(ctx, true, filters, driveItems) +} + +func (g BaseGraphService) listSharesWithSpaceRootFilter(ctx context.Context, spaceRoot bool, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { g.logger.Error().Err(err).Msg("could not select next gateway client") @@ -278,6 +273,7 @@ func (g BaseGraphService) listUserShares(ctx context.Context, filters []*collabo concreteFilters := []*collaboration.Filter{ share.UserGranteeFilter(), share.GroupGranteeFilter(), + share.SpaceRootFilter(spaceRoot), } concreteFilters = append(concreteFilters, filters...) @@ -562,9 +558,7 @@ func (g BaseGraphService) cs3OCMSharesToDriveItems(ctx context.Context, shares [ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share, roleCondition string) (*libregraph.Permission, error) { perm := libregraph.Permission{} perm.SetRoles([]string{}) - if roleCondition != unifiedrole.UnifiedRoleConditionDrive { - perm.SetId(share.GetId().GetOpaqueId()) - } + perm.SetId(share.GetId().GetOpaqueId()) grantedTo := libregraph.SharePointIdentitySet{} switch share.GetGrantee().GetType() { case storageprovider.GranteeType_GRANTEE_TYPE_USER: @@ -578,9 +572,6 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c return nil, errorcode.New(errorcode.GeneralException, err.Error()) default: grantedTo.SetUser(user) - if roleCondition == unifiedrole.UnifiedRoleConditionDrive { - perm.SetId("u:" + user.GetId()) - } } case storageprovider.GranteeType_GRANTEE_TYPE_GROUP: group, err := groupIdToIdentity(ctx, g.identityCache, share.Grantee.GetGroupId().GetOpaqueId()) @@ -593,9 +584,6 @@ func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *c return nil, errorcode.New(errorcode.GeneralException, err.Error()) default: grantedTo.SetGroup(group) - if roleCondition == unifiedrole.UnifiedRoleConditionDrive { - perm.SetId("g:" + group.GetId()) - } } } @@ -926,35 +914,6 @@ func (g BaseGraphService) removeUserShare(ctx context.Context, permissionID stri return nil } -func (g BaseGraphService) removeSpacePermission(ctx context.Context, permissionID string, resourceId *storageprovider.ResourceId) error { - grantee, err := spacePermissionIdToCS3Grantee(permissionID) - if err != nil { - return err - } - - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - return err - } - removeShareResp, err := gatewayClient.RemoveShare(ctx, &collaboration.RemoveShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Key{ - Key: &collaboration.ShareKey{ - ResourceId: resourceId, - Grantee: &grantee, - }, - }, - }, - }) - if err := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); err != nil { - return err - } - - // We need to return an untyped nil here otherwise the error==nil check won't work - return nil -} - func (g BaseGraphService) getOCMPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) { cs3Share, err := g.getCS3OCMShareByID(ctx, permissionID) if err != nil { @@ -1070,20 +1029,19 @@ func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID st } return permission, publicShare.GetResourceId(), nil case IsSpaceRoot(itemID): - // itemID is referencing a spaceroot this is a space permission. Handle - // that here and get space id - resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient) + // itemID is referencing a space root — use the share manager to look up the permission + driveItems := make(driveItemsByResourceID) + driveItems, err = g.listSpaceRootUserShares(ctx, []*collaboration.Filter{ + share.ResourceIDFilter(itemID), + }, driveItems) if err != nil { return nil, nil, err } - - perms, _, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId(), false) - if err != nil { - return nil, nil, err - } - for _, p := range perms { - if p.GetId() == permissionID { - return &p, itemID, nil + for _, item := range driveItems { + for i := range item.Permissions { + if item.Permissions[i].GetId() == permissionID { + return &item.Permissions[i], itemID, nil + } } } case errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound: @@ -1238,29 +1196,10 @@ func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID stri } var cs3UpdateShareReq collaboration.UpdateShareRequest - // When updating a space root we need to reference the share by resourceId and grantee - if IsSpaceRoot(itemID) { - grantee, err := spacePermissionIdToCS3Grantee(permissionID) - if err != nil { - g.logger.Debug().Err(err).Str("permissionid", permissionID).Msg("failed to parse space permission id") - return nil, err - } - cs3UpdateShareReq.Share = &collaboration.Share{ - ResourceId: itemID, - Grantee: &grantee, - } - cs3UpdateShareReq.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "spacegrant": {}, - }, - } - cs3UpdateShareReq.Opaque = utils.AppendPlainToOpaque(cs3UpdateShareReq.Opaque, "spacetype", _spaceTypeProject) - } else { - cs3UpdateShareReq.Share = &collaboration.Share{ - Id: &collaboration.ShareId{ - OpaqueId: permissionID, - }, - } + cs3UpdateShareReq.Share = &collaboration.Share{ + Id: &collaboration.ShareId{ + OpaqueId: permissionID, + }, } fieldmask := []string{} if expiration, ok := newPermission.GetExpirationDateTimeOk(); ok { diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index da89e25bad..7a6615173b 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -15,8 +15,6 @@ import ( "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -349,35 +347,6 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &ListResponse{Value: files}) } -func spacePermissionIdToCS3Grantee(permissionID string) (storageprovider.Grantee, error) { - // the permission ID for space permission is made of two parts - // the grantee type ('u' or user, 'g' for group) and the user or group id - var grantee storageprovider.Grantee - parts := strings.SplitN(permissionID, ":", 2) - if len(parts) != 2 { - return grantee, errorcode.New(errorcode.InvalidRequest, "invalid space permission id") - } - switch parts[0] { - case "u": - grantee.Type = storageprovider.GranteeType_GRANTEE_TYPE_USER - grantee.Id = &storageprovider.Grantee_UserId{ - UserId: &userpb.UserId{ - OpaqueId: parts[1], - }, - } - case "g": - grantee.Type = storageprovider.GranteeType_GRANTEE_TYPE_GROUP - grantee.Id = &storageprovider.Grantee_GroupId{ - GroupId: &grouppb.GroupId{ - OpaqueId: parts[1], - }, - } - default: - return grantee, errorcode.New(errorcode.InvalidRequest, "invalid space permission id") - } - return grantee, nil -} - func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.ResourceId, baseURL *url.URL) (*libregraph.RemoteItem, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { @@ -461,14 +430,22 @@ func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInf parentRef.SetPath(path.Dir(res.GetPath())) driveItem.ParentReference = parentRef } - if res.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_FILE && res.GetMimeType() != "" { + switch res.GetType() { + case storageprovider.ResourceType_RESOURCE_TYPE_FILE: + mimeType := res.GetMimeType() + if mimeType == "" { + mimeType = "application/octet-stream" + } // We cannot use a libregraph.File here because the openapi codegenerator autodetects 'File' as a go type ... driveItem.File = &libregraph.OpenGraphFile{ - MimeType: &res.MimeType, + MimeType: &mimeType, + } + case storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER: + if IsSpaceRoot(res.GetId()) { + driveItem.SetRoot(map[string]any{}) + } else { + driveItem.SetFolder(libregraph.Folder{}) } - } - if res.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER { - driveItem.Folder = &libregraph.Folder{} } if res.GetArbitraryMetadata() != nil { diff --git a/services/graph/pkg/service/v0/sharedwithme.go b/services/graph/pkg/service/v0/sharedwithme.go index f6e645e193..c9d1ca92cf 100644 --- a/services/graph/pkg/service/v0/sharedwithme.go +++ b/services/graph/pkg/service/v0/sharedwithme.go @@ -10,6 +10,7 @@ import ( ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" "github.com/go-chi/render" libregraph "github.com/opencloud-eu/libre-graph-api-go" + "github.com/opencloud-eu/reva/v2/pkg/share" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" @@ -41,7 +42,11 @@ func (g Graph) listSharedWithMe(ctx context.Context, expandThumbnails bool) ([]l return nil, err } - listReceivedSharesResponse, err := gatewayClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + listReceivedSharesResponse, err := gatewayClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ + Filters: []*collaboration.Filter{ + share.SpaceRootFilter(false), + }, + }) if err := errorcode.FromCS3Status(listReceivedSharesResponse.GetStatus(), err); err != nil { g.logger.Error().Err(err).Msg("listing shares failed") return nil, err diff --git a/vendor/github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1/resources.pb.go b/vendor/github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1/resources.pb.go index 53fc1e43d2..f1b7f9c900 100644 --- a/vendor/github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1/resources.pb.go +++ b/vendor/github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1/resources.pb.go @@ -115,6 +115,10 @@ const ( Filter_TYPE_EXCLUDE_DENIALS Filter_Type = 6 Filter_TYPE_SPACE_ID Filter_Type = 7 Filter_TYPE_STATE Filter_Type = 8 + // Filter shares by whether the shared resource is a space root. + // When the term `space_root` is true, only shares on space roots are returned. + // When false, shares on space roots are excluded. + Filter_TYPE_SPACE_ROOT Filter_Type = 9 ) // Enum value maps for Filter_Type. @@ -129,6 +133,7 @@ var ( 6: "TYPE_EXCLUDE_DENIALS", 7: "TYPE_SPACE_ID", 8: "TYPE_STATE", + 9: "TYPE_SPACE_ROOT", } Filter_Type_value = map[string]int32{ "TYPE_INVALID": 0, @@ -140,6 +145,7 @@ var ( "TYPE_EXCLUDE_DENIALS": 6, "TYPE_SPACE_ID": 7, "TYPE_STATE": 8, + "TYPE_SPACE_ROOT": 9, } ) @@ -769,6 +775,7 @@ type Filter struct { // *Filter_GranteeType // *Filter_SpaceId // *Filter_State + // *Filter_SpaceRoot Term isFilter_Term `protobuf_oneof:"term"` } @@ -860,6 +867,13 @@ func (x *Filter) GetState() ShareState { return ShareState_SHARE_STATE_INVALID } +func (x *Filter) GetSpaceRoot() bool { + if x, ok := x.GetTerm().(*Filter_SpaceRoot); ok { + return x.SpaceRoot + } + return false +} + type isFilter_Term interface { isFilter_Term() } @@ -888,6 +902,11 @@ type Filter_State struct { State ShareState `protobuf:"varint,8,opt,name=state,proto3,enum=cs3.sharing.collaboration.v1beta1.ShareState,oneof"` } +type Filter_SpaceRoot struct { + // Used with TYPE_SPACE_ROOT. + SpaceRoot bool `protobuf:"varint,9,opt,name=space_root,json=spaceRoot,proto3,oneof"` +} + func (*Filter_ResourceId) isFilter_Term() {} func (*Filter_Owner) isFilter_Term() {} @@ -900,6 +919,8 @@ func (*Filter_SpaceId) isFilter_Term() {} func (*Filter_State) isFilter_Term() {} +func (*Filter_SpaceRoot) isFilter_Term() {} + var File_cs3_sharing_collaboration_v1beta1_resources_proto protoreflect.FileDescriptor var file_cs3_sharing_collaboration_v1beta1_resources_proto_rawDesc = []byte{ @@ -1017,7 +1038,7 @@ var file_cs3_sharing_collaboration_v1beta1_resources_proto_rawDesc = []byte{ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x83, 0x05, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb9, 0x05, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, @@ -1046,45 +1067,49 @@ var file_cs3_sharing_collaboration_v1beta1_resources_proto_rawDesc = []byte{ 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x22, 0xb1, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x44, 0x10, 0x02, 0x12, 0x0e, - 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x03, 0x12, 0x10, - 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x4f, 0x52, 0x10, 0x04, - 0x12, 0x15, 0x0a, 0x11, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x41, 0x4e, 0x54, 0x45, 0x45, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x45, 0x58, 0x43, 0x4c, 0x55, 0x44, 0x45, 0x5f, 0x44, 0x45, 0x4e, 0x49, 0x41, 0x4c, 0x53, 0x10, - 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x50, 0x41, 0x43, 0x45, 0x5f, - 0x49, 0x44, 0x10, 0x07, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x10, 0x08, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x2a, 0x72, 0x0a, 0x0a, - 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x48, - 0x41, 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, - 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, - 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x41, 0x43, 0x43, 0x45, - 0x50, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, - 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x03, - 0x42, 0xb3, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x73, 0x68, 0x61, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x53, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x73, 0x33, 0x6f, 0x72, 0x67, 0x2f, - 0x67, 0x6f, 0x2d, 0x63, 0x73, 0x33, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x63, 0x73, 0x33, 0x2f, 0x73, - 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6c, - 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0xa2, 0x02, 0x03, 0x43, 0x53, 0x43, 0xaa, 0x02, 0x21, 0x43, 0x73, 0x33, 0x2e, 0x53, 0x68, - 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x21, 0x43, 0x73, - 0x33, 0x5c, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, - 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, - 0x02, 0x2d, 0x43, 0x73, 0x33, 0x5c, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5c, 0x43, 0x6f, - 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x24, 0x43, 0x73, 0x33, 0x3a, 0x3a, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x3a, 0x3a, - 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x1f, 0x0a, 0x0a, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x6f, 0x6f, + 0x74, 0x22, 0xc6, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x44, 0x10, 0x02, 0x12, + 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x03, 0x12, + 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x4f, 0x52, 0x10, + 0x04, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x41, 0x4e, 0x54, 0x45, + 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x45, 0x58, 0x43, 0x4c, 0x55, 0x44, 0x45, 0x5f, 0x44, 0x45, 0x4e, 0x49, 0x41, 0x4c, 0x53, + 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x50, 0x41, 0x43, 0x45, + 0x5f, 0x49, 0x44, 0x10, 0x07, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x45, 0x10, 0x08, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x50, + 0x41, 0x43, 0x45, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x09, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x65, + 0x72, 0x6d, 0x2a, 0x72, 0x0a, 0x0a, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x17, 0x0a, 0x13, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x48, 0x41, + 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, + 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x4a, 0x45, + 0x43, 0x54, 0x45, 0x44, 0x10, 0x03, 0x42, 0xb3, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x63, + 0x73, 0x33, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, + 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x42, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x50, 0x01, 0x5a, 0x53, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x73, 0x33, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x73, 0x33, 0x61, 0x70, 0x69, 0x73, + 0x2f, 0x63, 0x73, 0x33, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2f, 0x63, 0x6f, 0x6c, + 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x3b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x53, 0x43, 0xaa, 0x02, 0x21, + 0x43, 0x73, 0x33, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, + 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0xca, 0x02, 0x21, 0x43, 0x73, 0x33, 0x5c, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5c, + 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x56, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x43, 0x73, 0x33, 0x5c, 0x53, 0x68, 0x61, 0x72, + 0x69, 0x6e, 0x67, 0x5c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x43, 0x73, 0x33, 0x3a, 0x3a, 0x53, 0x68, 0x61, + 0x72, 0x69, 0x6e, 0x67, 0x3a, 0x3a, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1269,6 +1294,7 @@ func file_cs3_sharing_collaboration_v1beta1_resources_proto_init() { (*Filter_GranteeType)(nil), (*Filter_SpaceId)(nil), (*Filter_State)(nil), + (*Filter_SpaceRoot)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/gateway/usershareprovider.go b/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/gateway/usershareprovider.go index 738faa8267..9e23a694a2 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/gateway/usershareprovider.go +++ b/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/gateway/usershareprovider.go @@ -150,7 +150,16 @@ func (s *svc) updateShare(ctx context.Context, req *collaboration.UpdateShareReq Expiration: res.GetShare().GetExpiration(), Creator: creator.GetId(), } - updateGrantStatus, err := s.updateGrant(ctx, res.GetShare().GetResourceId(), grant, nil) + var opaque *typesv1beta1.Opaque + if refIsSpaceRoot(res.GetShare().GetResourceId()) { + opaque = &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "spacegrant": {}, + }, + } + utils.AppendPlainToOpaque(opaque, "spacetype", utils.ReadPlainFromOpaque(req.GetOpaque(), "spacetype")) + } + updateGrantStatus, err := s.updateGrant(ctx, res.GetShare().GetResourceId(), grant, opaque) if err != nil { return nil, errors.Wrap(err, "gateway: error calling updateGrant") @@ -613,13 +622,22 @@ func (s *svc) addShare(ctx context.Context, req *collaboration.CreateShareReques if s.c.CommitShareToStorageGrant { // If the share is a denial we call denyGrant instead. var status *rpc.Status + var opaque *typesv1beta1.Opaque + if refIsSpaceRoot(req.ResourceInfo.Id) { + opaque = &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "spacegrant": {}, + }, + } + utils.AppendPlainToOpaque(opaque, "spacetype", req.ResourceInfo.GetSpace().GetSpaceType()) + } if grants.PermissionsEqual(req.Grant.Permissions.Permissions, &provider.ResourcePermissions{}) { - status, err = s.denyGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, nil) + status, err = s.denyGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, opaque) if err != nil { return nil, errors.Wrap(err, "gateway: error denying grant in storage") } } else { - status, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, req.Grant.Expiration, nil) + status, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, req.Grant.Expiration, opaque) if err != nil { appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg(err.Error()) rollBackFn(status) @@ -744,7 +762,15 @@ func (s *svc) removeShare(ctx context.Context, req *collaboration.RemoveShareReq } if s.c.CommitShareToStorageGrant { - removeGrantStatus, err := s.removeGrant(ctx, share.ResourceId, share.Grantee, share.Permissions.Permissions, nil) + var opaque *typesv1beta1.Opaque + if refIsSpaceRoot(share.ResourceId) { + opaque = &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "spacegrant": {}, + }, + } + } + removeGrantStatus, err := s.removeGrant(ctx, share.ResourceId, share.Grantee, share.Permissions.Permissions, opaque) if err != nil { return nil, errors.Wrap(err, "gateway: error removing grant from storage") } diff --git a/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 04c253e824..b36df8bda0 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/vendor/github.com/opencloud-eu/reva/v2/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -46,6 +46,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/rgrpc" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/status" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + "github.com/opencloud-eu/reva/v2/pkg/share" "github.com/opencloud-eu/reva/v2/pkg/sharedconf" "github.com/opencloud-eu/reva/v2/pkg/utils" "github.com/pkg/errors" @@ -1093,12 +1094,8 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere if ref.ResourceId.OpaqueId == utils.ShareStorageProviderID && ref.Path != "." { lsRes, err := sharingCollaborationClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ Filters: []*collaboration.Filter{ - { - Type: collaboration.Filter_TYPE_STATE, - Term: &collaboration.Filter_State{ - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - }, - }, + share.StateFilter(collaboration.ShareState_SHARE_STATE_ACCEPTED), + share.SpaceRootFilter(false), // TODO filter by mountpoint? }, }) @@ -1179,12 +1176,8 @@ func (s *service) fetchAcceptedShares(ctx context.Context, opaque *typesv1beta1. lsRes, err := sharingCollaborationClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ Filters: []*collaboration.Filter{ - { - Type: collaboration.Filter_TYPE_STATE, - Term: &collaboration.Filter_State{ - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - }, - }, + share.StateFilter(collaboration.ShareState_SHARE_STATE_ACCEPTED), + share.SpaceRootFilter(false), }, }) if err != nil { 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 ac3629334e..ed1d73781c 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 @@ -166,13 +166,6 @@ func (s *service) isPathAllowed(path string) bool { func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) { log := appctx.GetLogger(ctx) user := ctxpkg.ContextMustGetUser(ctx) - // Grants must not allow grant permissions - if HasGrantPermissions(req.GetGrant().GetPermissions().GetPermissions()) { - return &collaboration.CreateShareResponse{ - Status: status.NewInvalidArg(ctx, "resharing not supported"), - }, nil - } - // check if the grantee is a user or group if req.GetGrant().GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_USER { // check if the tenantId of the user matches the tenantId of the target user @@ -225,6 +218,15 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar Status: status.NewPermissionDenied(ctx, nil, "no permission to add grants on shared resource"), }, err } + // resharing is forbidden for not space roots + if !utils.IsSpaceRoot(sRes.GetInfo()) { + // Resharing of Files/Directories is forbidden. So the grants must not allow the "grant" permissions + if HasGrantPermissions(req.GetGrant().GetPermissions().GetPermissions()) { + return &collaboration.CreateShareResponse{ + Status: status.NewInvalidArg(ctx, "resharing not supported"), + }, nil + } + } // check if the share creator has sufficient permissions to do so. if shareCreationAllowed := conversions.SufficientCS3Permissions( sRes.GetInfo().GetPermissionSet(), @@ -361,13 +363,6 @@ func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShar log := appctx.GetLogger(ctx) user := ctxpkg.ContextMustGetUser(ctx) - // Grants must not allow grant permissions - if HasGrantPermissions(req.GetShare().GetPermissions().GetPermissions()) { - return &collaboration.UpdateShareResponse{ - Status: status.NewInvalidArg(ctx, "resharing not supported"), - }, nil - } - gatewayClient, err := s.gatewaySelector.Next() if err != nil { return nil, err @@ -427,6 +422,15 @@ func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShar }, err } + // resharing is forbidden for not space roots + if !utils.IsSpaceRoot(sRes.GetInfo()) { + // Resharing of Files/Directories is forbidden. So the grants must not allow the "grant" permissions + if HasGrantPermissions(req.GetShare().GetPermissions().GetPermissions()) { + return &collaboration.UpdateShareResponse{ + Status: status.NewInvalidArg(ctx, "resharing not supported"), + }, nil + } + } // If this is a permissions update, check if user's permissions on the resource are sufficient to set the desired permissions var newPermissions *provider.ResourcePermissions if slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathPermissions) { diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/share.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/share.go index 57e5c8226e..904cd41716 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/share/share.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/share/share.go @@ -136,6 +136,18 @@ func StateFilter(state collaboration.ShareState) *collaboration.Filter { } } +// SpaceRootFilter is an abstraction for filtering shares by whether the shared +// resource is a space root. Pass true to include only space-root shares (space +// membership), false to exclude them (file/folder shares only). +func SpaceRootFilter(spaceRoot bool) *collaboration.Filter { + return &collaboration.Filter{ + Type: collaboration.Filter_TYPE_SPACE_ROOT, + Term: &collaboration.Filter_SpaceRoot{ + SpaceRoot: spaceRoot, + }, + } +} + // IsCreatedByUser checks if the user is the owner or creator of the share. func IsCreatedByUser(share *collaboration.Share, user *userv1beta1.User) bool { return utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator) @@ -172,6 +184,9 @@ func MatchesFilter(share *collaboration.Share, state collaboration.ShareState, f return share.ResourceId.SpaceId == filter.GetSpaceId() case collaboration.Filter_TYPE_STATE: return state == filter.GetState() + case collaboration.Filter_TYPE_SPACE_ROOT: + isSpaceRoot := share.ResourceId.SpaceId == share.ResourceId.OpaqueId + return isSpaceRoot == filter.GetSpaceRoot() default: return false } diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/config.go b/vendor/github.com/tus/tusd/v2/pkg/handler/config.go index 480fa243c2..4efa43598c 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/config.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/config.go @@ -33,6 +33,9 @@ type Config struct { // DisableTermination indicates whether the server will refuse termination // requests of the uploaded file, by not mounting the DELETE handler. DisableTermination bool + // DisableConcatenation indicates whether the server will refuse POST requests + // for creating uploads that use the concatenation extension. + DisableConcatenation bool // Cors can be used to customize the handling of Cross-Origin Resource Sharing (CORS). // See the CorsConfig struct for more details. // Defaults to DefaultCorsConfig. diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/context.go b/vendor/github.com/tus/tusd/v2/pkg/handler/context.go index 892009bab5..3348a4cf58 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/context.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/context.go @@ -80,18 +80,13 @@ func (h UnroutedHandler) getContext(w http.ResponseWriter, r *http.Request) *htt return c } -func (c httpContext) Value(key any) any { - // We overwrite the Value function to ensure that the values from the request - // context are returned because c.Context does not contain any values. - return c.req.Context().Value(key) -} - -// newDelayedContext returns a context that is cancelled with a delay. If the parent context +// newDelayedContext returns a context with delayed cancellation propagation. If the parent context // is done, the new context will also be cancelled but only after waiting the specified delay. // Note: The parent context MUST be cancelled or otherwise this will leak resources. In the // case of http.Request.Context, the net/http package ensures that the context is always cancelled. func newDelayedContext(parent context.Context, delay time.Duration) context.Context { - ctx, cancel := context.WithCancel(context.Background()) + // Use context.WithoutCancel to preserve the values. + ctx, cancel := context.WithCancel(context.WithoutCancel(parent)) go func() { <-parent.Done() <-time.After(delay) diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/hooks.go b/vendor/github.com/tus/tusd/v2/pkg/handler/hooks.go index c1c6f11333..1b0e274865 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/hooks.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/hooks.go @@ -33,7 +33,8 @@ func newHookEvent(c *httpContext, info FileInfo) HookEvent { // > For incoming requests, the Host header is promoted to the // > Request.Host field and removed from the Header map. // That's why we add it back manually. - c.req.Header.Set("Host", c.req.Host) + copiedHeader := c.req.Header.Clone() + copiedHeader.Set("Host", c.req.Host) return HookEvent{ Context: c, @@ -42,7 +43,7 @@ func newHookEvent(c *httpContext, info FileInfo) HookEvent { Method: c.req.Method, URI: c.req.RequestURI, RemoteAddr: c.req.RemoteAddr, - Header: c.req.Header, + Header: copiedHeader, }, } } diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/http.go b/vendor/github.com/tus/tusd/v2/pkg/handler/http.go index d521fd1ac4..802ba97024 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/http.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/http.go @@ -1,6 +1,7 @@ package handler import ( + "maps" "net/http" "strconv" ) @@ -67,13 +68,9 @@ func (resp1 HTTPResponse) MergeWith(resp2 HTTPResponse) HTTPResponse { // into the header map from response 1. newResp.Header = make(HTTPHeader, len(resp1.Header)+len(resp2.Header)) - for key, value := range resp1.Header { - newResp.Header[key] = value - } + maps.Copy(newResp.Header, resp1.Header) - for key, value := range resp2.Header { - newResp.Header[key] = value - } + maps.Copy(newResp.Header, resp2.Header) return newResp } diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/metrics.go b/vendor/github.com/tus/tusd/v2/pkg/handler/metrics.go index 1a2c6aad99..ddd7af3174 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/metrics.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/metrics.go @@ -1,6 +1,7 @@ package handler import ( + "maps" "sync" "sync/atomic" ) @@ -123,9 +124,7 @@ func (e *ErrorsTotalMap) retrievePointerFor(err Error) *uint64 { func (e *ErrorsTotalMap) Load() map[ErrorsTotalMapEntry]*uint64 { m := make(map[ErrorsTotalMapEntry]*uint64, len(e.counter)) e.lock.RLock() - for err, ptr := range e.counter { - m[err] = ptr - } + maps.Copy(m, e.counter) e.lock.RUnlock() return m diff --git a/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go b/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go index 9cb7be6278..3053c7de6b 100644 --- a/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go +++ b/vendor/github.com/tus/tusd/v2/pkg/handler/unrouted_handler.go @@ -54,6 +54,7 @@ var ( ErrNotImplemented = NewError("ERR_NOT_IMPLEMENTED", "feature not implemented", http.StatusNotImplemented) ErrUploadNotFinished = NewError("ERR_UPLOAD_NOT_FINISHED", "one of the partial uploads is not finished", http.StatusBadRequest) ErrInvalidConcat = NewError("ERR_INVALID_CONCAT", "invalid Upload-Concat header", http.StatusBadRequest) + ErrConcatenationUnsupported = NewError("ERR_CONCATENATION_UNSUPPORTED", "Upload-Concat header is not supported by server", http.StatusBadRequest) ErrModifyFinal = NewError("ERR_MODIFY_FINAL", "modifying a final upload is not allowed", http.StatusForbidden) ErrUploadLengthAndUploadDeferLength = NewError("ERR_AMBIGUOUS_UPLOAD_LENGTH", "provided both Upload-Length and Upload-Defer-Length", http.StatusBadRequest) ErrInvalidUploadDeferLength = NewError("ERR_INVALID_UPLOAD_LENGTH_DEFER", "invalid Upload-Defer-Length header", http.StatusBadRequest) @@ -125,10 +126,10 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) { // Only promote extesions using the Tus-Extension header which are implemented extensions := "creation,creation-with-upload" - if config.StoreComposer.UsesTerminater { + if config.StoreComposer.UsesTerminater && !config.DisableTermination { extensions += ",termination" } - if config.StoreComposer.UsesConcater { + if config.StoreComposer.UsesConcater && !config.DisableConcatenation { extensions += ",concatenation" } if config.StoreComposer.UsesLengthDeferrer { @@ -299,6 +300,11 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) concatHeader = r.Header.Get("Upload-Concat") } + if concatHeader != "" && handler.config.DisableConcatenation { + handler.sendError(c, ErrConcatenationUnsupported) + return + } + // Parse Upload-Concat header isPartial, isFinal, partialUploadIDs, err := parseConcat(concatHeader, handler.basePath) if err != nil { @@ -1574,7 +1580,7 @@ func getIETFDraftUploadLength(r *http.Request) (length int64, lengthIsDeferred b func ParseMetadataHeader(header string) map[string]string { meta := make(map[string]string) - for _, element := range strings.Split(header, ",") { + for element := range strings.SplitSeq(header, ",") { element := strings.TrimSpace(element) parts := strings.Split(element, " ") @@ -1640,8 +1646,8 @@ func parseConcat(header string, basePath string) (isPartial bool, isFinal bool, if strings.HasPrefix(header, "final;") && len(header) > l { isFinal = true - list := strings.Split(header[l:], " ") - for _, value := range list { + list := strings.SplitSeq(header[l:], " ") + for value := range list { value := strings.TrimSpace(value) if value == "" { continue diff --git a/vendor/modules.txt b/vendor/modules.txt index a18475d00d..2f3e0c8734 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -315,7 +315,7 @@ github.com/crewjam/saml github.com/crewjam/saml/logger github.com/crewjam/saml/samlsp github.com/crewjam/saml/xmlenc -# github.com/cs3org/go-cs3apis v0.0.0-20260310080202-fb97596763d6 +# github.com/cs3org/go-cs3apis v0.0.0-20260310080202-fb97596763d6 => github.com/rhafer/go-cs3apis v0.0.0-20260312152349-0b5ca0d4b142 ## explicit; go 1.21 github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1 @@ -1370,7 +1370,7 @@ github.com/opencloud-eu/icap-client # github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d ## explicit; go 1.18 github.com/opencloud-eu/libre-graph-api-go -# github.com/opencloud-eu/reva/v2 v2.42.6-0.20260311175421-d77bc89ffe35 +# github.com/opencloud-eu/reva/v2 v2.42.6-0.20260311175421-d77bc89ffe35 => github.com/rhafer/reva/v2 v2.0.0-20260317140355-5ba923b2b4a0 ## explicit; go 1.25.0 github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace github.com/opencloud-eu/reva/v2/cmd/revad/runtime @@ -2125,8 +2125,8 @@ github.com/trustelem/zxcvbn/internal/mathutils github.com/trustelem/zxcvbn/match github.com/trustelem/zxcvbn/matching github.com/trustelem/zxcvbn/scoring -# github.com/tus/tusd/v2 v2.8.0 -## explicit; go 1.23.0 +# github.com/tus/tusd/v2 v2.9.2 +## explicit; go 1.25.0 github.com/tus/tusd/v2/pkg/handler # github.com/unrolled/secure v1.16.0 => github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 ## explicit; go 1.13 @@ -2570,8 +2570,8 @@ golang.org/x/tools/internal/stdlib golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal golang.org/x/tools/internal/versions -# google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb -## explicit; go 1.23.0 +# google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 +## explicit; go 1.24.0 google.golang.org/genproto/protobuf/field_mask # google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 ## explicit; go 1.24.0 @@ -2732,3 +2732,5 @@ stash.kopano.io/kgol/rndm # github.com/unrolled/secure => github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 # go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.20241115112658-b5d4de5ed9b3 # github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a +# github.com/cs3org/go-cs3apis => github.com/rhafer/go-cs3apis v0.0.0-20260312152349-0b5ca0d4b142 +# github.com/opencloud-eu/reva/v2 => github.com/rhafer/reva/v2 v2.0.0-20260317140355-5ba923b2b4a0