diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index eaab359763e..75e119ac9fc 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -206,7 +206,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/internal/grpc/services/usershareprovider/usershareprovider_test.go b/internal/grpc/services/usershareprovider/usershareprovider_test.go index 0d46625388f..1742479e140 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider_test.go +++ b/internal/grpc/services/usershareprovider/usershareprovider_test.go @@ -435,6 +435,7 @@ var _ = Describe("user share provider service", func() { Entry("ListGrants", conversions.RoleFromName("manager").CS3ResourcePermissions(), &providerpb.ResourcePermissions{ListGrants: true}, rpcpb.Code_CODE_OK, 1), ) }) + Context("create share with tenant awareness", func() { JustBeforeEach(func() { rgrpcService := usershareprovider.New(gatewaySelector, manager, []*regexp.Regexp{}) @@ -490,6 +491,28 @@ var _ = Describe("user share provider service", func() { manager.AssertNumberOfCalls(GinkgoT(), "Share", 1) }) + + It("succeeds when sharing with a guest using an email address", func() { + createShareResponse, err := provider.CreateShare(ctx, &collaborationpb.CreateShareRequest{ + ResourceInfo: &providerpb.ResourceInfo{ + PermissionSet: conversions.RoleFromName("manager").CS3ResourcePermissions(), + }, + Grant: &collaborationpb.ShareGrant{ + Grantee: &providerpb.Grantee{ + Type: providerpb.GranteeType_GRANTEE_TYPE_GUEST, + Id: &providerpb.Grantee_Email{Email: "guest@example.com"}, + }, + Permissions: &collaborationpb.SharePermissions{ + Permissions: conversions.RoleFromName("viewer").CS3ResourcePermissions(), + }, + }, + }) + + Expect(err).ToNot(HaveOccurred()) + Expect(createShareResponse.Status.Code).To(Equal(rpcpb.Code_CODE_OK)) + + manager.AssertNumberOfCalls(GinkgoT(), "Share", 1) + }) }) }) diff --git a/pkg/storage/fs/posix/tree/tree.go b/pkg/storage/fs/posix/tree/tree.go index f3f8fb9c15e..708fad80e44 100644 --- a/pkg/storage/fs/posix/tree/tree.go +++ b/pkg/storage/fs/posix/tree/tree.go @@ -155,7 +155,14 @@ func New(lu node.PathLookup, bs node.Blobstore, um usermapper.Mapper, trashbin * return nil, err } case "natswatcher": - t.watcher, err = natswatcher.New(context.TODO(), t, o.NatsWatcher, o.WatchRoot, log) + cfg := options.NatsWatcherConfig{ + Endpoint: "nats://localhost:9233", + Cluster: "test-cluster", + Durable: "natswatcher", + Stream: "leilfs-events", + TLSInsecure: true, + } + t.watcher, err = natswatcher.New(context.TODO(), t, cfg, o.WatchRoot, log) if err != nil { return nil, err } diff --git a/pkg/storage/pkg/decomposedfs/decomposedfs.go b/pkg/storage/pkg/decomposedfs/decomposedfs.go index 82260a60933..93093cb9c53 100644 --- a/pkg/storage/pkg/decomposedfs/decomposedfs.go +++ b/pkg/storage/pkg/decomposedfs/decomposedfs.go @@ -128,6 +128,7 @@ type Decomposedfs struct { UserCache *ttlcache.Cache userSpaceIndex *spaceidindex.Index groupSpaceIndex *spaceidindex.Index + mailSpaceIndex *spaceidindex.Index spaceTypeIndex *spaceidindex.Index log *zerolog.Logger @@ -216,6 +217,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, "indexes"), "by-mail") + err = mailSpaceIndex.Init() + if err != nil { + return nil, err + } spaceTypeIndex := spaceidindex.New(filepath.Join(o.Root, "indexes"), "by-type") err = spaceTypeIndex.Init() if err != nil { @@ -242,6 +248,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/pkg/storage/pkg/decomposedfs/grants.go b/pkg/storage/pkg/decomposedfs/grants.go index a51b24c4f5d..773a0199c44 100644 --- a/pkg/storage/pkg/decomposedfs/grants.go +++ b/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" @@ -235,8 +236,14 @@ func (fs *Decomposedfs) RemoveGrant(ctx context.Context, ref *provider.Reference 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 + if g.Grantee.GetUserId().GetType() == userpb.UserType_USER_TYPE_GUEST { + if err := fs.mailSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), grantNode.SpaceID); err != nil { + return err + } + } else { + if err := fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), grantNode.SpaceID); err != nil { + return err + } } case provider.GranteeType_GRANTEE_TYPE_GROUP: // remove from group index diff --git a/pkg/storage/pkg/decomposedfs/grants_test.go b/pkg/storage/pkg/decomposedfs/grants_test.go index 963029954e2..86d8f8d4a00 100644 --- a/pkg/storage/pkg/decomposedfs/grants_test.go +++ b/pkg/storage/pkg/decomposedfs/grants_test.go @@ -34,19 +34,24 @@ import ( var _ = Describe("Grants", func() { var ( - env *helpers.DecomposedTestEnv - ref *provider.Reference - grant *provider.Grant + env *helpers.DecomposedTestEnv + ref *provider.Reference + grant *provider.Grant + userid *userpb.UserId ) BeforeEach(func() { + userid = &userpb.UserId{ + OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", + } + }) + + JustBeforeEach(func() { grant = &provider.Grant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", - }, + UserId: userid, }, }, Permissions: &provider.ResourcePermissions{ @@ -59,9 +64,7 @@ var _ = Describe("Grants", func() { OpaqueId: helpers.OwnerID, }, } - }) - JustBeforeEach(func() { var err error env, err = helpers.NewTestEnv(nil) Expect(err).ToNot(HaveOccurred()) @@ -178,5 +181,34 @@ var _ = Describe("Grants", func() { Expect(len(grants)).To(Equal(0)) }) }) + + Context("with guest grants", func() { + BeforeEach(func() { + userid = &userpb.UserId{ + OpaqueId: "foo@example.com", + Type: userpb.UserType_USER_TYPE_GUEST, + } + }) + + It("adds, lists and removes the guest grant", func() { + err := env.Fs.AddGrant(env.Ctx, ref, grant) + Expect(err).ToNot(HaveOccurred()) + + grants, err := env.Fs.ListGrants(env.Ctx, ref) + Expect(err).ToNot(HaveOccurred()) + Expect(len(grants)).To(Equal(1)) + + g := grants[0] + Expect(g.Grantee.GetUserId().OpaqueId).To(Equal(grant.Grantee.GetUserId().OpaqueId)) + Expect(g.Grantee.GetUserId().Type).To(Equal(userpb.UserType_USER_TYPE_GUEST)) + + err = env.Fs.RemoveGrant(env.Ctx, ref, grant) + Expect(err).ToNot(HaveOccurred()) + + grants, err = env.Fs.ListGrants(env.Ctx, ref) + Expect(err).ToNot(HaveOccurred()) + Expect(len(grants)).To(Equal(0)) + }) + }) }) }) diff --git a/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go b/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go index 843a02c7893..20f58a15721 100644 --- a/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go +++ b/pkg/storage/pkg/decomposedfs/metadata/prefixes/prefixes.go @@ -55,6 +55,7 @@ const ( GrantPrefix string = OcPrefix + "grant." GrantUserAcePrefix string = OcPrefix + "grant." + UserAcePrefix GrantGroupAcePrefix string = OcPrefix + "grant." + GroupAcePrefix + GrantMailAcePrefix string = OcPrefix + "grant." + MailAcePrefix MetadataPrefix string = OcPrefix + "md." // favorite flag, per user @@ -105,6 +106,7 @@ const ( UserAcePrefix string = "u:" GroupAcePrefix string = "g:" + MailAcePrefix string = "m:" ) func FavoriteKey(uid *userpb.UserId) string { diff --git a/pkg/storage/pkg/decomposedfs/node/node.go b/pkg/storage/pkg/decomposedfs/node/node.go index 810cc2362e1..72fb8c17103 100644 --- a/pkg/storage/pkg/decomposedfs/node/node.go +++ b/pkg/storage/pkg/decomposedfs/node/node.go @@ -1234,10 +1234,15 @@ func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant func (n *Node) DeleteGrant(ctx context.Context, g *provider.Grant, acquireLock bool) (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 + default: + prefix := prefixes.GrantUserAcePrefix + if g.Grantee.GetUserId().GetType() == userpb.UserType_USER_TYPE_GUEST { + prefix = prefixes.GrantMailAcePrefix + } + attr = prefix + g.Grantee.GetUserId().OpaqueId } if err = n.RemoveXattr(ctx, attr, acquireLock); err != nil { diff --git a/pkg/storage/pkg/decomposedfs/spaces.go b/pkg/storage/pkg/decomposedfs/spaces.go index 0942cc3ee25..ad2f4ebabee 100644 --- a/pkg/storage/pkg/decomposedfs/spaces.go +++ b/pkg/storage/pkg/decomposedfs/spaces.go @@ -854,6 +854,9 @@ func (fs *Decomposedfs) updateIndexes(ctx context.Context, grantee *provider.Gra // create space grant index switch grantee.Type { case provider.GranteeType_GRANTEE_TYPE_USER: + if grantee.GetUserId().GetType() == userv1beta1.UserType_USER_TYPE_GUEST { + return fs.linkSpaceByMail(ctx, grantee.GetUserId().GetOpaqueId(), spaceID, target) + } return fs.linkSpaceByUser(ctx, grantee.GetUserId().GetOpaqueId(), spaceID, target) case provider.GranteeType_GRANTEE_TYPE_GROUP: return fs.linkSpaceByGroup(ctx, grantee.GetGroupId().GetOpaqueId(), spaceID, target) @@ -870,6 +873,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) } @@ -943,10 +950,18 @@ 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") + if g.Grantee.GetUserId().GetType() == userv1beta1.UserType_USER_TYPE_GUEST { + // remove from mail index + if err := fs.mailSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), 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(g.Grantee.GetUserId().GetOpaqueId(), 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/pkg/storage/utils/ace/ace.go b/pkg/storage/utils/ace/ace.go index ef3da2ee29e..e90592982f1 100644 --- a/pkg/storage/utils/ace/ace.go +++ b/pkg/storage/utils/ace/ace.go @@ -215,7 +215,12 @@ func FromGrant(g *provider.Grant) *ACE { } func UserAce(id *userpb.UserId) string { - return "u:" + id.OpaqueId + switch id.GetType() { + case userpb.UserType_USER_TYPE_GUEST: + return "m:" + id.OpaqueId + default: + return "u:" + id.OpaqueId + } } // Principal returns the principal of the ACE, eg. `u:` or `g:` @@ -260,7 +265,7 @@ func Unmarshal(principal string, v []byte) (e *ACE, err error) { return nil, fmt.Errorf("inconsistent ace: expected group") } } else { - if principal[:1] != "u" { + if principal[:1] != "u" && principal[:1] != "m" { return nil, fmt.Errorf("inconsistent ace: expected user") } } @@ -288,7 +293,11 @@ 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, "m:") { + g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: id, Type: userpb.UserType_USER_TYPE_GUEST}} + } 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/pkg/storage/utils/ace/ace_test.go b/pkg/storage/utils/ace/ace_test.go index a98ae5aa106..5651cf94bbc 100644 --- a/pkg/storage/utils/ace/ace_test.go +++ b/pkg/storage/utils/ace/ace_test.go @@ -63,6 +63,20 @@ var _ = Describe("ACE", func() { Permissions: &provider.ResourcePermissions{ CreateContainer: true, }, + Creator: &userpb.UserId{}, + } + + guestGrant = &provider.Grant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_MAIL, + Id: &provider.Grantee_Mail{ + Mail: "guest@example.com", + }, + }, + Permissions: &provider.ResourcePermissions{ + CreateContainer: true, + }, + Creator: &userpb.UserId{}, } ) @@ -76,6 +90,12 @@ var _ = Describe("ACE", func() { ace := ace.FromGrant(groupGrant) Expect(ace.Principal()).To(Equal("g:foo")) }) + + It("creates an ACE from a guest grant", func() { + guestGrant.Grantee.Id = &provider.Grantee_Mail{Mail: "GUEST@example.com"} + ace := ace.FromGrant(guestGrant) + Expect(ace.Principal()).To(Equal("m:guest@example.com")) + }) }) Describe("Grant", func() { @@ -86,6 +106,23 @@ var _ = Describe("ACE", func() { grant.Grantee.Opaque = nil Expect(grant).To(BeComparableTo(userGrant, protocmp.Transform())) }) + + It("returns a proper Grant for group ACE", func() { + ace := ace.FromGrant(groupGrant) + grant := ace.Grant() + // do not check opaque values + grant.Grantee.Opaque = nil + Expect(grant).To(BeComparableTo(groupGrant, protocmp.Transform())) + }) + + It("returns a proper Grant for guest ACE", func() { + guestGrant.Grantee.Id = &provider.Grantee_Mail{Mail: "guest@example.com"} + ace := ace.FromGrant(guestGrant) + grant := ace.Grant() + // do not check opaque values + grant.Grantee.Opaque = nil + Expect(grant).To(BeComparableTo(guestGrant, protocmp.Transform())) + }) }) Describe("marshalling", func() {