diff --git a/go.mod b/go.mod index f916b3a..726d755 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mileusna/useragent v1.3.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -58,11 +59,12 @@ require ( golang.org/x/crypto v0.23.0 // indirect golang.org/x/image v0.13.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.9.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/protobuf v1.34.1 // indirect + gorm.io/driver/sqlite v1.5.7 // indirect ) replace github.com/cs3org/reva => ../reva diff --git a/go.sum b/go.sum index ab0bd75..625084c 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -206,6 +208,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -241,6 +245,8 @@ gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/share/model.go b/share/model.go index 040fa3f..8621398 100644 --- a/share/model.go +++ b/share/model.go @@ -47,7 +47,7 @@ import ( // | notify_uploads_extra_recipients | varchar(2048) | YES | | NULL | | // +---------------------------------+-----------------+------+-----+---------+----------------+ -type protoShare struct { +type ProtoShare struct { // Including gorm.Model will embed a number of gorm-default fields // such as creation_time, id etc gorm.Model @@ -66,13 +66,16 @@ type protoShare struct { } type Share struct { - protoShare + // Including gorm.Model will embed a number of gorm-default fields + // such as creation_time, id etc + + ProtoShare ShareWith string SharedWithIsGroup bool } type PublicLink struct { - protoShare + ProtoShare Token string // Enforce uniqueness in db re: Itemsource Quicklink bool @@ -86,7 +89,7 @@ type PublicLink struct { // Unique index on combo of (shareid, user) type ShareState struct { gorm.Model - ShareId uint //foreign key to share + ShareID uint //foreign key to share // Can not be uid because of lw accs User string Synced bool @@ -99,7 +102,7 @@ func (s *Share) AsCS3Share(granteeType userpb.UserType) *collaboration.Share { } return &collaboration.Share{ Id: &collaboration.ShareId{ - OpaqueId: strconv.Itoa(int(s.ID)), + OpaqueId: strconv.FormatUint(uint64(s.ID), 10), }, //ResourceId: &provider.Reference{StorageId: s.Prefix, NodeId: s.ItemSource}, ResourceId: &provider.ResourceId{ @@ -116,9 +119,18 @@ func (s *Share) AsCS3Share(granteeType userpb.UserType) *collaboration.Share { } func (s *Share) AsCS3ReceivedShare(state *ShareState, granteeType userpb.UserType) *collaboration.ReceivedShare { + // Currently, some implementations still rely on the ShareState to determine whether a file is hidden + // instead of using the field + var rsharestate resourcespb.ShareState + if state.Hidden { + rsharestate = resourcespb.ShareState_SHARE_STATE_REJECTED + } else { + rsharestate = resourcespb.ShareState_SHARE_STATE_ACCEPTED + } + return &collaboration.ReceivedShare{ Share: s.AsCS3Share(granteeType), - State: resourcespb.ShareState_SHARE_STATE_ACCEPTED, + State: rsharestate, Hidden: state.Hidden, } } @@ -166,44 +178,21 @@ func (p *PublicLink) AsCS3PublicShare() *link.PublicShare { } } -// The package 'conversions' is currently internal in Reva -// It should become public so we can use it here -// Since it generates CS3ResourcePermissions I'm not sure why it would be private - -// IntTosharePerm retrieves read/write permissions from an integer. -func intTosharePerm(p int, itemType string) *provider.ResourcePermissions { - switch p { - case 1: - return conversions.NewViewerRole().CS3ResourcePermissions() - case 15: - if itemType == "folder" { - return conversions.NewEditorRole().CS3ResourcePermissions() - } - return conversions.NewFileEditorRole().CS3ResourcePermissions() - case 4: - return conversions.NewUploaderRole().CS3ResourcePermissions() - default: - // TODO we may have other options, for now this is a denial - return &provider.ResourcePermissions{} - } -} - // ExtractGrantee retrieves the CS3API Grantee from a grantee type and username/groupname. // The grantee userType is relevant only for users. func extractGrantee(sharedWithIsGroup bool, g string, gtype userpb.UserType) *provider.Grantee { var grantee provider.Grantee if sharedWithIsGroup { - grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER - grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{ + grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP + grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{ OpaqueId: g, - Type: gtype, }} } else { - grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP - grantee.Id = &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{ + grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER + grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{ OpaqueId: g, + Type: gtype, }} } - return &grantee } diff --git a/share/sql/sql.go b/share/sql/sql.go index a1c12a8..4f3924f 100644 --- a/share/sql/sql.go +++ b/share/sql/sql.go @@ -41,6 +41,7 @@ import ( "github.com/cs3org/reva/pkg/utils/cfg" "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" "gorm.io/gorm" // Provides mysql drivers. @@ -68,6 +69,7 @@ func (mgr) RevaPlugin() reva.PluginInfo { } type config struct { + Engine string `mapstructure:"engine"` // mysql | sqlite DBUsername string `mapstructure:"db_username"` DBPassword string `mapstructure:"db_password"` DBHost string `mapstructure:"db_host"` @@ -93,9 +95,17 @@ func New(ctx context.Context, m map[string]interface{}) (revashare.Manager, erro return nil, err } - dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DBUsername, c.DBPassword, c.DBHost, c.DBPort, c.DBName) - db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) - // db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DBUsername, c.DBPassword, c.DBHost, c.DBPort, c.DBName)) + var db *gorm.DB + var err error + switch c.Engine { + case "mysql": + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DBUsername, c.DBPassword, c.DBHost, c.DBPort, c.DBName) + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + case "sqlite": + db, err = gorm.Open(sqlite.Open(c.DBName), &gorm.Config{}) + default: + return nil, errors.New("ShareManager SQL: unsupported database type " + c.Engine) + } if err != nil { return nil, err } @@ -126,14 +136,24 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora Grantee: g.Grantee, } _, err := m.getByKey(ctx, key, true) - // share already exists + // TODO stricter error checking if err == nil { - return nil, errtypes.AlreadyExists(key.String()) + return nil, errors.New(errtypes.AlreadyExists(key.String()).Error()) + } + + var shareWith string + if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { + shareWith = conversions.FormatUserID(g.Grantee.GetUserId()) + } else if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + // ShareWith is a group + shareWith = g.Grantee.GetGroupId().OpaqueId + } else { + return nil, errors.New("Unsuppored grantee type passed to Share()") } share := &model.Share{ - ShareWith: conversions.FormatUserID(g.Grantee.GetUserId()), + ShareWith: shareWith, SharedWithIsGroup: g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP, } share.UIDOwner = conversions.FormatUserID(md.Owner) @@ -172,14 +192,14 @@ func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey, checkOw uid := conversions.FormatUserID(appctx.ContextMustGetUser(ctx).Id) var share model.Share - shareType, shareWith := conversions.FormatGrantee(key.Grantee) + _, shareWith := conversions.FormatGrantee(key.Grantee) query := m.db.Model(&share). Where("orphan = ?", false). Where("uid_owner = ?", owner). - Where("fileid_prefix = ?", key.ResourceId.StorageId). - Where("item_source = ?", key.ResourceId.OpaqueId). - Where("share_type = ?", shareType). + Where("instance = ?", key.ResourceId.StorageId). + Where("inode = ?", key.ResourceId.OpaqueId). + Where("shared_with_is_group = ?", key.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP). Where("share_with = ?", strings.ToLower(shareWith)) if checkOwner { @@ -206,7 +226,6 @@ func (m *mgr) getShare(ctx context.Context, ref *collaboration.ShareReference) ( return nil, err } case ref.GetKey() != nil: - //s, err = m.getByKey(ctx, ref.GetKey(), userpb.UserType_USER_TYPE_INVALID, false) s, err = m.getByKey(ctx, ref.GetKey(), false) if err != nil { return nil, err @@ -215,6 +234,11 @@ func (m *mgr) getShare(ctx context.Context, ref *collaboration.ShareReference) ( return nil, errtypes.NotFound(ref.String()) } + user := appctx.ContextMustGetUser(ctx) + if s.UIDOwner == user.Id.OpaqueId && s.UIDInitiator == user.Id.OpaqueId { + return s, nil + } + path, err := m.getPath(ctx, &provider.ResourceId{ StorageId: s.Instance, OpaqueId: s.Inode, @@ -223,15 +247,10 @@ func (m *mgr) getShare(ctx context.Context, ref *collaboration.ShareReference) ( return nil, err } - user := appctx.ContextMustGetUser(ctx) if m.isProjectAdmin(user, path) { return s, nil } - if s.UIDOwner == user.Id.OpaqueId && s.UIDInitiator == user.Id.OpaqueId { - return s, nil - } - return s, nil } @@ -248,7 +267,7 @@ func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) ( } func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { - share, err := m.GetShare(ctx, ref) + share, err := m.getShare(ctx, ref) if err != nil { return err } @@ -319,10 +338,9 @@ func (m *mgr) isProjectAdmin(u *userpb.User, path string) bool { } func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { - var share model.Share uid := conversions.FormatUserID(appctx.ContextMustGetUser(ctx).Id) - query := m.db.Model(&share). + query := m.db.Model(&model.Share{}). Where("uid_owner = ? or uid_initiator = ?", uid, uid). Where("orphan = ?", false) @@ -338,7 +356,12 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( for _, s := range shares { granteeType, _ := m.getUserType(ctx, s.ShareWith) - cs3shares = append(cs3shares, share.AsCS3Share(granteeType)) + cs3share := s.AsCS3Share(granteeType) + cs3shares = append(cs3shares, cs3share) + } + + if len(shares) > 0 { + fmt.Printf("First cs3share has id %s\n", cs3shares[0].Id.OpaqueId) } return cs3shares, nil @@ -346,10 +369,9 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( // we list the shares that are targeted to the user in context or to the user groups. func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { - var share model.Share user := appctx.ContextMustGetUser(ctx) - query := m.db.Model(&share). + query := m.db.Model(&model.Share{}). Where("orphan = ?", false) // Also search by all the groups the user is a member of @@ -374,7 +396,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F for _, s := range shares { shareId := &collaboration.ShareId{ - OpaqueId: strconv.Itoa(int(s.ID)), + OpaqueId: strconv.FormatUint(uint64(s.ID), 10), } shareState, err := m.getShareState(ctx, shareId, user) if err != nil { @@ -383,7 +405,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F granteeType, _ := m.getUserType(ctx, s.ShareWith) - receivedShares = append(receivedShares, share.AsCS3ReceivedShare(shareState, granteeType)) + receivedShares = append(receivedShares, s.AsCS3ReceivedShare(shareState, granteeType)) } return receivedShares, nil @@ -398,7 +420,20 @@ func (m *mgr) getShareState(ctx context.Context, shareId *collaboration.ShareId, res := query.First(&shareState) if res.RowsAffected == 0 { - return nil, errtypes.NotFound(shareId.OpaqueId) + shareIdInt, err := strconv.Atoi(shareId.OpaqueId) + if err != nil { + return nil, errors.New("Failed to fetch shareState, and failed to create one (share_id is not an int)") + } + // If no share state has been created yet, we create it now using these defaults + shareState = model.ShareState{ + ShareID: uint(shareIdInt), + Hidden: false, + Synced: false, + User: user.Username, + } + // Does not really matter if it fails, next time the user + // lists his shares this will just be called again + m.db.Save(&shareState) } return &shareState, nil @@ -466,41 +501,46 @@ func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareRefe func (m *mgr) UpdateReceivedShare(ctx context.Context, share *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { - // user := appctx.ContextMustGetUser(ctx) + user := appctx.ContextMustGetUser(ctx) rs, err := m.GetReceivedShare(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: share.Share.Id}}) if err != nil { return nil, err } - // TODO: do actual update based on FieldMask + shareState, err := m.getShareState(ctx, share.Share.Id, user) + if err != nil { + return nil, err + } + + // FieldMask determines which parts of the share we actually update + // Right now, only updating the state is supported + for _, path := range fieldMask.Paths { + switch path { + case "state": + rs.State = share.State + case "hidden": + rs.Hidden = share.Hidden + default: + return nil, errtypes.NotSupported("updating " + path + " is not supported") + } + } + + // Now we do the actual update to the db model + switch rs.State { + case collaboration.ShareState_SHARE_STATE_ACCEPTED: + shareState.Hidden = false + case collaboration.ShareState_SHARE_STATE_REJECTED: + fmt.Printf("Updating share state to hidden\n") + shareState.Hidden = true + } - res := m.db.Save(&rs.State) + res := m.db.Save(&shareState) if res.Error != nil { return nil, res.Error } return rs, nil - - // for i := range fieldMask.Paths { - // switch fieldMask.Paths[i] { - // case "state": - // rs.State = share.State - // default: - // return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") - // } - // } - - // state := 0 - // switch rs.GetState() { - // case collaboration.ShareState_SHARE_STATE_REJECTED: - // state = -1 - // case collaboration.ShareState_SHARE_STATE_ACCEPTED: - // state = 1 - // } - - // params := []interface{}{rs.Share.Id.OpaqueId, conversions.FormatUserID(user.Id), state, state} - // query := "insert into oc_share_status(id, recipient, state) values(?, ?, ?) ON DUPLICATE KEY UPDATE state = ?" } func (m *mgr) getUserType(ctx context.Context, username string) (userpb.UserType, error) { @@ -542,14 +582,6 @@ func (m *mgr) appendFiltersToQuery(query *gorm.DB, filters []*collaboration.Filt query = query.Where(innerQuery) case collaboration.Filter_TYPE_EXCLUDE_DENIALS: query = query.Where("permissions > ?", 0) - case collaboration.Filter_TYPE_CREATOR: - break - case collaboration.Filter_TYPE_OWNER: - break - case collaboration.Filter_TYPE_STATE: - break - case collaboration.Filter_TYPE_SPACE_ID: - break case collaboration.Filter_TYPE_GRANTEE_TYPE: innerQuery := m.db for i, filter := range filters { diff --git a/share/sql/sql_test.go b/share/sql/sql_test.go new file mode 100644 index 0000000..c85d5a3 --- /dev/null +++ b/share/sql/sql_test.go @@ -0,0 +1,669 @@ +package sql + +import ( + "context" + "log" + "os" + "testing" + "time" + + groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + conversions "github.com/cs3org/reva/pkg/cbox/utils" + revashare "github.com/cs3org/reva/pkg/share" + "google.golang.org/genproto/protobuf/field_mask" +) + +// =========================== +// Helper functions for tests +// =========================== + +// You can use testing.T, if you want to test the code without benchmarking +func setupSuite(tb testing.TB) (revashare.Manager, error, func(tb testing.TB)) { + ctx := context.Background() + dbName := "test_db.sqlite" + cfg := map[string]interface{}{ + "engine": "sqlite", + "db_name": dbName, + } + mgr, err := New(ctx, cfg) + if err != nil { + return nil, err, nil + } + + // Return a function to teardown the test + return mgr, nil, func(tb testing.TB) { + log.Println("teardown suite") + os.Remove(dbName) + } +} + +func getRandomFile(owner *userpb.User) *provider.ResourceInfo { + return &provider.ResourceInfo{ + Id: &provider.ResourceId{ + StorageId: "project-b", + OpaqueId: "45468401564", + }, + Type: provider.ResourceType_RESOURCE_TYPE_FILE, + Path: "/eos/project/b/myfile", + Owner: owner.Id, + Mtime: &typesv1beta1.Timestamp{Seconds: uint64(time.Now().Unix())}, + } +} + +// Return context populated with user info +func getUserContext(id string) context.Context { + user := &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: id, + Type: userpb.UserType_USER_TYPE_APPLICATION, + }, + Username: id, + Mail: "myuser@cern.ch", + MailVerified: true, + } + ctx := appctx.ContextSetUser(context.Background(), user) + return ctx +} + +func getUserShareGrant(shareeId, resourcetype string) *collaboration.ShareGrant { + sharee := &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{ + Type: userpb.UserType_USER_TYPE_APPLICATION, + OpaqueId: shareeId, + }, + }, + } + + sharegrant := &collaboration.ShareGrant{ + Grantee: sharee, + Permissions: &collaboration.SharePermissions{ + Permissions: conversions.IntTosharePerm(1, resourcetype), + }, + } + return sharegrant +} + +func getGroupShareGrant(shareeId, resourcetype string) *collaboration.ShareGrant { + sharee := &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: shareeId, + }, + }, + } + return &collaboration.ShareGrant{ + Grantee: sharee, + Permissions: &collaboration.SharePermissions{ + Permissions: conversions.IntTosharePerm(1, resourcetype), + }, + } +} + +// =========================== +// Actual tests +// =========================== + +func TestGetShareById(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + retrievedshare, err := mgr.GetShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + if retrievedshare.Id.OpaqueId != share.Id.OpaqueId { + t.Errorf("Retrieved share does not match created share, expected %s, got %s", share.Id.OpaqueId, retrievedshare.Id.OpaqueId) + } +} + +func TestGetShareByKey(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + key := &collaboration.ShareKey{ + Owner: user.Id, + ResourceId: file.Id, + Grantee: sharegrant.Grantee, + } + + retrievedshare, err := mgr.GetShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Key{ + Key: key, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + if retrievedshare.Id.OpaqueId != share.Id.OpaqueId { + t.Errorf("Retrieved share does not match created share, expected %s, got %s", share.Id.OpaqueId, retrievedshare.Id.OpaqueId) + } +} + +func TestGetReceivedShareById(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + receivedshare, err := mgr.GetReceivedShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + if receivedshare.Share.Id.OpaqueId != share.Id.OpaqueId { + t.Errorf("Retrieved received share does not match created share, expected %s, got %s", share.Id.OpaqueId, receivedshare.Share.Id.OpaqueId) + } +} + +func TestGetReceivedShareByKey(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + receivingUserCtx := getUserContext("1000") + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + key := &collaboration.ShareKey{ + Owner: user.Id, + ResourceId: file.Id, + Grantee: sharegrant.Grantee, + } + + receivedshare, err := mgr.GetReceivedShare(receivingUserCtx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Key{ + Key: key, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + if receivedshare.Share.Id.OpaqueId != share.Id.OpaqueId { + t.Errorf("Retrieved received share does not match created share, expected %s, got %s", share.Id.OpaqueId, receivedshare.Share.Id.OpaqueId) + } +} + +func TestDoNotCreateSameShareTwice(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + sharee := &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{ + Type: userpb.UserType_USER_TYPE_APPLICATION, + OpaqueId: "1000", + }, + }, + } + sharegrant := &collaboration.ShareGrant{ + Grantee: sharee, + Permissions: &collaboration.SharePermissions{ + Permissions: conversions.IntTosharePerm(1, "file"), + }, + } + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + + _, err = mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + _, err = mgr.Share(userctx, file, sharegrant) + if err == nil { + t.Log("Creating same share succeeded, while it should have failed") + t.FailNow() + } +} + +func TestListShares(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + res, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + shares, err := mgr.ListShares(userctx, nil) + if err != nil { + t.Error(err) + t.FailNow() + } + + if len(shares) != 1 { + t.Errorf("Expected 1 share, got %d", len(shares)) + t.FailNow() + } + + if shares[0].Id.OpaqueId != res.Id.OpaqueId { + t.Errorf("Expected share with id %s, got %s", res.Id.OpaqueId, shares[0].Id.OpaqueId) + } +} + +func TestListReceivedShares(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + receivingUserCtx := getUserContext("1000") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + res, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + receivedShares, err := mgr.ListReceivedShares(receivingUserCtx, nil) + if err != nil { + t.Error(err) + t.FailNow() + } + + if len(receivedShares) != 1 { + t.Errorf("Expected 1 received share, got %d", len(receivedShares)) + } + + if receivedShares[0].Share.Id.OpaqueId != res.Id.OpaqueId { + t.Errorf("Expected share with id %s, got %s", res.Id.OpaqueId, receivedShares[0].Share.Id.OpaqueId) + } +} + +func TestListSharesWithFilters(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + res, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + filters := []*collaboration.Filter{ + { + Type: collaboration.Filter_TYPE_RESOURCE_ID, + Term: &collaboration.Filter_ResourceId{ + ResourceId: &provider.ResourceId{ + StorageId: "project-b", + OpaqueId: "45468401564", + }, + }, + }, + } + + shares, err := mgr.ListShares(userctx, filters) + if err != nil { + t.Error(err) + t.FailNow() + } + + if len(shares) != 1 { + t.Errorf("Expected 1 share, got %d", len(shares)) + } + + if shares[0].Id.OpaqueId != res.Id.OpaqueId { + t.Errorf("Expected share with id %s, got %s", res.Id.OpaqueId, shares[0].Id.OpaqueId) + } +} + +func TestDeleteShare(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + err = mgr.Unshare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + _, err = mgr.GetShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }) + if err == nil { + t.Errorf("Expected share to be deleted, but it was not") + } +} + +func TestUpdateShare(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + newPermissions := 15 + + updatedShare, err := mgr.UpdateShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }, &collaboration.SharePermissions{ + Permissions: conversions.IntTosharePerm(newPermissions, "file"), + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + retrievedPerms := conversions.SharePermToInt(updatedShare.Permissions.Permissions) + if retrievedPerms != newPermissions { + t.Errorf("Expected share permissions to be updated, but they were not: got %d instead of %d", retrievedPerms, newPermissions) + } +} + +func TestUpdateReceivedShare(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + share, err := mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + receivedShare, err := mgr.GetReceivedShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + // Update the received share state + receivedShare.State = collaboration.ShareState_SHARE_STATE_REJECTED + + _, err = mgr.UpdateReceivedShare(userctx, receivedShare, &field_mask.FieldMask{ + Paths: []string{"state"}, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + updatedReceivedShare, err := mgr.GetReceivedShare(userctx, &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: share.Id, + }, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + + if updatedReceivedShare.State != collaboration.ShareState_SHARE_STATE_REJECTED { + t.Errorf("Expected received share state to be updated, but it was not") + } +} + +func TestShareWithGroup(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getGroupShareGrant("myegroup", "file") + + _, err = mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + shares, err := mgr.ListShares(userctx, nil) + if err != nil { + t.Error(err) + t.FailNow() + } + + if len(shares) != 1 { + t.Errorf("Expected 1 share, got %d", len(shares)) + } + + if shares[0].Grantee.Type != provider.GranteeType_GRANTEE_TYPE_GROUP { + t.Errorf("Expected share to be with a group, but it was not") + } +} + +func TestListSharesWithGranteeTypeFilter(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getGroupShareGrant("myegroup", "file") + + _, err = mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + filters := []*collaboration.Filter{ + { + Type: collaboration.Filter_TYPE_GRANTEE_TYPE, + Term: &collaboration.Filter_GranteeType{ + GranteeType: provider.GranteeType_GRANTEE_TYPE_GROUP, + }, + }, + } + + shares, err := mgr.ListShares(userctx, filters) + if err != nil { + t.Error(err) + t.FailNow() + } + + if len(shares) != 1 { + t.Errorf("Expected 1 share, got %d", len(shares)) + } +} + +func TestListSharesWithMultipleFilters(t *testing.T) { + mgr, err, teardown := setupSuite(t) + defer teardown(t) + + if err != nil { + t.Error(err) + } + + userctx := getUserContext("123456") + user, _ := appctx.ContextGetUser(userctx) + file := getRandomFile(user) + sharegrant := getUserShareGrant("1000", "file") + + _, err = mgr.Share(userctx, file, sharegrant) + if err != nil { + t.Error(err) + t.FailNow() + } + + filters := []*collaboration.Filter{ + { + Type: collaboration.Filter_TYPE_RESOURCE_ID, + Term: &collaboration.Filter_ResourceId{ResourceId: &provider.ResourceId{ + StorageId: "project-b", + OpaqueId: "45468401564", + }}, + }, + { + Type: collaboration.Filter_TYPE_GRANTEE_TYPE, + Term: &collaboration.Filter_GranteeType{ + GranteeType: provider.GranteeType_GRANTEE_TYPE_USER, + }, + }, + } + + shares, err := mgr.ListShares(userctx, filters) + if err != nil { + t.Error(err) + t.FailNow() + } + + if len(shares) != 1 { + t.Errorf("Expected 1 share, got %d", len(shares)) + } +}