From 68dca24f831807106edcb86ac87c86a727a4baf5 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 23 Feb 2021 17:31:35 +0100 Subject: [PATCH 1/6] SQL driver for the publicshare service --- .../unreleased/publicshare-sql-driver.md | 3 + pkg/cbox/loader/loader.go | 1 + pkg/cbox/publicshare/sql/sql.go | 475 ++++++++++++++++++ pkg/cbox/share/sql/sql.go | 147 +++--- pkg/cbox/{share/sql => utils}/conversions.go | 99 +++- pkg/publicshare/manager/json/json.go | 60 +-- 6 files changed, 644 insertions(+), 141 deletions(-) create mode 100644 changelog/unreleased/publicshare-sql-driver.md create mode 100644 pkg/cbox/publicshare/sql/sql.go rename pkg/cbox/{share/sql => utils}/conversions.go (62%) diff --git a/changelog/unreleased/publicshare-sql-driver.md b/changelog/unreleased/publicshare-sql-driver.md new file mode 100644 index 0000000000..dcfeee32e1 --- /dev/null +++ b/changelog/unreleased/publicshare-sql-driver.md @@ -0,0 +1,3 @@ +Enhancement: SQL driver for the publicshare service + +https://github.com/cs3org/reva/pull/1495 diff --git a/pkg/cbox/loader/loader.go b/pkg/cbox/loader/loader.go index 8cfab6636e..a46b40c90a 100644 --- a/pkg/cbox/loader/loader.go +++ b/pkg/cbox/loader/loader.go @@ -21,6 +21,7 @@ package loader import ( // Load cbox specific drivers. _ "github.com/cs3org/reva/pkg/cbox/group/rest" + _ "github.com/cs3org/reva/pkg/cbox/publicshare/sql" _ "github.com/cs3org/reva/pkg/cbox/share/sql" _ "github.com/cs3org/reva/pkg/cbox/user/rest" ) diff --git a/pkg/cbox/publicshare/sql/sql.go b/pkg/cbox/publicshare/sql/sql.go new file mode 100644 index 0000000000..ed0115d7c6 --- /dev/null +++ b/pkg/cbox/publicshare/sql/sql.go @@ -0,0 +1,475 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package filesystem + +import ( + "context" + "database/sql" + "fmt" + "math/rand" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "golang.org/x/crypto/bcrypt" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + conversions "github.com/cs3org/reva/pkg/cbox/utils" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/publicshare" + "github.com/cs3org/reva/pkg/publicshare/manager/registry" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +const publicShareType = 3 + +func init() { + registry.Register("sql", New) +} + +type config struct { + SharePasswordHashCost int `mapstructure:"password_hash_cost"` + JanitorRunInterval int `mapstructure:"janitor_run_interval"` + DbUsername string `mapstructure:"db_username"` + DbPassword string `mapstructure:"db_password"` + DbHost string `mapstructure:"db_host"` + DbPort int `mapstructure:"db_port"` + DbName string `mapstructure:"db_name"` +} + +type manager struct { + c *config + db *sql.DB +} + +type dbShare struct { + ID string + UIDOwner string + UIDInitiator string + Prefix string + ItemSource string + ShareWith string + Permissions int + ShareType int + STime int + FileTarget string + State int +} + +func (c *config) init() { + if c.SharePasswordHashCost == 0 { + c.SharePasswordHashCost = 11 + } + if c.JanitorRunInterval == 0 { + c.JanitorRunInterval = 3600 + } +} + +func (m *manager) startJanitorRun() { + ticker := time.NewTicker(time.Duration(m.c.JanitorRunInterval)) + work := make(chan os.Signal, 1) + signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT) + + for { + select { + case <-work: + return + case <-ticker.C: + _ = m.cleanupExpiredShares() + } + } +} + +func New(m map[string]interface{}) (publicshare.Manager, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, err + } + c.init() + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)) + if err != nil { + return nil, err + } + + mgr := manager{ + c: c, + db: db, + } + go mgr.startJanitorRun() + + return &mgr, nil +} + +func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) { + + tkn := randString(15) + now := time.Now().Unix() + + displayName, ok := rInfo.ArbitraryMetadata.Metadata["name"] + if !ok { + displayName = tkn + } + createdAt := &typespb.Timestamp{ + Seconds: uint64(now), + } + + creator := conversions.FormatUserID(u.Id) + owner := conversions.FormatUserID(rInfo.Owner) + permissions := conversions.SharePermToInt(g.Permissions.Permissions) + itemType := conversions.ResourceTypeToItem(rInfo.Type) + prefix := rInfo.Id.StorageId + itemSource := rInfo.Id.OpaqueId + fileSource, err := strconv.ParseUint(itemSource, 10, 64) + if err != nil { + // it can be the case that the item source may be a character string + // we leave fileSource blank in that case + fileSource = 0 + } + + query := "insert into oc_share set share_type=?,uid_owner=?,uid_initiator=?,item_type=?,fileid_prefix=?,item_source=?,file_source=?,permissions=?,stime=?,token=?,share_name=?" + params := []interface{}{publicShareType, owner, creator, itemType, prefix, itemSource, fileSource, permissions, now, tkn, displayName} + + var passwordProtected bool + password := g.Password + if password != "" { + password, err = hashPassword(password, m.c.SharePasswordHashCost) + if err != nil { + return nil, errors.Wrap(err, "could not hash share password") + } + passwordProtected = true + + query += ",share_with=?" + params = append(params, password) + } + + if g.Expiration.Seconds != 0 { + t := time.Unix(int64(g.Expiration.Seconds), 0) + query += ",expiration=?" + params = append(params, t) + } + + stmt, err := m.db.Prepare(query) + if err != nil { + return nil, err + } + result, err := stmt.Exec(params...) + if err != nil { + return nil, err + } + lastID, err := result.LastInsertId() + if err != nil { + return nil, err + } + + return &link.PublicShare{ + Id: &link.PublicShareId{ + OpaqueId: strconv.FormatInt(lastID, 10), + }, + Owner: rInfo.GetOwner(), + Creator: u.Id, + ResourceId: rInfo.Id, + Token: tkn, + Permissions: g.Permissions, + Ctime: createdAt, + Mtime: createdAt, + PasswordProtected: passwordProtected, + Expiration: g.Expiration, + DisplayName: displayName, + }, nil +} + +func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest, g *link.Grant) (*link.PublicShare, error) { + query := "update oc_share set " + paramsMap := map[string]interface{}{} + params := []interface{}{} + + now := time.Now().Unix() + uid := conversions.FormatUserID(u.Id) + + switch req.GetUpdate().GetType() { + case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME: + paramsMap["share_name"] = req.Update.GetDisplayName() + case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS: + paramsMap["permissions"] = conversions.SharePermToInt(req.Update.GetGrant().GetPermissions().Permissions) + case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION: + paramsMap["expiration"] = time.Unix(int64(req.Update.GetGrant().Expiration.Seconds), 0) + case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD: + if req.Update.GetGrant().Password == "" { + paramsMap["share_with"] = "" + } else { + h, err := hashPassword(req.Update.GetGrant().Password, m.c.SharePasswordHashCost) + if err != nil { + return nil, errors.Wrap(err, "could not hash share password") + } + paramsMap["share_with"] = h + } + default: + return nil, fmt.Errorf("invalid update type: %v", req.GetUpdate().GetType()) + } + + for k, v := range paramsMap { + query += k + "=?" + params = append(params, v) + } + + switch { + case req.Ref.GetId() != nil: + query += ",stime=? where id=? AND (uid_owner=? or uid_initiator=?)" + params = append(params, now, req.Ref.GetId().OpaqueId, uid, uid) + case req.Ref.GetToken() != "": + query += ",stime=? where token=? AND (uid_owner=? or uid_initiator=?)" + params = append(params, now, req.Ref.GetToken(), uid, uid) + default: + return nil, errtypes.NotFound(req.Ref.String()) + } + + stmt, err := m.db.Prepare(query) + if err != nil { + return nil, err + } + if _, err = stmt.Exec(params...); err != nil { + return nil, err + } + + return m.GetPublicShare(ctx, u, req.Ref) +} + +func (m *manager) getByToken(ctx context.Context, token string, u *user.User) (*link.PublicShare, error) { + s := conversions.DBShare{Token: token} + 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, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE share_type=? AND token=?" + if err := m.db.QueryRow(query, publicShareType, token).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions); err != nil { + if err == sql.ErrNoRows { + return nil, errtypes.NotFound(token) + } + return nil, err + } + return conversions.ConvertToCS3PublicShare(s), nil +} + +func (m *manager) getByID(ctx context.Context, id *link.PublicShareId, u *user.User) (*link.PublicShare, error) { + uid := conversions.FormatUserID(u.Id) + s := conversions.DBShare{ID: id.OpaqueId} + 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, coalesce(token,'') as token, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, stime, permissions FROM oc_share WHERE share_type=? AND id=? AND (uid_owner=? OR uid_initiator=?)" + if err := m.db.QueryRow(query, publicShareType, id.OpaqueId, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.Token, &s.Expiration, &s.ShareName, &s.STime, &s.Permissions); err != nil { + if err == sql.ErrNoRows { + return nil, errtypes.NotFound(id.OpaqueId) + } + return nil, err + } + return conversions.ConvertToCS3PublicShare(s), nil +} + +func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) (*link.PublicShare, error) { + var s *link.PublicShare + var err error + switch { + case ref.GetId() != nil: + s, err = m.getByID(ctx, ref.GetId(), u) + case ref.GetToken() != "": + s, err = m.getByToken(ctx, ref.GetToken(), u) + default: + err = errtypes.NotFound(ref.String()) + } + if err != nil { + return nil, err + } + + if expired(s) { + req := link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: s.Id.OpaqueId, + }, + }, + } + if err := m.RevokePublicShare(ctx, u, &req); err != nil { + return nil, err + } + return nil, errtypes.NotFound(ref.String()) + } + + return s, nil +} + +func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, md *provider.ResourceInfo) ([]*link.PublicShare, error) { + uid := conversions.FormatUserID(u.Id) + 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, coalesce(token,'') as token, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE (uid_owner=? or uid_initiator=?) AND (share_type=?)" + var filterQuery string + params := []interface{}{uid, uid, publicShareType} + + for i, f := range filters { + switch f.Type { + case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID: + filterQuery += "(fileid_prefix=? AND item_source=?)" + if i != len(filters)-1 { + filterQuery += " AND " + } + params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) + case link.ListPublicSharesRequest_Filter_TYPE_OWNER: + filterQuery += "(uid_owner=?)" + if i != len(filters)-1 { + filterQuery += " AND " + } + params = append(params, conversions.FormatUserID(f.GetOwner())) + case link.ListPublicSharesRequest_Filter_TYPE_CREATOR: + filterQuery += "(uid_initiator=?)" + if i != len(filters)-1 { + filterQuery += " AND " + } + params = append(params, conversions.FormatUserID(f.GetCreator())) + } + } + if filterQuery != "" { + query = fmt.Sprintf("%s AND (%s)", query, filterQuery) + } + + rows, err := m.db.Query(query, params...) + if err != nil { + return nil, err + } + defer rows.Close() + + var s conversions.DBShare + shares := []*link.PublicShare{} + for rows.Next() { + if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.Token, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions); err != nil { + continue + } + cs3Share := conversions.ConvertToCS3PublicShare(s) + if expired(cs3Share) { + _ = m.RevokePublicShare(ctx, u, &link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: cs3Share.Id.OpaqueId, + }, + }, + }) + } else { + shares = append(shares, cs3Share) + } + } + if err = rows.Err(); err != nil { + return nil, err + } + + return shares, nil +} + +func (m *manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error { + uid := conversions.FormatUserID(u.Id) + query := "delete from oc_share where" + params := []interface{}{} + + switch { + case ref.GetId() != nil && ref.GetId().OpaqueId != "": + query += "id=? AND (uid_owner=? or uid_initiator=?)" + params = append(params, ref.GetId().OpaqueId, uid, uid) + case ref.GetToken() != "": + query += "token=? AND (uid_owner=? or uid_initiator=?)" + params = append(params, ref.GetToken(), uid, uid) + default: + return errtypes.NotFound(ref.String()) + } + + stmt, err := m.db.Prepare(query) + if err != nil { + return err + } + res, err := stmt.Exec(params...) + if err != nil { + return err + } + + rowCnt, err := res.RowsAffected() + if err != nil { + return err + } + if rowCnt == 0 { + return errtypes.NotFound(ref.String()) + } + return nil +} + +func (m *manager) GetPublicShareByToken(ctx context.Context, token, password string) (*link.PublicShare, error) { + s := conversions.DBShare{Token: token} + 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, coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name, id, stime, permissions FROM oc_share WHERE share_type=? AND token=?" + if err := m.db.QueryRow(query, publicShareType, token).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions); err != nil { + if err == sql.ErrNoRows { + return nil, errtypes.NotFound(token) + } + return nil, err + } + if s.ShareWith != "" { + if check := checkPasswordHash(password, s.ShareWith); check { + return conversions.ConvertToCS3PublicShare(s), nil + } + } + + return nil, errtypes.NotFound(fmt.Sprintf("share with token: " + token)) +} + +func (m *manager) cleanupExpiredShares() error { + query := "delete from oc_share where expiration IS NOT NULL AND expiration < ?" + params := []interface{}{time.Now().Format("2006-01-02 03:04:05")} + + stmt, err := m.db.Prepare(query) + if err != nil { + return err + } + if _, err = stmt.Exec(params...); err != nil { + return err + } + return nil +} + +func expired(s *link.PublicShare) bool { + if s.Expiration != nil { + if t := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos())); t.Before(time.Now()) { + return true + } + } + return false +} + +func hashPassword(password string, cost int) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost) + return "1|" + string(bytes), err +} + +func checkPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(strings.TrimPrefix(hash, "1|")), []byte(password)) + return err == nil +} + +func randString(n int) string { + var l = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = l[rand.Intn(len(l))] + } + return string(b) +} diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 62b2fbf2c1..842871d369 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -30,6 +30,7 @@ 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" + conversions "github.com/cs3org/reva/pkg/cbox/utils" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/share/manager/registry" @@ -59,20 +60,6 @@ type mgr struct { db *sql.DB } -type dbShare struct { - ID string - UIDOwner string - UIDInitiator string - Prefix string - ItemSource string - ShareWith string - Permissions int - ShareType int - STime int - FileTarget string - State int -} - // New returns a new mgr. func New(m map[string]interface{}) (share.Manager, error) { c, err := parseConfig(m) @@ -128,10 +115,10 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora Seconds: uint64(now), } - shareType, shareWith := formatGrantee(g.Grantee) - itemType := resourceTypeToItem(md.Type) + shareType, shareWith := conversions.FormatGrantee(g.Grantee) + itemType := conversions.ResourceTypeToItem(md.Type) targetPath := path.Join("/", path.Base(md.Path)) - permissions := sharePermToInt(g.Permissions.Permissions) + permissions := conversions.SharePermToInt(g.Permissions.Permissions) prefix := md.Id.StorageId itemSource := md.Id.OpaqueId fileSource, err := strconv.ParseUint(itemSource, 10, 64) @@ -142,7 +129,7 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora } stmtString := "insert into oc_share set share_type=?,uid_owner=?,uid_initiator=?,item_type=?,fileid_prefix=?,item_source=?,file_source=?,permissions=?,stime=?,share_with=?,file_target=?" - stmtValues := []interface{}{shareType, formatUserID(md.Owner), formatUserID(user.Id), itemType, prefix, itemSource, fileSource, permissions, now, shareWith, targetPath} + stmtValues := []interface{}{shareType, conversions.FormatUserID(md.Owner), conversions.FormatUserID(user.Id), itemType, prefix, itemSource, fileSource, permissions, now, shareWith, targetPath} stmt, err := m.db.Prepare(stmtString) if err != nil { @@ -172,28 +159,31 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora } func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.Share, error) { - s := dbShare{ID: id.OpaqueId} - 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=?" + s := conversions.DBShare{ID: id.OpaqueId} + 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 (uid_owner=? or uid_initiator=?)" if err := m.db.QueryRow(query, id.OpaqueId).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 convertToCS3Share(s), nil + return conversions.ConvertToCS3Share(s), nil } func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey) (*collaboration.Share, error) { - s := dbShare{} - shareType, shareWith := formatGrantee(key.Grantee) - 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, shareType, shareWith).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { + owner := conversions.FormatUserID(key.Owner) + uid := conversions.FormatUserID(user.ContextMustGetUser(ctx).Id) + + s := conversions.DBShare{} + shareType, shareWith := conversions.FormatGrantee(key.Grantee) + 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=? AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" + if err := m.db.QueryRow(query, owner, key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, uid, uid).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 } - return convertToCS3Share(s), nil + return conversions.ConvertToCS3Share(s), nil } func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { @@ -212,33 +202,24 @@ func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) ( return nil, err } - // check if we are the owner - user := user.ContextMustGetUser(ctx) - if utils.UserEqual(user.Id, s.Owner) || utils.UserEqual(user.Id, s.Creator) { - return s, nil - } - - // we return not found to not disclose information - return nil, errtypes.NotFound(ref.String()) + return s, nil } func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { - user := user.ContextMustGetUser(ctx) + uid := conversions.FormatUserID(user.ContextMustGetUser(ctx).Id) var query string params := []interface{}{} switch { case ref.GetId() != nil: query = "delete from oc_share where id=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, ref.GetId().OpaqueId, formatUserID(user.Id), formatUserID(user.Id)) + params = append(params, ref.GetId().OpaqueId, uid, uid) case ref.GetKey() != nil: key := ref.GetKey() - if key.Owner != user.Id { - return errtypes.NotFound(ref.String()) - } - shareType, shareWith := formatGrantee(key.Grantee) - 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, shareType, shareWith) + shareType, shareWith := conversions.FormatGrantee(key.Grantee) + owner := conversions.FormatUserID(key.Owner) + query = "delete from oc_share where uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" + params = append(params, owner, key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, uid, uid) default: return errtypes.NotFound(ref.String()) } @@ -263,23 +244,21 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er } func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { - user := user.ContextMustGetUser(ctx) - permissions := sharePermToInt(p.Permissions) + permissions := conversions.SharePermToInt(p.Permissions) + uid := conversions.FormatUserID(user.ContextMustGetUser(ctx).Id) var query string params := []interface{}{} switch { case ref.GetId() != nil: query = "update oc_share set permissions=?,stime=? where id=? AND (uid_owner=? or uid_initiator=?)" - params = append(params, permissions, time.Now().Unix(), ref.GetId().OpaqueId, formatUserID(user.Id), formatUserID(user.Id)) + params = append(params, permissions, time.Now().Unix(), ref.GetId().OpaqueId, uid, uid) case ref.GetKey() != nil: key := ref.GetKey() - if key.Owner != user.Id { - return nil, errtypes.NotFound(ref.String()) - } - shareType, shareWith := formatGrantee(key.Grantee) - 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, permissions, time.Now().Unix(), formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith) + shareType, shareWith := conversions.FormatGrantee(key.Grantee) + owner := conversions.FormatUserID(key.Owner) + 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=? AND (uid_owner=? or uid_initiator=?)" + params = append(params, permissions, time.Now().Unix(), owner, owner, key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, uid, uid) default: return nil, errtypes.NotFound(ref.String()) } @@ -296,14 +275,15 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference } func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListSharesRequest_Filter) ([]*collaboration.Share, error) { + uid := conversions.FormatUserID(user.ContextMustGetUser(ctx).Id) 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 (share_type=? OR share_type=?)" var filterQuery string - params := []interface{}{formatUserID(user.ContextMustGetUser(ctx).Id), formatUserID(user.ContextMustGetUser(ctx).Id), 0, 1} + params := []interface{}{uid, uid, 0, 1} for i, f := range filters { if f.Type == collaboration.ListSharesRequest_Filter_TYPE_RESOURCE_ID { filterQuery += "(fileid_prefix=? AND item_source=?)" if i != len(filters)-1 { - filterQuery += " OR " + filterQuery += " AND " } params = append(params, f.GetResourceId().StorageId, f.GetResourceId().OpaqueId) } @@ -318,13 +298,13 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.ListShare } defer rows.Close() - var s dbShare + var s conversions.DBShare shares := []*collaboration.Share{} 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 } - shares = append(shares, convertToCS3Share(s)) + shares = append(shares, conversions.ConvertToCS3Share(s)) } if err = rows.Err(); err != nil { return nil, err @@ -336,8 +316,9 @@ 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) { user := user.ContextMustGetUser(ctx) + uid := conversions.FormatUserID(user.Id) - params := []interface{}{formatUserID(user.Id), formatUserID(user.Id)} + params := []interface{}{uid, uid} for _, v := range user.Groups { params = append(params, v) } @@ -355,13 +336,13 @@ func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.Received } defer rows.Close() - var s dbShare + var s conversions.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, &s.State); err != nil { continue } - shares = append(shares, convertToCS3ReceivedShare(s)) + shares = append(shares, conversions.ConvertToCS3ReceivedShare(s)) } if err = rows.Err(); err != nil { return nil, err @@ -372,13 +353,14 @@ func (m *mgr) ListReceivedShares(ctx context.Context) ([]*collaboration.Received func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.ReceivedShare, error) { user := user.ContextMustGetUser(ctx) + uid := conversions.FormatUserID(user.Id) - params := []interface{}{id.OpaqueId, formatUserID(user.Id), formatUserID(user.Id)} + params := []interface{}{id.OpaqueId, uid, uid} for _, v := range user.Groups { params = append(params, v) } - s := dbShare{ID: id.OpaqueId} + s := conversions.DBShare{ID: id.OpaqueId} 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, accepted 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) + "))" @@ -391,21 +373,34 @@ func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (* } return nil, err } - return convertToCS3ReceivedShare(s), nil + return conversions.ConvertToCS3ReceivedShare(s), nil } 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, accepted 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=? AND id not in (SELECT distinct(id) FROM oc_share_acl WHERE rejected_by=?)" - shareType, shareWith := formatGrantee(key.Grantee) - if err := m.db.QueryRow(query, formatUserID(key.Owner), formatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { + user := user.ContextMustGetUser(ctx) + uid := conversions.FormatUserID(user.Id) + + shareType, shareWith := conversions.FormatGrantee(key.Grantee) + params := []interface{}{conversions.FormatUserID(key.Owner), key.ResourceId.StorageId, key.ResourceId.OpaqueId, shareType, shareWith, shareWith, uid} + for _, v := range user.Groups { + params = append(params, v) + } + + s := conversions.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, accepted FROM oc_share WHERE (orphan = 0 or orphan IS NULL) AND uid_owner=? AND fileid_prefix=? AND item_source=? AND share_type=? AND share_with=? 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.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(key.String()) } return nil, err } - - return convertToCS3ReceivedShare(s), nil + return conversions.ConvertToCS3ReceivedShare(s), nil } func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { @@ -424,21 +419,7 @@ func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareRefe return nil, err } - user := user.ContextMustGetUser(ctx) - if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Share.Grantee.GetUserId()) { - return s, nil - } - - if s.Share.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - for _, v := range user.Groups { - if s.Share.Grantee.GetGroupId().OpaqueId == v { - return s, nil - } - } - } - - // we return not found to not disclose information - return nil, errtypes.NotFound(ref.String()) + return s, nil } @@ -455,7 +436,7 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, ref *collaboration.ShareR 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)) + params = append(params, conversions.FormatUserID(user.Id)) case collaboration.ShareState_SHARE_STATE_ACCEPTED: query = "update oc_share set accepted=1 where id=?" } diff --git a/pkg/cbox/share/sql/conversions.go b/pkg/cbox/utils/conversions.go similarity index 62% rename from pkg/cbox/share/sql/conversions.go rename to pkg/cbox/utils/conversions.go index 5e24fdea75..f4efcbd1ac 100644 --- a/pkg/cbox/share/sql/conversions.go +++ b/pkg/cbox/utils/conversions.go @@ -16,51 +16,70 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package sql +package utils import ( "fmt" "strings" + "time" grouppb "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" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) -func formatGrantee(g *provider.Grantee) (int, string) { +type DBShare struct { + ID string + UIDOwner string + UIDInitiator string + Prefix string + ItemSource string + ShareWith string + Token string + Expiration string + Permissions int + ShareType int + ShareName string + STime int + FileTarget string + State int +} + +func FormatGrantee(g *provider.Grantee) (int, string) { var granteeType int var formattedID string switch g.Type { case provider.GranteeType_GRANTEE_TYPE_USER: granteeType = 0 - formattedID = formatUserID(g.GetUserId()) + formattedID = FormatUserID(g.GetUserId()) case provider.GranteeType_GRANTEE_TYPE_GROUP: granteeType = 1 - formattedID = formatGroupID(g.GetGroupId()) + formattedID = FormatGroupID(g.GetGroupId()) default: granteeType = -1 } return granteeType, formattedID } -func extractGrantee(t int, g string) *provider.Grantee { +func ExtractGrantee(t int, g string) *provider.Grantee { var grantee *provider.Grantee switch t { case 0: grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER - grantee.Id = &provider.Grantee_UserId{UserId: extractUserID(g)} + grantee.Id = &provider.Grantee_UserId{UserId: ExtractUserID(g)} case 1: grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP - grantee.Id = &provider.Grantee_GroupId{GroupId: extractGroupID(g)} + grantee.Id = &provider.Grantee_GroupId{GroupId: ExtractGroupID(g)} default: grantee.Type = provider.GranteeType_GRANTEE_TYPE_INVALID } return grantee } -func resourceTypeToItem(r provider.ResourceType) string { +func ResourceTypeToItem(r provider.ResourceType) string { switch r { case provider.ResourceType_RESOURCE_TYPE_FILE: return "file" @@ -75,7 +94,7 @@ func resourceTypeToItem(r provider.ResourceType) string { } } -func sharePermToInt(p *provider.ResourcePermissions) int { +func SharePermToInt(p *provider.ResourcePermissions) int { var perm int if p.CreateContainer { perm = 15 @@ -85,7 +104,7 @@ func sharePermToInt(p *provider.ResourcePermissions) int { return perm } -func intTosharePerm(p int) *provider.ResourcePermissions { +func IntTosharePerm(p int) *provider.ResourcePermissions { switch p { case 1: return &provider.ResourcePermissions{ @@ -122,7 +141,7 @@ func intTosharePerm(p int) *provider.ResourcePermissions { } } -func intToShareState(g int) collaboration.ShareState { +func IntToShareState(g int) collaboration.ShareState { switch g { case 0: return collaboration.ShareState_SHARE_STATE_PENDING @@ -133,14 +152,14 @@ func intToShareState(g int) collaboration.ShareState { } } -func formatUserID(u *userpb.UserId) string { +func FormatUserID(u *userpb.UserId) string { if u.Idp != "" { return fmt.Sprintf("%s:%s", u.OpaqueId, u.Idp) } return u.OpaqueId } -func extractUserID(u string) *userpb.UserId { +func ExtractUserID(u string) *userpb.UserId { parts := strings.Split(u, ":") if len(parts) > 1 { return &userpb.UserId{OpaqueId: parts[0], Idp: parts[1]} @@ -148,14 +167,14 @@ func extractUserID(u string) *userpb.UserId { return &userpb.UserId{OpaqueId: parts[0]} } -func formatGroupID(u *grouppb.GroupId) string { +func FormatGroupID(u *grouppb.GroupId) string { if u.Idp != "" { return fmt.Sprintf("%s:%s", u.OpaqueId, u.Idp) } return u.OpaqueId } -func extractGroupID(u string) *grouppb.GroupId { +func ExtractGroupID(u string) *grouppb.GroupId { parts := strings.Split(u, ":") if len(parts) > 1 { return &grouppb.GroupId{OpaqueId: parts[0], Idp: parts[1]} @@ -163,7 +182,7 @@ func extractGroupID(u string) *grouppb.GroupId { return &grouppb.GroupId{OpaqueId: parts[0]} } -func convertToCS3Share(s dbShare) *collaboration.Share { +func ConvertToCS3Share(s DBShare) *collaboration.Share { ts := &typespb.Timestamp{ Seconds: uint64(s.STime), } @@ -172,19 +191,53 @@ func convertToCS3Share(s dbShare) *collaboration.Share { OpaqueId: s.ID, }, ResourceId: &provider.ResourceId{OpaqueId: s.ItemSource, StorageId: s.Prefix}, - Permissions: &collaboration.SharePermissions{Permissions: intTosharePerm(s.Permissions)}, - Grantee: extractGrantee(s.ShareType, s.ShareWith), - Owner: extractUserID(s.UIDOwner), - Creator: extractUserID(s.UIDInitiator), + Permissions: &collaboration.SharePermissions{Permissions: IntTosharePerm(s.Permissions)}, + Grantee: ExtractGrantee(s.ShareType, s.ShareWith), + Owner: ExtractUserID(s.UIDOwner), + Creator: ExtractUserID(s.UIDInitiator), Ctime: ts, Mtime: ts, } } -func convertToCS3ReceivedShare(s dbShare) *collaboration.ReceivedShare { - share := convertToCS3Share(s) +func ConvertToCS3ReceivedShare(s DBShare) *collaboration.ReceivedShare { + share := ConvertToCS3Share(s) return &collaboration.ReceivedShare{ Share: share, - State: intToShareState(s.State), + State: IntToShareState(s.State), + } +} + +func ConvertToCS3PublicShare(s DBShare) *link.PublicShare { + ts := &typespb.Timestamp{ + Seconds: uint64(s.STime), + } + pwd := false + if s.ShareWith != "" { + pwd = true + } + var expires uint64 + if s.Expiration != "" { + t, err := time.Parse("2006-01-02 03:04:05", s.Expiration) + if err == nil { + expires = uint64(t.Unix()) + } + } + return &link.PublicShare{ + Id: &link.PublicShareId{ + OpaqueId: s.ID, + }, + ResourceId: &provider.ResourceId{OpaqueId: s.ItemSource, StorageId: s.Prefix}, + Permissions: &link.PublicSharePermissions{Permissions: IntTosharePerm(s.Permissions)}, + Owner: ExtractUserID(s.UIDOwner), + Creator: ExtractUserID(s.UIDInitiator), + Token: s.Token, + DisplayName: s.ShareName, + PasswordProtected: pwd, + Expiration: &typespb.Timestamp{ + Seconds: expires, + }, + Ctime: ts, + Mtime: ts, } } diff --git a/pkg/publicshare/manager/json/json.go b/pkg/publicshare/manager/json/json.go index 052840ac34..6c9a4c2f97 100644 --- a/pkg/publicshare/manager/json/json.go +++ b/pkg/publicshare/manager/json/json.go @@ -49,30 +49,6 @@ import ( "go.opencensus.io/trace" ) -type janitor struct { - m *manager - interval time.Duration -} - -func (j *janitor) run() { - ticker := time.NewTicker(j.interval) - work := make(chan os.Signal, 1) - signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT) - - for { - select { - case <-work: - return - case <-ticker.C: - j.m.cleanupExpiredShares() - } - } -} - -var j = janitor{ - interval: time.Minute, // TODO we want this interval configurable -} - func init() { registry.Register("json", New) } @@ -114,8 +90,7 @@ func New(c map[string]interface{}) (publicshare.Manager, error) { } } - j.m = &m - go j.run() + go m.startJanitorRun() return &m, nil } @@ -123,6 +98,7 @@ func New(c map[string]interface{}) (publicshare.Manager, error) { type config struct { File string `mapstructure:"file"` SharePasswordHashCost int `mapstructure:"password_hash_cost"` + JanitorRunInterval int `mapstructure:"janitor_run_interval"` } func (c *config) init() { @@ -132,15 +108,34 @@ func (c *config) init() { if c.SharePasswordHashCost == 0 { c.SharePasswordHashCost = 11 } + if c.JanitorRunInterval == 0 { + c.JanitorRunInterval = 60 + } } type manager struct { mutex *sync.Mutex file string - marshaler jsonpb.Marshaler - unmarshaler jsonpb.Unmarshaler - passwordHashCost int + marshaler jsonpb.Marshaler + unmarshaler jsonpb.Unmarshaler + passwordHashCost int + janitorRunInterval int +} + +func (m *manager) startJanitorRun() { + ticker := time.NewTicker(time.Duration(m.janitorRunInterval)) + work := make(chan os.Signal, 1) + signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT) + + for { + select { + case <-work: + return + case <-ticker.C: + m.cleanupExpiredShares() + } + } } // CreatePublicShare adds a new entry to manager.shares @@ -173,11 +168,6 @@ func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *pr Nanos: uint32(now % 1000000000), } - modifiedAt := &typespb.Timestamp{ - Seconds: uint64(now / 1000000000), - Nanos: uint32(now % 1000000000), - } - s := link.PublicShare{ Id: id, Owner: rInfo.GetOwner(), @@ -186,7 +176,7 @@ func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *pr Token: tkn, Permissions: g.Permissions, Ctime: createdAt, - Mtime: modifiedAt, + Mtime: createdAt, PasswordProtected: passwordProtected, Expiration: g.Expiration, DisplayName: displayName, From 8be838fc67b5a2d316c94408125b468921a6fad6 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 24 Feb 2021 19:06:35 +0100 Subject: [PATCH 2/6] Add comments to exported functions --- pkg/cbox/publicshare/sql/sql.go | 15 +-------------- pkg/cbox/share/sql/sql.go | 2 +- pkg/cbox/utils/conversions.go | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pkg/cbox/publicshare/sql/sql.go b/pkg/cbox/publicshare/sql/sql.go index ed0115d7c6..3201089d3e 100644 --- a/pkg/cbox/publicshare/sql/sql.go +++ b/pkg/cbox/publicshare/sql/sql.go @@ -65,20 +65,6 @@ type manager struct { db *sql.DB } -type dbShare struct { - ID string - UIDOwner string - UIDInitiator string - Prefix string - ItemSource string - ShareWith string - Permissions int - ShareType int - STime int - FileTarget string - State int -} - func (c *config) init() { if c.SharePasswordHashCost == 0 { c.SharePasswordHashCost = 11 @@ -103,6 +89,7 @@ func (m *manager) startJanitorRun() { } } +// New returns a new public share manager. func New(m map[string]interface{}) (publicshare.Manager, error) { c := &config{} if err := mapstructure.Decode(m, c); err != nil { diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 842871d369..5dd6fe8105 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -60,7 +60,7 @@ type mgr struct { db *sql.DB } -// New returns a new mgr. +// New returns a new share manager. func New(m map[string]interface{}) (share.Manager, error) { c, err := parseConfig(m) if err != nil { diff --git a/pkg/cbox/utils/conversions.go b/pkg/cbox/utils/conversions.go index f4efcbd1ac..a9ea9ac364 100644 --- a/pkg/cbox/utils/conversions.go +++ b/pkg/cbox/utils/conversions.go @@ -31,6 +31,7 @@ import ( typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ) +// DBShare stores information about user and public shares. type DBShare struct { ID string UIDOwner string @@ -48,6 +49,7 @@ type DBShare struct { State int } +// FormatGrantee formats a CS3API grantee to a string func FormatGrantee(g *provider.Grantee) (int, string) { var granteeType int var formattedID string @@ -64,6 +66,7 @@ func FormatGrantee(g *provider.Grantee) (int, string) { return granteeType, formattedID } +// ExtractGrantee retrieves the CS3API grantee from a formatted string func ExtractGrantee(t int, g string) *provider.Grantee { var grantee *provider.Grantee switch t { @@ -79,6 +82,7 @@ func ExtractGrantee(t int, g string) *provider.Grantee { return grantee } +// ResourceTypeToItem maps a resource type to an integer func ResourceTypeToItem(r provider.ResourceType) string { switch r { case provider.ResourceType_RESOURCE_TYPE_FILE: @@ -94,6 +98,7 @@ func ResourceTypeToItem(r provider.ResourceType) string { } } +// SharePermToInt maps read/write permissions to an integer func SharePermToInt(p *provider.ResourcePermissions) int { var perm int if p.CreateContainer { @@ -104,6 +109,7 @@ func SharePermToInt(p *provider.ResourcePermissions) int { return perm } +// IntTosharePerm retrieves read/write permissions from an integer func IntTosharePerm(p int) *provider.ResourcePermissions { switch p { case 1: @@ -141,6 +147,7 @@ func IntTosharePerm(p int) *provider.ResourcePermissions { } } +// IntToShareState retrieves the received share state from an integer func IntToShareState(g int) collaboration.ShareState { switch g { case 0: @@ -152,6 +159,7 @@ func IntToShareState(g int) collaboration.ShareState { } } +// FormatUserID formats a CS3API user ID to a string func FormatUserID(u *userpb.UserId) string { if u.Idp != "" { return fmt.Sprintf("%s:%s", u.OpaqueId, u.Idp) @@ -159,6 +167,7 @@ func FormatUserID(u *userpb.UserId) string { return u.OpaqueId } +// ExtractUserID retrieves a CS3API user ID from a string func ExtractUserID(u string) *userpb.UserId { parts := strings.Split(u, ":") if len(parts) > 1 { @@ -167,6 +176,7 @@ func ExtractUserID(u string) *userpb.UserId { return &userpb.UserId{OpaqueId: parts[0]} } +// FormatGroupID formats a CS3API group ID to a string func FormatGroupID(u *grouppb.GroupId) string { if u.Idp != "" { return fmt.Sprintf("%s:%s", u.OpaqueId, u.Idp) @@ -174,6 +184,7 @@ func FormatGroupID(u *grouppb.GroupId) string { return u.OpaqueId } +// ExtractGroupID retrieves a CS3API group ID from a string func ExtractGroupID(u string) *grouppb.GroupId { parts := strings.Split(u, ":") if len(parts) > 1 { @@ -182,6 +193,7 @@ func ExtractGroupID(u string) *grouppb.GroupId { return &grouppb.GroupId{OpaqueId: parts[0]} } +// ConvertToCS3Share converts a DBShare to a CS3API collaboration share func ConvertToCS3Share(s DBShare) *collaboration.Share { ts := &typespb.Timestamp{ Seconds: uint64(s.STime), @@ -200,6 +212,7 @@ func ConvertToCS3Share(s DBShare) *collaboration.Share { } } +// ConvertToCS3ReceivedShare converts a DBShare to a CS3API collaboration received share func ConvertToCS3ReceivedShare(s DBShare) *collaboration.ReceivedShare { share := ConvertToCS3Share(s) return &collaboration.ReceivedShare{ @@ -208,6 +221,7 @@ func ConvertToCS3ReceivedShare(s DBShare) *collaboration.ReceivedShare { } } +// ConvertToCS3PublicShare converts a DBShare to a CS3API public share func ConvertToCS3PublicShare(s DBShare) *link.PublicShare { ts := &typespb.Timestamp{ Seconds: uint64(s.STime), From efc9f78277572ea9af4c9e4d913d0ea87d6e8f65 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Wed, 24 Feb 2021 19:24:23 +0100 Subject: [PATCH 3/6] Specify unit in duration --- pkg/cbox/publicshare/sql/sql.go | 2 +- pkg/publicshare/manager/json/json.go | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/cbox/publicshare/sql/sql.go b/pkg/cbox/publicshare/sql/sql.go index 3201089d3e..8fe01c763a 100644 --- a/pkg/cbox/publicshare/sql/sql.go +++ b/pkg/cbox/publicshare/sql/sql.go @@ -75,7 +75,7 @@ func (c *config) init() { } func (m *manager) startJanitorRun() { - ticker := time.NewTicker(time.Duration(m.c.JanitorRunInterval)) + ticker := time.NewTicker(time.Duration(m.c.JanitorRunInterval) * time.Second) work := make(chan os.Signal, 1) signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT) diff --git a/pkg/publicshare/manager/json/json.go b/pkg/publicshare/manager/json/json.go index 6c9a4c2f97..f7e5188572 100644 --- a/pkg/publicshare/manager/json/json.go +++ b/pkg/publicshare/manager/json/json.go @@ -63,11 +63,12 @@ func New(c map[string]interface{}) (publicshare.Manager, error) { conf.init() m := manager{ - mutex: &sync.Mutex{}, - marshaler: jsonpb.Marshaler{}, - unmarshaler: jsonpb.Unmarshaler{}, - file: conf.File, - passwordHashCost: conf.SharePasswordHashCost, + mutex: &sync.Mutex{}, + marshaler: jsonpb.Marshaler{}, + unmarshaler: jsonpb.Unmarshaler{}, + file: conf.File, + passwordHashCost: conf.SharePasswordHashCost, + janitorRunInterval: conf.JanitorRunInterval, } // attempt to create the db file @@ -124,7 +125,7 @@ type manager struct { } func (m *manager) startJanitorRun() { - ticker := time.NewTicker(time.Duration(m.janitorRunInterval)) + ticker := time.NewTicker(time.Duration(m.janitorRunInterval) * time.Second) work := make(chan os.Signal, 1) signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT) From 5a8726c64a880ef2443a0a225d0af7dc7a8ac559 Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 25 Feb 2021 15:37:35 +0100 Subject: [PATCH 4/6] Fixes --- pkg/cbox/publicshare/sql/sql.go | 35 ++++++++++++++------------------- pkg/cbox/share/sql/sql.go | 4 ++-- pkg/cbox/utils/conversions.go | 18 ++++++++--------- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/pkg/cbox/publicshare/sql/sql.go b/pkg/cbox/publicshare/sql/sql.go index 8fe01c763a..3fff62a034 100644 --- a/pkg/cbox/publicshare/sql/sql.go +++ b/pkg/cbox/publicshare/sql/sql.go @@ -153,7 +153,7 @@ func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *pr params = append(params, password) } - if g.Expiration.Seconds != 0 { + if g.Expiration != nil && g.Expiration.Seconds != 0 { t := time.Unix(int64(g.Expiration.Seconds), 0) query += ",expiration=?" params = append(params, t) @@ -286,14 +286,7 @@ func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.Pu } if expired(s) { - req := link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: s.Id.OpaqueId, - }, - }, - } - if err := m.RevokePublicShare(ctx, u, &req); err != nil { + if err := m.cleanupExpiredShares(); err != nil { return nil, err } return nil, errtypes.NotFound(ref.String()) @@ -348,13 +341,7 @@ func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters [] } cs3Share := conversions.ConvertToCS3PublicShare(s) if expired(cs3Share) { - _ = m.RevokePublicShare(ctx, u, &link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: cs3Share.Id.OpaqueId, - }, - }, - }) + _ = m.cleanupExpiredShares() } else { shares = append(shares, cs3Share) } @@ -368,7 +355,7 @@ func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters [] func (m *manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error { uid := conversions.FormatUserID(u.Id) - query := "delete from oc_share where" + query := "delete from oc_share where " params := []interface{}{} switch { @@ -411,12 +398,20 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token, password str return nil, err } if s.ShareWith != "" { - if check := checkPasswordHash(password, s.ShareWith); check { - return conversions.ConvertToCS3PublicShare(s), nil + if check := checkPasswordHash(password, s.ShareWith); !check { + return nil, errtypes.InvalidCredentials(token) + } + } + + cs3Share := conversions.ConvertToCS3PublicShare(s) + if expired(cs3Share) { + if err := m.cleanupExpiredShares(); err != nil { + return nil, err } + return nil, errtypes.NotFound(token) } - return nil, errtypes.NotFound(fmt.Sprintf("share with token: " + token)) + return cs3Share, nil } func (m *manager) cleanupExpiredShares() error { diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 5dd6fe8105..db86988f21 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -159,9 +159,10 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceInfo, g *collabora } func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.Share, error) { + uid := conversions.FormatUserID(user.ContextMustGetUser(ctx).Id) s := conversions.DBShare{ID: id.OpaqueId} 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 (uid_owner=? or uid_initiator=?)" - if err := m.db.QueryRow(query, id.OpaqueId).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.Prefix, &s.ItemSource, &s.STime, &s.Permissions, &s.ShareType); err != nil { + if err := m.db.QueryRow(query, id.OpaqueId, uid, uid).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) } @@ -207,7 +208,6 @@ func (m *mgr) GetShare(ctx context.Context, ref *collaboration.ShareReference) ( func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { uid := conversions.FormatUserID(user.ContextMustGetUser(ctx).Id) - var query string params := []interface{}{} switch { diff --git a/pkg/cbox/utils/conversions.go b/pkg/cbox/utils/conversions.go index a9ea9ac364..38d5424911 100644 --- a/pkg/cbox/utils/conversions.go +++ b/pkg/cbox/utils/conversions.go @@ -68,7 +68,7 @@ func FormatGrantee(g *provider.Grantee) (int, string) { // ExtractGrantee retrieves the CS3API grantee from a formatted string func ExtractGrantee(t int, g string) *provider.Grantee { - var grantee *provider.Grantee + var grantee provider.Grantee switch t { case 0: grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER @@ -79,7 +79,7 @@ func ExtractGrantee(t int, g string) *provider.Grantee { default: grantee.Type = provider.GranteeType_GRANTEE_TYPE_INVALID } - return grantee + return &grantee } // ResourceTypeToItem maps a resource type to an integer @@ -230,11 +230,13 @@ func ConvertToCS3PublicShare(s DBShare) *link.PublicShare { if s.ShareWith != "" { pwd = true } - var expires uint64 + var expires *typespb.Timestamp if s.Expiration != "" { t, err := time.Parse("2006-01-02 03:04:05", s.Expiration) if err == nil { - expires = uint64(t.Unix()) + expires = &typespb.Timestamp{ + Seconds: uint64(t.Unix()), + } } } return &link.PublicShare{ @@ -248,10 +250,8 @@ func ConvertToCS3PublicShare(s DBShare) *link.PublicShare { Token: s.Token, DisplayName: s.ShareName, PasswordProtected: pwd, - Expiration: &typespb.Timestamp{ - Seconds: expires, - }, - Ctime: ts, - Mtime: ts, + Expiration: expires, + Ctime: ts, + Mtime: ts, } } From cbf0ef64ff83462058cbf4c8a2db82201de3081b Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Thu, 25 Feb 2021 16:23:51 +0100 Subject: [PATCH 5/6] Fix deletion time parsing in EOS --- pkg/storage/utils/eosfs/eosfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 08f38bd17c..a064a653d0 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1306,7 +1306,7 @@ func (fs *eosfs) convertToRecycleItem(ctx context.Context, eosDeletedItem *eoscl Path: path, Key: eosDeletedItem.RestoreKey, Size: eosDeletedItem.Size, - DeletionTime: &types.Timestamp{Seconds: eosDeletedItem.DeletionMTime / 1000}, // TODO(labkode): check if eos time is millis or nanos + DeletionTime: &types.Timestamp{Seconds: eosDeletedItem.DeletionMTime}, } if eosDeletedItem.IsDir { recycleItem.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER From 39ed9986d509d3525ebd4a8a756216562d0f1e2f Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 26 Feb 2021 10:26:03 +0100 Subject: [PATCH 6/6] Move random string generator to utils --- pkg/cbox/publicshare/sql/sql.go | 15 +++------------ pkg/publicshare/manager/json/json.go | 18 ++++-------------- pkg/utils/utils.go | 11 +++++++++++ 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/pkg/cbox/publicshare/sql/sql.go b/pkg/cbox/publicshare/sql/sql.go index 3fff62a034..86e24b0a70 100644 --- a/pkg/cbox/publicshare/sql/sql.go +++ b/pkg/cbox/publicshare/sql/sql.go @@ -16,13 +16,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package filesystem +package sql import ( "context" "database/sql" "fmt" - "math/rand" "os" "os/signal" "strconv" @@ -40,6 +39,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/publicshare/manager/registry" + "github.com/cs3org/reva/pkg/utils" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -113,7 +113,7 @@ func New(m map[string]interface{}) (publicshare.Manager, error) { func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) { - tkn := randString(15) + tkn := utils.RandString(15) now := time.Now().Unix() displayName, ok := rInfo.ArbitraryMetadata.Metadata["name"] @@ -446,12 +446,3 @@ func checkPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(strings.TrimPrefix(hash, "1|")), []byte(password)) return err == nil } - -func randString(n int) string { - var l = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]rune, n) - for i := range b { - b[i] = l[rand.Intn(len(l))] - } - return string(b) -} diff --git a/pkg/publicshare/manager/json/json.go b/pkg/publicshare/manager/json/json.go index f7e5188572..daff07bad3 100644 --- a/pkg/publicshare/manager/json/json.go +++ b/pkg/publicshare/manager/json/json.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package filesystem +package json import ( "bytes" @@ -24,7 +24,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math/rand" "os" "os/signal" "path/filepath" @@ -43,6 +42,7 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/publicshare/manager/registry" + "github.com/cs3org/reva/pkg/utils" "github.com/golang/protobuf/jsonpb" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -142,10 +142,10 @@ func (m *manager) startJanitorRun() { // CreatePublicShare adds a new entry to manager.shares func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) { id := &link.PublicShareId{ - OpaqueId: randString(15), + OpaqueId: utils.RandString(15), } - tkn := randString(15) + tkn := utils.RandString(15) now := time.Now().UnixNano() displayName, ok := rInfo.ArbitraryMetadata.Metadata["name"] @@ -534,16 +534,6 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token, password str return nil, errtypes.NotFound(fmt.Sprintf("share with token: `%v` not found", token)) } -// randString is a helper to create tokens. It could be a token manager instead. -func randString(n int) string { - var l = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]rune, n) - for i := range b { - b[i] = l[rand.Intn(len(l))] - } - return string(b) -} - func (m *manager) readDb() (map[string]interface{}, error) { db := map[string]interface{}{} readBytes, err := ioutil.ReadFile(m.file) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index ad2a9cbcd8..a321559039 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -19,6 +19,7 @@ package utils import ( + "math/rand" "net" "net/http" "os/user" @@ -99,6 +100,16 @@ func ResolvePath(path string) (string, error) { return path, nil } +// RandString is a helper to create tokens. +func RandString(n int) string { + var l = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = l[rand.Intn(len(l))] + } + return string(b) +} + // TSToUnixNano converts a protobuf Timestamp to uint64 // with nanoseconds resolution. func TSToUnixNano(ts *types.Timestamp) uint64 {