From 06aa61fcd366379d1f7ff4f73e71d42d33d932fd Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 26 Feb 2021 14:59:58 +0100 Subject: [PATCH] SQL driver for the publicshare service (#1495) --- .../unreleased/publicshare-sql-driver.md | 3 + pkg/cbox/loader/loader.go | 1 + pkg/cbox/publicshare/sql/sql.go | 448 ++++++++++++++++++ pkg/cbox/share/sql/sql.go | 153 +++--- pkg/cbox/{share/sql => utils}/conversions.go | 117 ++++- pkg/publicshare/manager/json/json.go | 89 ++-- pkg/storage/utils/eosfs/eosfs.go | 2 +- pkg/utils/utils.go | 11 + 8 files changed, 658 insertions(+), 166 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 (54%) 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..86e24b0a70 --- /dev/null +++ b/pkg/cbox/publicshare/sql/sql.go @@ -0,0 +1,448 @@ +// 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 sql + +import ( + "context" + "database/sql" + "fmt" + "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/cs3org/reva/pkg/utils" + "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 +} + +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) * time.Second) + 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() + } + } +} + +// 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 { + 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 := utils.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 != nil && 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) { + if err := m.cleanupExpiredShares(); 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.cleanupExpiredShares() + } 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 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 cs3Share, nil +} + +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 +} diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 62b2fbf2c1..db86988f21 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,21 +60,7 @@ 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. +// New returns a new share manager. func New(m map[string]interface{}) (share.Manager, error) { c, err := parseConfig(m) if err != nil { @@ -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,32 @@ 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=?" - 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 { + 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, 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) } 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 +203,23 @@ 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 54% rename from pkg/cbox/share/sql/conversions.go rename to pkg/cbox/utils/conversions.go index 5e24fdea75..38d5424911 100644 --- a/pkg/cbox/share/sql/conversions.go +++ b/pkg/cbox/utils/conversions.go @@ -16,51 +16,74 @@ // 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) { +// DBShare stores information about user and public shares. +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 +} + +// FormatGrantee formats a CS3API grantee to a string +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 { - var grantee *provider.Grantee +// ExtractGrantee retrieves the CS3API grantee from a formatted string +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 + return &grantee } -func resourceTypeToItem(r provider.ResourceType) string { +// ResourceTypeToItem maps a resource type to an integer +func ResourceTypeToItem(r provider.ResourceType) string { switch r { case provider.ResourceType_RESOURCE_TYPE_FILE: return "file" @@ -75,7 +98,8 @@ func resourceTypeToItem(r provider.ResourceType) string { } } -func sharePermToInt(p *provider.ResourcePermissions) int { +// SharePermToInt maps read/write permissions to an integer +func SharePermToInt(p *provider.ResourcePermissions) int { var perm int if p.CreateContainer { perm = 15 @@ -85,7 +109,8 @@ func sharePermToInt(p *provider.ResourcePermissions) int { return perm } -func intTosharePerm(p int) *provider.ResourcePermissions { +// IntTosharePerm retrieves read/write permissions from an integer +func IntTosharePerm(p int) *provider.ResourcePermissions { switch p { case 1: return &provider.ResourcePermissions{ @@ -122,7 +147,8 @@ func intTosharePerm(p int) *provider.ResourcePermissions { } } -func intToShareState(g int) collaboration.ShareState { +// IntToShareState retrieves the received share state from an integer +func IntToShareState(g int) collaboration.ShareState { switch g { case 0: return collaboration.ShareState_SHARE_STATE_PENDING @@ -133,14 +159,16 @@ func intToShareState(g int) collaboration.ShareState { } } -func formatUserID(u *userpb.UserId) string { +// 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) } return u.OpaqueId } -func extractUserID(u string) *userpb.UserId { +// ExtractUserID retrieves a CS3API user ID from a string +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 +176,16 @@ func extractUserID(u string) *userpb.UserId { return &userpb.UserId{OpaqueId: parts[0]} } -func formatGroupID(u *grouppb.GroupId) string { +// 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) } return u.OpaqueId } -func extractGroupID(u string) *grouppb.GroupId { +// ExtractGroupID retrieves a CS3API group ID from a string +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 +193,8 @@ func extractGroupID(u string) *grouppb.GroupId { return &grouppb.GroupId{OpaqueId: parts[0]} } -func convertToCS3Share(s dbShare) *collaboration.Share { +// ConvertToCS3Share converts a DBShare to a CS3API collaboration share +func ConvertToCS3Share(s DBShare) *collaboration.Share { ts := &typespb.Timestamp{ Seconds: uint64(s.STime), } @@ -172,19 +203,55 @@ 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) +// ConvertToCS3ReceivedShare converts a DBShare to a CS3API collaboration received share +func ConvertToCS3ReceivedShare(s DBShare) *collaboration.ReceivedShare { + share := ConvertToCS3Share(s) return &collaboration.ReceivedShare{ Share: share, - State: intToShareState(s.State), + State: IntToShareState(s.State), + } +} + +// ConvertToCS3PublicShare converts a DBShare to a CS3API public share +func ConvertToCS3PublicShare(s DBShare) *link.PublicShare { + ts := &typespb.Timestamp{ + Seconds: uint64(s.STime), + } + pwd := false + if s.ShareWith != "" { + pwd = true + } + var expires *typespb.Timestamp + if s.Expiration != "" { + t, err := time.Parse("2006-01-02 03:04:05", s.Expiration) + if err == nil { + expires = &typespb.Timestamp{ + Seconds: 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: expires, + Ctime: ts, + Mtime: ts, } } diff --git a/pkg/publicshare/manager/json/json.go b/pkg/publicshare/manager/json/json.go index 052840ac34..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,36 +42,13 @@ 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" "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) } @@ -87,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 @@ -114,8 +91,7 @@ func New(c map[string]interface{}) (publicshare.Manager, error) { } } - j.m = &m - go j.run() + go m.startJanitorRun() return &m, nil } @@ -123,6 +99,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,24 +109,43 @@ 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) * time.Second) + 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 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"] @@ -173,11 +169,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 +177,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, @@ -543,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/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 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 {