From e4f31a44ceee132e171f43636e8c5d160678842f Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 6 Oct 2020 19:35:31 +0200 Subject: [PATCH] Add logic for received shares method --- changelog/unreleased/eos-version-invariant.md | 2 +- changelog/unreleased/shares-sql-driver.md | 6 + go.mod | 1 + go.sum | 1 + pkg/share/manager/json/json.go | 1 + pkg/share/manager/sql/conversions.go | 35 +-- pkg/share/manager/sql/sql.go | 226 ++++++++++-------- 7 files changed, 156 insertions(+), 116 deletions(-) create mode 100644 changelog/unreleased/shares-sql-driver.md diff --git a/changelog/unreleased/eos-version-invariant.md b/changelog/unreleased/eos-version-invariant.md index 320965cb029..4ad541e16de 100644 --- a/changelog/unreleased/eos-version-invariant.md +++ b/changelog/unreleased/eos-version-invariant.md @@ -5,4 +5,4 @@ versions of a file by returning the inode of the version folder which remains constant. It requires extra metadata operations so a flag is provided to disable it. -https://github.com/cs3org/reva/pull/1174. +https://github.com/cs3org/reva/pull/1174 diff --git a/changelog/unreleased/shares-sql-driver.md b/changelog/unreleased/shares-sql-driver.md new file mode 100644 index 00000000000..a10e27d89aa --- /dev/null +++ b/changelog/unreleased/shares-sql-driver.md @@ -0,0 +1,6 @@ +Enhancement: Add SQL driver for share manager + +This PR adds an SQL driver for the shares manager which expects a schema +equivalent to the one used in production for CERNBox. + +https://github.com/cs3org/reva/pull/1224 diff --git a/go.mod b/go.mod index 3a29009d99b..ccb9d8f4837 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/go-ldap/ldap/v3 v3.2.3 github.com/go-openapi/errors v0.19.6 // indirect github.com/go-openapi/strfmt v0.19.2 // indirect + github.com/go-sql-driver/mysql v1.5.0 github.com/gofrs/uuid v3.3.0+incompatible github.com/golang/protobuf v1.4.2 github.com/gomodule/redigo v1.8.2 diff --git a/go.sum b/go.sum index 00d380131cd..d3164f65275 100644 --- a/go.sum +++ b/go.sum @@ -180,6 +180,7 @@ github.com/go-openapi/strfmt v0.19.2 h1:clPGfBnJohokno0e+d7hs6Yocrzjlgz6EsQSDncC github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 60b4170fbac..4aa6eed07b5 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -452,5 +452,6 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareR return nil, err } + rs.State = f.GetState() return rs, nil } diff --git a/pkg/share/manager/sql/conversions.go b/pkg/share/manager/sql/conversions.go index 85dbdf21551..83982575fe8 100644 --- a/pkg/share/manager/sql/conversions.go +++ b/pkg/share/manager/sql/conversions.go @@ -27,7 +27,6 @@ import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - _ "github.com/go-sql-driver/mysql" ) func granteeTypeToInt(g provider.GranteeType) int { @@ -67,21 +66,6 @@ func resourceTypeToItem(r provider.ResourceType) string { } } -func itemToresourceType(r string) provider.ResourceType { - switch r { - case "file": - return provider.ResourceType_RESOURCE_TYPE_FILE - case "folder": - return provider.ResourceType_RESOURCE_TYPE_CONTAINER - case "reference": - return provider.ResourceType_RESOURCE_TYPE_REFERENCE - case "symlink": - return provider.ResourceType_RESOURCE_TYPE_SYMLINK - default: - return provider.ResourceType_RESOURCE_TYPE_INVALID - } -} - func sharePermToInt(p *provider.ResourcePermissions) int { var perm int if p.CreateContainer { @@ -130,6 +114,17 @@ func intTosharePerm(p int) *provider.ResourcePermissions { } } +func intToShareState(g int) collaboration.ShareState { + switch g { + case 0: + return collaboration.ShareState_SHARE_STATE_PENDING + case 1: + return collaboration.ShareState_SHARE_STATE_ACCEPTED + default: + return collaboration.ShareState_SHARE_STATE_INVALID + } +} + func formatUserID(u *userpb.UserId) string { if u.Idp != "" { return fmt.Sprintf("%s:%s", u.OpaqueId, u.Idp) @@ -162,3 +157,11 @@ func convertToCS3Share(s dbShare) *collaboration.Share { Mtime: ts, } } + +func convertToCS3ReceivedShare(s dbShare) *collaboration.ReceivedShare { + share := convertToCS3Share(s) + return &collaboration.ReceivedShare{ + Share: share, + State: intToShareState(s.State), + } +} diff --git a/pkg/share/manager/sql/sql.go b/pkg/share/manager/sql/sql.go index 38bbbb1d3c2..40b84e76918 100644 --- a/pkg/share/manager/sql/sql.go +++ b/pkg/share/manager/sql/sql.go @@ -23,8 +23,8 @@ import ( "database/sql" "fmt" "path" - "reflect" "strconv" + "strings" "time" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -32,12 +32,13 @@ import ( typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/share" - _ "github.com/go-sql-driver/mysql" + "github.com/cs3org/reva/pkg/share/manager/registry" + "github.com/cs3org/reva/pkg/user" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" - "github.com/cs3org/reva/pkg/share/manager/registry" - "github.com/cs3org/reva/pkg/user" + // Provides mysql drivers + _ "github.com/go-sql-driver/mysql" ) func init() { @@ -48,7 +49,7 @@ type config struct { dbUsername string `mapstructure:"db_username"` dbPassword string `mapstructure:"db_password"` dbHost string `mapstructure:"db_host"` - dbPort string `mapstructure:"db_port"` + dbPort int `mapstructure:"db_port"` dbName string `mapstructure:"db_name"` } @@ -155,14 +156,14 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora if err != nil { return nil, err } - lastId, err := result.LastInsertId() + lastID, err := result.LastInsertId() if err != nil { return nil, err } return &collaboration.Share{ Id: &collaboration.ShareId{ - OpaqueId: strconv.FormatInt(lastId, 10), + OpaqueId: strconv.FormatInt(lastID, 10), }, ResourceId: md.Id, Permissions: g.Permissions, @@ -203,7 +204,9 @@ func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey) (*colla return convertToCS3Share(s), nil } -func (m *mgr) get(ctx context.Context, ref *collaboration.ShareReference) (s *collaboration.Share, err error) { +func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { + var s *collaboration.Share + var err error switch { case ref.GetId() != nil: s, err = m.getByID(ctx, ref.GetId()) @@ -228,15 +231,6 @@ func (m *mgr) get(ctx context.Context, ref *collaboration.ShareReference) (s *co return nil, errtypes.NotFound(ref.String()) } -func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { - share, err := m.get(ctx, ref) - if err != nil { - return nil, err - } - - return share, nil -} - func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { user := user.ContextMustGetUser(ctx) @@ -249,7 +243,7 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er case ref.GetKey() != nil: key := ref.GetKey() if key.Owner != user.Id { - return errtypes.PermissionDenied(ref.String()) + return errtypes.NotFound(ref.String()) } query = "delete from oc_share where (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=?" params = append(params, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id)) @@ -276,21 +270,6 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er return nil } -// TODO(labkode): this is fragile, the check should be done on primitive types. -func equal(ref *collaboration.ShareReference, s *collaboration.Share) bool { - if ref.GetId() != nil && s.Id != nil { - if ref.GetId().OpaqueId == s.Id.OpaqueId { - return true - } - } else if ref.GetKey() != nil { - if (reflect.DeepEqual(*ref.GetKey().Owner, *s.Owner) || reflect.DeepEqual(*ref.GetKey().Owner, *s.Creator)) && - reflect.DeepEqual(*ref.GetKey().ResourceId, *s.ResourceId) && reflect.DeepEqual(*ref.GetKey().Grantee, *s.Grantee) { - return true - } - } - return false -} - func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { user := user.ContextMustGetUser(ctx) @@ -303,7 +282,7 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference case ref.GetKey() != nil: key := ref.GetKey() if key.Owner != user.Id { - return nil, errtypes.PermissionDenied(ref.String()) + return nil, errtypes.NotFound(ref.String()) } query = "update oc_share set permissions=?,stime=? where (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=?" params = append(params, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id)) @@ -362,100 +341,149 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare // we list the shares that are targeted to the user in context or to the user groups. func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { - var rss []*collaboration.ReceivedShare - m.Lock() - defer m.Unlock() user := user.ContextMustGetUser(ctx) - for _, s := range m.model.Shares { - if (user.Id.Idp == s.Owner.Idp && user.Id.OpaqueId == s.Owner.OpaqueId) || - (user.Id.Idp == s.Creator.Idp && user.Id.OpaqueId == s.Creator.OpaqueId) { - // omit shares created by me + + params := []interface{}{formatUserID(user.Id), formatUserID(user.Id)} + for _, v := range user.Groups { + params = append(params, v) + } + + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" + if len(user.Groups) > 0 { + query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" + } else { + query += "AND (share_with=?)" + } + + rows, err := m.db.Query(query, params...) + if err != nil { + return nil, err + } + defer rows.Close() + + var s dbShare + shares := []*collaboration.ReceivedShare{} + for rows.Next() { + if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { continue } - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { - if user.Id.Idp == s.Grantee.Id.Idp && user.Id.OpaqueId == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - // check if all user groups match this share; TODO(labkode): filter shares created by us. - for _, g := range user.Groups { - if g == s.Grantee.Id.OpaqueId { - rs := m.convert(ctx, s) - rss = append(rss, rs) - } - } - } + shares = append(shares, convertToCS3ReceivedShare(s)) } - return rss, nil + if err = rows.Err(); err != nil { + return nil, err + } + + return shares, nil } -// convert must be called in a lock-controlled block. -func (m *mgr) convert(ctx context.Context, s *collaboration.Share) *collaboration.ReceivedShare { - rs := &collaboration.ReceivedShare{ - Share: s, - State: collaboration.ShareState_SHARE_STATE_PENDING, - } +func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.ReceivedShare, error) { user := user.ContextMustGetUser(ctx) - if v, ok := m.model.State[user.Id.String()]; ok { - if state, ok := v[s.Id.String()]; ok { - rs.State = state + + intID, err := strconv.ParseInt(id.OpaqueId, 10, 64) + if err != nil { + return nil, err + } + + params := []interface{}{id.OpaqueId, formatUserID(user.Id), formatUserID(user.Id)} + for _, v := range user.Groups { + params = append(params, v) + } + + s := dbShare{ID: int(intID)} + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND id=? AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" + if len(user.Groups) > 0 { + query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" + } else { + query += "AND (share_with=?)" + } + if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.STime, &s.Permissions, &s.ShareType); err != nil { + if err == sql.ErrNoRows { + return nil, errtypes.NotFound(id.OpaqueId) } + return nil, err } - return rs + return convertToCS3ReceivedShare(s), nil } -func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { - return m.getReceived(ctx, ref) +func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) (*collaboration.ReceivedShare, error) { + s := dbShare{} + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(fileid_prefix, '') as fileid_prefix, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND (uid_owner=? or uid_initiator=?) AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=?" + if err := m.db.QueryRow(query, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, granteeTypeToInt(key.Grantee.Type), formatUserID(key.Grantee.Id)).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { + if err == sql.ErrNoRows { + return nil, errtypes.NotFound(key.String()) + } + return nil, err + } + + // check if it hasn't been rejected already + if _, err := m.getReceivedByID(ctx, &collaboration.ShareId{OpaqueId: strconv.Itoa(s.ID)}); err != nil { + return nil, errtypes.NotFound(key.String()) + } + + return convertToCS3ReceivedShare(s), nil } -func (m *mgr) getReceived(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { - m.Lock() - defer m.Unlock() +func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { + var s *collaboration.ReceivedShare + var err error + switch { + case ref.GetId() != nil: + s, err = m.getReceivedByID(ctx, ref.GetId()) + case ref.GetKey() != nil: + s, err = m.getReceivedByKey(ctx, ref.GetKey()) + default: + err = errtypes.NotFound(ref.String()) + } + + if err != nil { + return nil, err + } + user := user.ContextMustGetUser(ctx) - for _, s := range m.model.Shares { - if equal(ref, s) { - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && - s.Grantee.Id.Idp == user.Id.Idp && s.Grantee.Id.OpaqueId == user.Id.OpaqueId { - rs := m.convert(ctx, s) - return rs, nil - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - for _, g := range user.Groups { - if s.Grantee.Id.OpaqueId == g { - rs := m.convert(ctx, s) - return rs, nil - } - } + if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && s.Share.Grantee.Id.OpaqueId == user.Id.OpaqueId && s.Share.Grantee.Id.Idp == user.Id.Idp { + return s, nil + } + + if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { + for _, v := range user.Groups { + if s.Share.Grantee.Id.OpaqueId == v { + return s, nil } } } + + // we return not found to not disclose information return nil, errtypes.NotFound(ref.String()) + } func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareReference, f *collaboration.UpdateReceivedShareRequest_UpdateField) (*collaboration.ReceivedShare, error) { - rs, err := m.getReceived(ctx, ref) + user := user.ContextMustGetUser(ctx) + + rs, err := m.GetReceivedShare(ctx, ref) if err != nil { return nil, err } - user := user.ContextMustGetUser(ctx) - m.Lock() - defer m.Unlock() - - if v, ok := m.model.State[user.Id.String()]; ok { - v[rs.Share.Id.String()] = f.GetState() - m.model.State[user.Id.String()] = v - } else { - a := map[string]collaboration.ShareState{ - rs.Share.Id.String(): f.GetState(), - } - m.model.State[user.Id.String()] = a + var query string + params := []interface{}{rs.Share.Id.OpaqueId} + switch f.GetState() { + case collaboration.ShareState_SHARE_STATE_REJECTED: + query = "insert into oc_share_acl(id, rejected_by) values(?, ?)" + params = append(params, formatUserID(user.Id)) + case collaboration.ShareState_SHARE_STATE_ACCEPTED: + query = "update oc_share set accepted=1 where id=?" } - if err := m.model.Save(); err != nil { - err = errors.Wrap(err, "error saving model") + stmt, err := m.db.Prepare(query) + if err != nil { + return nil, err + } + _, err = stmt.Exec(params...) + if err != nil { return nil, err } + rs.State = f.GetState() return rs, nil }