From 700488e808b698052b638282790a097ff25a8e47 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Wed, 4 Jan 2023 15:52:08 +0100 Subject: [PATCH 1/6] add an expiration to shares --- go.mod | 2 ++ go.sum | 4 ++-- .../grpc/services/gateway/ocmshareprovider.go | 2 +- .../services/gateway/usershareprovider.go | 7 +++--- .../ocs/handlers/apps/sharing/shares/user.go | 23 +++++++++++++++++++ pkg/storage/utils/ace/ace.go | 19 +++++++++++++-- pkg/storage/utils/decomposedfs/grants_test.go | 2 +- 7 files changed, 50 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index cad68d46f8..ab52a5ac94 100644 --- a/go.mod +++ b/go.mod @@ -208,3 +208,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/cs3org/go-cs3apis => github.com/c0rby/go-cs3apis v0.0.0-20230110100311-5b424f1baa35 diff --git a/go.sum b/go.sum index 3eca282db0..307fcb6791 100644 --- a/go.sum +++ b/go.sum @@ -202,6 +202,8 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= +github.com/c0rby/go-cs3apis v0.0.0-20230110100311-5b424f1baa35 h1:bbpRY/l4z5MTH+TRGZdkIqDM9JXQQewJdO1o+80zcok= +github.com/c0rby/go-cs3apis v0.0.0-20230110100311-5b424f1baa35/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -245,8 +247,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20221012090518-ef2996678965 h1:y4n2j68LLnvac+zw/al8MfPgO5aQiIwLmHM/JzYN8AM= -github.com/cs3org/go-cs3apis v0.0.0-20221012090518-ef2996678965/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/grpc/services/gateway/ocmshareprovider.go b/internal/grpc/services/gateway/ocmshareprovider.go index cdba8fbd78..f635621f5e 100644 --- a/internal/grpc/services/gateway/ocmshareprovider.go +++ b/internal/grpc/services/gateway/ocmshareprovider.go @@ -54,7 +54,7 @@ func (s *svc) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest } if s.c.CommitShareToStorageGrant { - addGrantStatus, err := s.addGrant(ctx, req.ResourceId, req.Grant.Grantee, req.Grant.Permissions.Permissions, nil) + addGrantStatus, err := s.addGrant(ctx, req.ResourceId, req.Grant.Grantee, req.Grant.Permissions.Permissions, nil, nil) if err != nil { return nil, errors.Wrap(err, "gateway: error adding OCM grant to storage") } diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index 79c250c153..16ecab09a8 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -389,7 +389,7 @@ func (s *svc) denyGrant(ctx context.Context, id *provider.ResourceId, g *provide return grantRes.Status, nil } -func (s *svc) addGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, p *provider.ResourcePermissions, opaque *typesv1beta1.Opaque) (*rpc.Status, error) { +func (s *svc) addGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, p *provider.ResourcePermissions, expiration *typesv1beta1.Timestamp, opaque *typesv1beta1.Opaque) (*rpc.Status, error) { ref := &provider.Reference{ ResourceId: id, } @@ -401,6 +401,7 @@ func (s *svc) addGrant(ctx context.Context, id *provider.ResourceId, g *provider Grantee: g, Permissions: p, Creator: creator.GetId(), + Expiration: expiration, }, Opaque: opaque, } @@ -568,7 +569,7 @@ func (s *svc) addShare(ctx context.Context, req *collaboration.CreateShareReques return nil, errors.Wrap(err, "gateway: error denying grant in storage") } } else { - status, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, nil) + status, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, req.Grant.Expiration, nil) if err != nil { return nil, errors.Wrap(err, "gateway: error adding grant to storage") } @@ -609,7 +610,7 @@ func (s *svc) addSpaceShare(ctx context.Context, req *collaboration.CreateShareR return nil, errors.Wrap(err, "gateway: error denying grant in storage") } } else { - st, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, opaque) + st, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions, req.Grant.Expiration, opaque) if err != nil { return nil, errors.Wrap(err, "gateway: error adding grant to storage") } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 9def610778..887858f862 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -20,6 +20,7 @@ package shares import ( "net/http" + "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -34,6 +35,10 @@ import ( "github.com/cs3org/reva/v2/pkg/utils" ) +const ( + _iso8601 = "2006-01-02T15:04:05Z0700" +) + func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statInfo *provider.ResourceInfo, role *conversions.Role, roleVal []byte) (*collaboration.Share, *ocsError) { ctx := r.Context() c, err := h.getClient() @@ -75,6 +80,23 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn } } + expireDate := r.PostFormValue("expireDate") + var expirationTs *types.Timestamp + if expireDate != "" { + expiration, err := time.Parse(_iso8601, expireDate) + if err != nil { + return nil, &ocsError{ + Code: response.MetaBadRequest.StatusCode, + Message: "could not parse expireDate", + Error: err, + } + } + expirationTs = &types.Timestamp{ + Seconds: uint64(expiration.UnixNano() / int64(time.Second)), + Nanos: uint32(expiration.UnixNano() % int64(time.Second)), + } + } + createShareReq := &collaboration.CreateShareRequest{ Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ @@ -93,6 +115,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn Permissions: &collaboration.SharePermissions{ Permissions: role.CS3ResourcePermissions(), }, + Expiration: expirationTs, }, } diff --git a/pkg/storage/utils/ace/ace.go b/pkg/storage/utils/ace/ace.go index 57f2f6ed2d..2774782363 100644 --- a/pkg/storage/utils/ace/ace.go +++ b/pkg/storage/utils/ace/ace.go @@ -24,10 +24,12 @@ import ( "fmt" "strconv" "strings" + "time" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/pkg/storage/utils/grants" ) @@ -181,7 +183,7 @@ type ACE struct { // sharing specific shareTime int // s creator string // c - expires int // e + expires int64 // e password string // w passWord TODO h = hash label string // l } @@ -204,6 +206,11 @@ func FromGrant(g *provider.Grant) *ACE { } else { e.principal = "u:" + g.Grantee.GetUserId().OpaqueId } + + if g.Expiration != nil { + e.expires = int64(g.Expiration.Seconds)*int64(time.Second) + int64(g.Expiration.Nanos) + } + return e } @@ -223,6 +230,7 @@ func (e *ACE) Marshal() (string, []byte) { fmt.Sprintf("f=%s", e.flags), fmt.Sprintf("p=%s", e.permissions), fmt.Sprintf("c=%s", e.creator), + fmt.Sprintf("e=%d", e.expires), }); err != nil { return "", nil } @@ -279,6 +287,13 @@ func (e *ACE) Grant() *provider.Grant { g.Grantee.Id = &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: id}} } + if e.expires != 0 { + g.Expiration = &typesv1beta1.Timestamp{ + Seconds: uint64(e.expires / int64(time.Second)), + Nanos: uint32(e.expires % int64(time.Second)), + } + } + return g } @@ -398,7 +413,7 @@ func unmarshalKV(s string) (*ACE, error) { case "c": e.creator = kv[1] case "e": - v, err := strconv.Atoi(kv[1]) + v, err := strconv.ParseInt(kv[1], 10, 64) if err != nil { return nil, err } diff --git a/pkg/storage/utils/decomposedfs/grants_test.go b/pkg/storage/utils/decomposedfs/grants_test.go index e7ebbd4a23..a165961b31 100644 --- a/pkg/storage/utils/decomposedfs/grants_test.go +++ b/pkg/storage/utils/decomposedfs/grants_test.go @@ -142,7 +142,7 @@ var _ = Describe("Grants", func() { localPath := n.InternalPath() attr, err := xattr.Get(localPath, xattrs.GrantUserAcePrefix+grant.Grantee.GetUserId().OpaqueId) Expect(err).ToNot(HaveOccurred()) - Expect(string(attr)).To(Equal(fmt.Sprintf("\x00t=A:f=:p=rw:c=%s", o.GetOpaqueId()+"\n"))) // NOTE: this tests ace package + Expect(string(attr)).To(Equal(fmt.Sprintf("\x00t=A:f=:p=rw:c=%s:e=0\n", o.GetOpaqueId()))) // NOTE: this tests ace package }) It("creates a storage space per created grant", func() { From f66282a6f3ce304c051f7f1c0b0f99dc051b2808 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 5 Jan 2023 13:49:13 +0100 Subject: [PATCH 2/6] add expiration to the share listing --- internal/http/services/owncloud/ocs/conversions/main.go | 9 +++++++++ pkg/share/manager/jsoncs3/jsoncs3.go | 1 + 2 files changed, 10 insertions(+) diff --git a/internal/http/services/owncloud/ocs/conversions/main.go b/internal/http/services/owncloud/ocs/conversions/main.go index c3f14425a4..2efa4673e5 100644 --- a/internal/http/services/owncloud/ocs/conversions/main.go +++ b/internal/http/services/owncloud/ocs/conversions/main.go @@ -60,6 +60,9 @@ const ( // ShareWithUserTypeGuest represents a guest user ShareWithUserTypeGuest ShareWithUserType = 1 + + // The datetime format of ISO8601 + _iso8601 = "2006-01-02T15:04:05Z0700" ) // ResourceType indicates the OCS type of the resource @@ -242,6 +245,12 @@ func CS3Share2ShareData(ctx context.Context, share *collaboration.Share) (*Share if share.Ctime != nil { sd.STime = share.Ctime.Seconds // TODO CS3 api birth time = btime } + + if share.Expiration != nil { + expiration := time.Unix(int64(share.Expiration.Seconds), int64(share.Expiration.Nanos)) + sd.Expiration = expiration.Format(_iso8601) + } + return sd, nil } diff --git a/pkg/share/manager/jsoncs3/jsoncs3.go b/pkg/share/manager/jsoncs3/jsoncs3.go index 7de735da29..081c1bffe2 100644 --- a/pkg/share/manager/jsoncs3/jsoncs3.go +++ b/pkg/share/manager/jsoncs3/jsoncs3.go @@ -238,6 +238,7 @@ func (m *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *colla ResourceId: md.Id, Permissions: g.Permissions, Grantee: g.Grantee, + Expiration: g.Expiration, Owner: md.Owner, Creator: user.Id, Ctime: ts, From 6c3d7d70fb5510ec556998cfb3d0a5f0b5dd02df Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 5 Jan 2023 15:13:37 +0100 Subject: [PATCH 3/6] remove expired shares on access --- pkg/share/manager/jsoncs3/jsoncs3.go | 84 ++++++++++++++++++---------- pkg/share/share.go | 7 +++ 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/pkg/share/manager/jsoncs3/jsoncs3.go b/pkg/share/manager/jsoncs3/jsoncs3.go index 081c1bffe2..91bcef7f6e 100644 --- a/pkg/share/manager/jsoncs3/jsoncs3.go +++ b/pkg/share/manager/jsoncs3/jsoncs3.go @@ -27,6 +27,7 @@ import ( "github.com/google/uuid" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" + "github.com/rs/zerolog/log" "google.golang.org/genproto/protobuf/field_mask" gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -406,35 +407,7 @@ func (m *Manager) Unshare(ctx context.Context, ref *collaboration.ShareReference return errtypes.NotFound(ref.String()) } - storageID, spaceID, _ := shareid.Decode(s.Id.OpaqueId) - err = m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.Cache.Sync(ctx, storageID, spaceID); err != nil { - return err - } - err = m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) - // TODO try more often? - } - if err != nil { - return err - } - - // remove from created cache - err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) - if _, ok := err.(errtypes.IsPreconditionFailed); ok { - if err := m.CreatedCache.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { - return err - } - err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) - // TODO try more often? - } - if err != nil { - return err - } - - // TODO remove from grantee cache - - return nil + return m.removeShare(ctx, s) } // UpdateShare updates the mode of the given share. @@ -530,6 +503,13 @@ func (m *Manager) listSharesByIDs(ctx context.Context, user *userv1beta1.User, f shares := m.Cache.ListSpace(providerID, spaceID) for _, s := range shares.Shares { + if share.IsExpired(s) { + if err := m.removeShare(ctx, s); err != nil { + log.Error().Err(err). + Msg("failed to unshare expired share") + } + continue + } if !share.MatchesFilters(s, filters) { continue } @@ -575,6 +555,13 @@ func (m *Manager) listCreatedShares(ctx context.Context, user *userv1beta1.User, if s == nil { continue } + if share.IsExpired(s) { + if err := m.removeShare(ctx, s); err != nil { + log.Error().Err(err). + Msg("failed to unshare expired share") + } + continue + } if utils.UserEqual(user.GetId(), s.GetCreator()) { if share.MatchesFilters(s, filters) { ss = append(ss, s) @@ -649,6 +636,13 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati if s == nil { continue } + if share.IsExpired(s) { + if err := m.removeShare(ctx, s); err != nil { + log.Error().Err(err). + Msg("failed to unshare expired share") + } + continue + } if share.IsGrantedToUser(s, user) { if share.MatchesFiltersWithState(s, state.State, filters) { @@ -817,3 +811,35 @@ func (m *Manager) Load(ctx context.Context, shareChan <-chan *collaboration.Shar return nil } + +func (m *Manager) removeShare(ctx context.Context, s *collaboration.Share) error { + storageID, spaceID, _ := shareid.Decode(s.Id.OpaqueId) + err := m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.Cache.Sync(ctx, storageID, spaceID); err != nil { + return err + } + err = m.Cache.Remove(ctx, storageID, spaceID, s.Id.OpaqueId) + // TODO try more often? + } + if err != nil { + return err + } + + // remove from created cache + err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) + if _, ok := err.(errtypes.IsPreconditionFailed); ok { + if err := m.CreatedCache.Sync(ctx, s.GetCreator().GetOpaqueId()); err != nil { + return err + } + err = m.CreatedCache.Remove(ctx, s.GetCreator().GetOpaqueId(), s.Id.OpaqueId) + // TODO try more often? + } + if err != nil { + return err + } + + // TODO remove from grantee cache + + return nil +} diff --git a/pkg/share/share.go b/pkg/share/share.go index 517bdb6c94..fdb541aa23 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -20,6 +20,7 @@ package share import ( "context" + "time" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -238,3 +239,9 @@ func GroupFiltersByType(filters []*collaboration.Filter) map[collaboration.Filte func FilterFiltersByType(f []*collaboration.Filter, t collaboration.Filter_Type) []*collaboration.Filter { return GroupFiltersByType(f)[t] } + +// IsExpired tests whether a share is expired +func IsExpired(s *collaboration.Share) bool { + expiration := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos())) + return s.Expiration != nil && expiration.Before(time.Now()) +} From 382afbc26ed370b753f6e06caccb9162ecbde769 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 5 Jan 2023 17:51:27 +0100 Subject: [PATCH 4/6] implement updating and removing the share expiration --- .../usershareprovider/usershareprovider.go | 2 +- .../handlers/apps/sharing/shares/shares.go | 44 ++++++++++----- .../ocs/handlers/apps/sharing/shares/user.go | 3 + pkg/cbox/share/sql/sql.go | 2 +- pkg/share/manager/cs3/cs3.go | 2 +- pkg/share/manager/cs3/cs3_test.go | 2 +- pkg/share/manager/json/json.go | 42 ++++++++++++-- pkg/share/manager/jsoncs3/jsoncs3.go | 55 ++++++++++++++----- pkg/share/manager/jsoncs3/jsoncs3_test.go | 8 +-- pkg/share/manager/memory/memory.go | 31 ++++++++++- pkg/share/manager/owncloudsql/owncloudsql.go | 2 +- .../manager/owncloudsql/owncloudsql_test.go | 2 +- pkg/share/mocks/Manager.go | 14 ++--- pkg/share/share.go | 2 +- 14 files changed, 156 insertions(+), 55 deletions(-) diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index f5fa2da036..eb5864854c 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -204,7 +204,7 @@ func (s *service) ListShares(ctx context.Context, req *collaboration.ListSharesR } func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) { - share, err := s.sm.UpdateShare(ctx, req.Ref, req.Field.GetPermissions()) // TODO(labkode): check what to update + share, err := s.sm.UpdateShare(ctx, req.Ref, req.Field.GetPermissions(), req.Share, req.UpdateMask) // TODO(labkode): check what to update if err != nil { return &collaboration.UpdateShareResponse{ Status: status.NewInternal(ctx, "error updating share"), diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 24fe11e91a..66be6ad059 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -38,6 +38,7 @@ import ( 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" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/go-chi/chi/v5" "github.com/rs/zerolog" "google.golang.org/grpc/metadata" @@ -729,21 +730,36 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID st return } + shareR.Share.Permissions = &collaboration.SharePermissions{Permissions: role.CS3ResourcePermissions()} + + var fieldMaskPaths = []string{"permissions"} + + expireDate := r.PostFormValue("expireDate") + var expirationTs *types.Timestamp + if expireDate != "" { + + expiration, err := time.Parse(time.RFC3339, expireDate) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "could not parse expireDate", err) + return + } + expirationTs = &types.Timestamp{ + Seconds: uint64(expiration.UnixNano() / int64(time.Second)), + Nanos: uint32(expiration.UnixNano() % int64(time.Second)), + } + + shareR.Share.Expiration = expirationTs + fieldMaskPaths = append(fieldMaskPaths, "expiration") + } else if r.Form.Has("expireDate") { + // If the expiration parameter was sent but is empty, then the expiration should be removed. + shareR.Share.Expiration = nil + fieldMaskPaths = append(fieldMaskPaths, "expiration") + } + uReq := &collaboration.UpdateShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: shareID, - }, - }, - }, - Field: &collaboration.UpdateShareRequest_UpdateField{ - Field: &collaboration.UpdateShareRequest_UpdateField_Permissions{ - Permissions: &collaboration.SharePermissions{ - // this completely overwrites the permissions for this user - Permissions: role.CS3ResourcePermissions(), - }, - }, + Share: shareR.Share, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: fieldMaskPaths, }, } uRes, err := client.UpdateShare(ctx, uReq) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index 887858f862..5dab9dfddf 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -83,6 +83,9 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn expireDate := r.PostFormValue("expireDate") var expirationTs *types.Timestamp if expireDate != "" { + // FIXME: the web ui sends the RFC3339 format when updating a share but + // initially on creating a share the format ISO 8601 is used. + // OC10 uses RFC3339 in both cases so we should fix the web ui and change it here. expiration, err := time.Parse(_iso8601, expireDate) if err != nil { return nil, &ocsError{ diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index f2c518134e..b9b484b9d7 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -249,7 +249,7 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er return nil } -func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { +func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { permissions := conversions.SharePermToInt(p.Permissions) uid := conversions.FormatUserID(ctxpkg.ContextMustGetUser(ctx).Id) diff --git a/pkg/share/manager/cs3/cs3.go b/pkg/share/manager/cs3/cs3.go index efd5e16081..be03a0583d 100644 --- a/pkg/share/manager/cs3/cs3.go +++ b/pkg/share/manager/cs3/cs3.go @@ -498,7 +498,7 @@ func (m *Manager) ListShares(ctx context.Context, filters []*collaboration.Filte } // UpdateShare updates the mode of the given share. -func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { +func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { if err := m.initialize(); err != nil { return nil, err } diff --git a/pkg/share/manager/cs3/cs3_test.go b/pkg/share/manager/cs3/cs3_test.go index 879caa53e5..70aec0c6c2 100644 --- a/pkg/share/manager/cs3/cs3_test.go +++ b/pkg/share/manager/cs3/cs3_test.go @@ -302,7 +302,7 @@ var _ = Describe("Manager", func() { Expect(share.Permissions.Permissions.AddGrant).To(BeFalse()) s, err := m.UpdateShare(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: share.Id}}, - &collaboration.SharePermissions{Permissions: &provider.ResourcePermissions{AddGrant: true}}) + &collaboration.SharePermissions{Permissions: &provider.ResourcePermissions{AddGrant: true}}, nil, nil) Expect(err).ToNot(HaveOccurred()) Expect(s).ToNot(BeNil()) Expect(s.Permissions.Permissions.AddGrant).To(BeTrue()) diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 8b57b3c500..520988790d 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -373,21 +373,51 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er return nil } -func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { +func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { m.Lock() defer m.Unlock() - idx, s, err := m.get(ref) - if err != nil { - return nil, err + + var ( + idx int + toUpdate *collaboration.Share + ) + + if ref != nil { + var err error + idx, toUpdate, err = m.get(ref) + if err != nil { + return nil, err + } + } else if updated != nil { + var err error + idx, toUpdate, err = m.getByID(updated.Id) + if err != nil { + return nil, err + } + } + + if fieldMask != nil { + for i := range fieldMask.Paths { + switch fieldMask.Paths[i] { + case "permissions": + m.model.Shares[idx].Permissions = updated.Permissions + case "expiration": + m.model.Shares[idx].Expiration = updated.Expiration + default: + return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") + } + } } user := ctxpkg.ContextMustGetUser(ctx) - if !share.IsCreatedByUser(s, user) { + if !share.IsCreatedByUser(toUpdate, user) { return nil, errtypes.NotFound(ref.String()) } now := time.Now().UnixNano() - m.model.Shares[idx].Permissions = p + if p != nil { + m.model.Shares[idx].Permissions = p + } m.model.Shares[idx].Mtime = &typespb.Timestamp{ Seconds: uint64(now / int64(time.Second)), Nanos: uint32(now % int64(time.Second)), diff --git a/pkg/share/manager/jsoncs3/jsoncs3.go b/pkg/share/manager/jsoncs3/jsoncs3.go index 91bcef7f6e..c982da7136 100644 --- a/pkg/share/manager/jsoncs3/jsoncs3.go +++ b/pkg/share/manager/jsoncs3/jsoncs3.go @@ -411,22 +411,47 @@ func (m *Manager) Unshare(ctx context.Context, ref *collaboration.ShareReference } // UpdateShare updates the mode of the given share. -func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { +func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { if err := m.initialize(); err != nil { return nil, err } m.Lock() defer m.Unlock() - s, err := m.get(ctx, ref) - if err != nil { - return nil, err + + var toUpdate *collaboration.Share + + if ref != nil { + var err error + toUpdate, err = m.get(ctx, ref) + if err != nil { + return nil, err + } + } else if updated != nil { + var err error + toUpdate, err = m.getByID(ctx, updated.Id) + if err != nil { + return nil, err + } + } + + if fieldMask != nil { + for i := range fieldMask.Paths { + switch fieldMask.Paths[i] { + case "permissions": + toUpdate.Permissions = updated.Permissions + case "expiration": + toUpdate.Expiration = updated.Expiration + default: + return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") + } + } } user := ctxpkg.ContextMustGetUser(ctx) - if !share.IsCreatedByUser(s, user) { + if !share.IsCreatedByUser(toUpdate, user) { req := &provider.StatRequest{ - Ref: &provider.Reference{ResourceId: s.ResourceId}, + Ref: &provider.Reference{ResourceId: toUpdate.ResourceId}, } res, err := m.gateway.Stat(ctx, req) if err != nil || @@ -436,30 +461,32 @@ func (m *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareRefer } } - s.Permissions = p - s.Mtime = utils.TSNow() + if p != nil { + toUpdate.Permissions = p + } + toUpdate.Mtime = utils.TSNow() // Update provider cache - err = m.Cache.Persist(ctx, s.ResourceId.StorageId, s.ResourceId.SpaceId) + err := m.Cache.Persist(ctx, toUpdate.ResourceId.StorageId, toUpdate.ResourceId.SpaceId) // when persisting fails if _, ok := err.(errtypes.IsPreconditionFailed); ok { // reupdate - s, err = m.get(ctx, ref) // does an implicit sync + toUpdate, err = m.get(ctx, ref) // does an implicit sync if err != nil { return nil, err } - s.Permissions = p - s.Mtime = utils.TSNow() + toUpdate.Permissions = p + toUpdate.Mtime = utils.TSNow() // persist again - err = m.Cache.Persist(ctx, s.ResourceId.StorageId, s.ResourceId.SpaceId) + err = m.Cache.Persist(ctx, toUpdate.ResourceId.StorageId, toUpdate.ResourceId.SpaceId) // TODO try more often? } if err != nil { return nil, err } - return s, nil + return toUpdate, nil } // ListShares returns the shares created by the user diff --git a/pkg/share/manager/jsoncs3/jsoncs3_test.go b/pkg/share/manager/jsoncs3/jsoncs3_test.go index a4ba227477..2cd19f0be8 100644 --- a/pkg/share/manager/jsoncs3/jsoncs3_test.go +++ b/pkg/share/manager/jsoncs3/jsoncs3_test.go @@ -538,7 +538,7 @@ var _ = Describe("Jsoncs3", func() { Permissions: &providerv1beta1.ResourcePermissions{ InitiateFileUpload: true, }, - }) + }, nil, nil) Expect(err).To(HaveOccurred()) }) @@ -560,7 +560,7 @@ var _ = Describe("Jsoncs3", func() { Permissions: &providerv1beta1.ResourcePermissions{ InitiateFileUpload: true, }, - }) + }, nil, nil) Expect(err).ToNot(HaveOccurred()) Expect(us).ToNot(BeNil()) Expect(us.GetPermissions().GetPermissions().InitiateFileUpload).To(BeTrue()) @@ -582,7 +582,7 @@ var _ = Describe("Jsoncs3", func() { Permissions: &providerv1beta1.ResourcePermissions{ InitiateFileUpload: false, }, - }) + }, nil, nil) Expect(err).ToNot(HaveOccurred()) Expect(us).ToNot(BeNil()) Expect(us.GetPermissions().GetPermissions().InitiateFileUpload).To(BeFalse()) @@ -612,7 +612,7 @@ var _ = Describe("Jsoncs3", func() { Permissions: &providerv1beta1.ResourcePermissions{ InitiateFileUpload: true, }, - }) + }, nil, nil) Expect(err).ToNot(HaveOccurred()) Expect(us).ToNot(BeNil()) Expect(us.GetPermissions().GetPermissions().InitiateFileUpload).To(BeTrue()) diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index 5925ed3a06..4a54302c15 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -212,15 +212,40 @@ func sharesEqual(ref *collaboration.ShareReference, s *collaboration.Share) bool return false } -func (m *manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { +func (m *manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { m.lock.Lock() defer m.lock.Unlock() user := ctxpkg.ContextMustGetUser(ctx) + var shareRef *collaboration.ShareReference + if ref != nil { + shareRef = ref + } else if updated != nil { + shareRef = &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: updated.Id, + }, + } + } + for i, s := range m.shares { - if sharesEqual(ref, s) { + if sharesEqual(shareRef, s) { if share.IsCreatedByUser(s, user) { now := time.Now().UnixNano() - m.shares[i].Permissions = p + if p != nil { + m.shares[i].Permissions = p + } + if fieldMask != nil { + for _, path := range fieldMask.Paths { + switch path { + case "permissions": + m.shares[i].Permissions = updated.Permissions + case "expiration": + m.shares[i].Expiration = updated.Expiration + default: + return nil, errtypes.NotSupported("updating " + path + " is not supported") + } + } + } m.shares[i].Mtime = &typespb.Timestamp{ Seconds: uint64(now / 1000000000), Nanos: uint32(now % 1000000000), diff --git a/pkg/share/manager/owncloudsql/owncloudsql.go b/pkg/share/manager/owncloudsql/owncloudsql.go index 2809b9a3fe..edca29181c 100644 --- a/pkg/share/manager/owncloudsql/owncloudsql.go +++ b/pkg/share/manager/owncloudsql/owncloudsql.go @@ -241,7 +241,7 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er return nil } -func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { +func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) { permissions := sharePermToInt(p.Permissions) uid := ctxpkg.ContextMustGetUser(ctx).Username diff --git a/pkg/share/manager/owncloudsql/owncloudsql_test.go b/pkg/share/manager/owncloudsql/owncloudsql_test.go index e51c746294..01c483d08e 100644 --- a/pkg/share/manager/owncloudsql/owncloudsql_test.go +++ b/pkg/share/manager/owncloudsql/owncloudsql_test.go @@ -516,7 +516,7 @@ var _ = Describe("SQL manager", func() { InitiateFileUpload: true, RestoreFileVersion: true, RestoreRecycleItem: true, - }}) + }}, nil, nil) Expect(err).ToNot(HaveOccurred()) Expect(share.Permissions.Permissions.Delete).To(BeFalse()) diff --git a/pkg/share/mocks/Manager.go b/pkg/share/mocks/Manager.go index fd0d9c06d1..7dd3d2c32f 100644 --- a/pkg/share/mocks/Manager.go +++ b/pkg/share/mocks/Manager.go @@ -189,13 +189,13 @@ func (_m *Manager) UpdateReceivedShare(ctx context.Context, _a1 *collaborationv1 return r0, r1 } -// UpdateShare provides a mock function with given fields: ctx, ref, p -func (_m *Manager) UpdateShare(ctx context.Context, ref *collaborationv1beta1.ShareReference, p *collaborationv1beta1.SharePermissions) (*collaborationv1beta1.Share, error) { - ret := _m.Called(ctx, ref, p) +// UpdateShare provides a mock function with given fields: ctx, ref, p, updated, fieldMask +func (_m *Manager) UpdateShare(ctx context.Context, ref *collaborationv1beta1.ShareReference, p *collaborationv1beta1.SharePermissions, updated *collaborationv1beta1.Share, fieldMask *fieldmaskpb.FieldMask) (*collaborationv1beta1.Share, error) { + ret := _m.Called(ctx, ref, p, updated, fieldMask) var r0 *collaborationv1beta1.Share - if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.SharePermissions) *collaborationv1beta1.Share); ok { - r0 = rf(ctx, ref, p) + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.SharePermissions, *collaborationv1beta1.Share, *fieldmaskpb.FieldMask) *collaborationv1beta1.Share); ok { + r0 = rf(ctx, ref, p, updated, fieldMask) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*collaborationv1beta1.Share) @@ -203,8 +203,8 @@ func (_m *Manager) UpdateShare(ctx context.Context, ref *collaborationv1beta1.Sh } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.SharePermissions) error); ok { - r1 = rf(ctx, ref, p) + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.SharePermissions, *collaborationv1beta1.Share, *fieldmaskpb.FieldMask) error); ok { + r1 = rf(ctx, ref, p, updated, fieldMask) } else { r1 = ret.Error(1) } diff --git a/pkg/share/share.go b/pkg/share/share.go index fdb541aa23..67e58ab0d6 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -56,7 +56,7 @@ type Manager interface { Unshare(ctx context.Context, ref *collaboration.ShareReference) error // UpdateShare updates the mode of the given share. - UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) + UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions, updated *collaboration.Share, fieldMask *field_mask.FieldMask) (*collaboration.Share, error) // ListShares returns the shares created by the user. If md is provided is not nil, // it returns only shares attached to the given resource. From 69a8324e332ea8e7e692a7072a7a140e14290ee3 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 10 Jan 2023 11:05:34 +0100 Subject: [PATCH 5/6] enhance capability struct to include share expiration --- changelog/unreleased/share-expiration.md | 5 +++++ internal/http/services/owncloud/ocs/data/capabilities.go | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/share-expiration.md diff --git a/changelog/unreleased/share-expiration.md b/changelog/unreleased/share-expiration.md new file mode 100644 index 0000000000..5163395381 --- /dev/null +++ b/changelog/unreleased/share-expiration.md @@ -0,0 +1,5 @@ +Enhancement: Add expiration to user and group shares + +Added expiration to user and group shares. When shares are accessed after expiration the share is automatically removed. + +https://github.com/cs3org/reva/pull/3594 diff --git a/internal/http/services/owncloud/ocs/data/capabilities.go b/internal/http/services/owncloud/ocs/data/capabilities.go index 4d7a0d733c..7f4a886ae4 100644 --- a/internal/http/services/owncloud/ocs/data/capabilities.go +++ b/internal/http/services/owncloud/ocs/data/capabilities.go @@ -199,9 +199,10 @@ type CapabilitiesFilesSharingPublicExpireDate struct { // CapabilitiesFilesSharingUser TODO document type CapabilitiesFilesSharingUser struct { - SendMail ocsBool `json:"send_mail" xml:"send_mail" mapstructure:"send_mail"` - ProfilePicture ocsBool `json:"profile_picture" xml:"profile_picture" mapstructure:"profile_picture"` - Settings []*CapabilitiesUserSettings `json:"settings" xml:"settings" mapstructure:"settings"` + SendMail ocsBool `json:"send_mail" xml:"send_mail" mapstructure:"send_mail"` + ProfilePicture ocsBool `json:"profile_picture" xml:"profile_picture" mapstructure:"profile_picture"` + Settings []*CapabilitiesUserSettings `json:"settings" xml:"settings" mapstructure:"settings"` + ExpireDate *CapabilitiesFilesSharingPublicExpireDate `json:"expire_date" xml:"expire_date" mapstructure:"expire_date"` } // CapabilitiesUserSettings holds available user settings service information From 968a7fbe75be72243aa6e9e4ec6064b213d6da72 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 12 Jan 2023 15:05:02 +0100 Subject: [PATCH 6/6] publish event when share expired --- pkg/share/manager/jsoncs3/jsoncs3.go | 137 ++++++++++++++++++++-- pkg/share/manager/jsoncs3/jsoncs3_test.go | 20 ++-- 2 files changed, 138 insertions(+), 19 deletions(-) diff --git a/pkg/share/manager/jsoncs3/jsoncs3.go b/pkg/share/manager/jsoncs3/jsoncs3.go index c982da7136..e8bef7c055 100644 --- a/pkg/share/manager/jsoncs3/jsoncs3.go +++ b/pkg/share/manager/jsoncs3/jsoncs3.go @@ -19,7 +19,12 @@ package jsoncs3 import ( + "bytes" "context" + "crypto/tls" + "crypto/x509" + "io" + "os" "strings" "sync" "time" @@ -38,6 +43,8 @@ import ( "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/events/stream" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/share" "github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/providercache" @@ -48,6 +55,7 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" // nolint:staticcheck // we need the legacy package to convert V1 to V2 messages "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/go-micro/plugins/v4/events/natsjs" ) /* @@ -106,12 +114,21 @@ func init() { } type config struct { - GatewayAddr string `mapstructure:"gateway_addr"` - ProviderAddr string `mapstructure:"provider_addr"` - ServiceUserID string `mapstructure:"service_user_id"` - ServiceUserIdp string `mapstructure:"service_user_idp"` - MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"` - CacheTTL int `mapstructure:"ttl"` + GatewayAddr string `mapstructure:"gateway_addr"` + ProviderAddr string `mapstructure:"provider_addr"` + ServiceUserID string `mapstructure:"service_user_id"` + ServiceUserIdp string `mapstructure:"service_user_idp"` + MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"` + CacheTTL int `mapstructure:"ttl"` + Events EventOptions `mapstructure:"events"` +} + +// EventOptions are the configurable options for events +type EventOptions struct { + NatsAddress string `mapstructure:"natsaddress"` + NatsClusterID string `mapstructure:"natsclusterid"` + TLSInsecure bool `mapstructure:"tlsinsecure"` + TLSRootCACertificate string `mapstructure:"tlsrootcacertificate"` } // Manager implements a share manager using a cs3 storage backend with local caching @@ -128,7 +145,8 @@ type Manager struct { initialized bool - gateway gatewayv1beta1.GatewayAPIClient + gateway gatewayv1beta1.GatewayAPIClient + eventStream events.Stream } // NewDefault returns a new manager instance with default dependencies @@ -149,11 +167,49 @@ func NewDefault(m map[string]interface{}) (share.Manager, error) { return nil, err } - return New(s, gc, c.CacheTTL) + var es events.Stream + if c.Events.NatsAddress != "" { + evtsCfg := c.Events + var ( + rootCAPool *x509.CertPool + tlsConf *tls.Config + ) + if evtsCfg.TLSRootCACertificate != "" { + rootCrtFile, err := os.Open(evtsCfg.TLSRootCACertificate) + if err != nil { + return nil, err + } + + var certBytes bytes.Buffer + if _, err := io.Copy(&certBytes, rootCrtFile); err != nil { + return nil, err + } + + rootCAPool = x509.NewCertPool() + rootCAPool.AppendCertsFromPEM(certBytes.Bytes()) + evtsCfg.TLSInsecure = false + + tlsConf = &tls.Config{ + InsecureSkipVerify: evtsCfg.TLSInsecure, //nolint:gosec + RootCAs: rootCAPool, + } + } + + es, err = stream.Nats( + natsjs.TLSConfig(tlsConf), + natsjs.Address(evtsCfg.NatsAddress), + natsjs.ClusterID(evtsCfg.NatsClusterID), + ) + if err != nil { + return nil, err + } + } + + return New(s, gc, c.CacheTTL, es) } // New returns a new manager instance. -func New(s metadata.Storage, gc gatewayv1beta1.GatewayAPIClient, ttlSeconds int) (*Manager, error) { +func New(s metadata.Storage, gc gatewayv1beta1.GatewayAPIClient, ttlSeconds int, es events.Stream) (*Manager, error) { ttl := time.Duration(ttlSeconds) * time.Second return &Manager{ Cache: providercache.New(s, ttl), @@ -162,6 +218,7 @@ func New(s metadata.Storage, gc gatewayv1beta1.GatewayAPIClient, ttlSeconds int) GroupReceivedCache: sharecache.New(s, "groups", "received.json", ttl), storage: s, gateway: gc, + eventStream: es, }, nil } @@ -366,6 +423,22 @@ func (m *Manager) GetShare(ctx context.Context, ref *collaboration.ShareReferenc if err != nil { return nil, err } + if share.IsExpired(s) { + if err := m.removeShare(ctx, s); err != nil { + log.Error().Err(err). + Msg("failed to unshare expired share") + } + if err := events.Publish(m.eventStream, events.ShareExpired{ + ShareOwner: s.GetOwner(), + ItemID: s.GetResourceId(), + ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), + GranteeUserID: s.GetGrantee().GetUserId(), + GranteeGroupID: s.GetGrantee().GetGroupId(), + }); err != nil { + log.Error().Err(err). + Msg("failed to publish share expired event") + } + } // check if we are the creator or the grantee // TODO allow manager to get shares in a space created by other users user := ctxpkg.ContextMustGetUser(ctx) @@ -535,6 +608,16 @@ func (m *Manager) listSharesByIDs(ctx context.Context, user *userv1beta1.User, f log.Error().Err(err). Msg("failed to unshare expired share") } + if err := events.Publish(m.eventStream, events.ShareExpired{ + ShareOwner: s.GetOwner(), + ItemID: s.GetResourceId(), + ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), + GranteeUserID: s.GetGrantee().GetUserId(), + GranteeGroupID: s.GetGrantee().GetGroupId(), + }); err != nil { + log.Error().Err(err). + Msg("failed to publish share expired event") + } continue } if !share.MatchesFilters(s, filters) { @@ -587,6 +670,16 @@ func (m *Manager) listCreatedShares(ctx context.Context, user *userv1beta1.User, log.Error().Err(err). Msg("failed to unshare expired share") } + if err := events.Publish(m.eventStream, events.ShareExpired{ + ShareOwner: s.GetOwner(), + ItemID: s.GetResourceId(), + ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), + GranteeUserID: s.GetGrantee().GetUserId(), + GranteeGroupID: s.GetGrantee().GetGroupId(), + }); err != nil { + log.Error().Err(err). + Msg("failed to publish share expired event") + } continue } if utils.UserEqual(user.GetId(), s.GetCreator()) { @@ -668,6 +761,16 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati log.Error().Err(err). Msg("failed to unshare expired share") } + if err := events.Publish(m.eventStream, events.ShareExpired{ + ShareOwner: s.GetOwner(), + ItemID: s.GetResourceId(), + ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), + GranteeUserID: s.GetGrantee().GetUserId(), + GranteeGroupID: s.GetGrantee().GetGroupId(), + }); err != nil { + log.Error().Err(err). + Msg("failed to publish share expired event") + } continue } @@ -725,6 +828,22 @@ func (m *Manager) getReceived(ctx context.Context, ref *collaboration.ShareRefer if !share.IsGrantedToUser(s, user) { return nil, errtypes.NotFound(ref.String()) } + if share.IsExpired(s) { + if err := m.removeShare(ctx, s); err != nil { + log.Error().Err(err). + Msg("failed to unshare expired share") + } + if err := events.Publish(m.eventStream, events.ShareExpired{ + ShareOwner: s.GetOwner(), + ItemID: s.GetResourceId(), + ExpiredAt: time.Unix(int64(s.GetExpiration().GetSeconds()), int64(s.GetExpiration().GetNanos())), + GranteeUserID: s.GetGrantee().GetUserId(), + GranteeGroupID: s.GetGrantee().GetGroupId(), + }); err != nil { + log.Error().Err(err). + Msg("failed to publish share expired event") + } + } return m.convert(ctx, user.Id.GetOpaqueId(), s), nil } diff --git a/pkg/share/manager/jsoncs3/jsoncs3_test.go b/pkg/share/manager/jsoncs3/jsoncs3_test.go index 2cd19f0be8..8806dde1d9 100644 --- a/pkg/share/manager/jsoncs3/jsoncs3_test.go +++ b/pkg/share/manager/jsoncs3/jsoncs3_test.go @@ -155,7 +155,7 @@ var _ = Describe("Jsoncs3", func() { Expect(err).ToNot(HaveOccurred()) client = &mocks.GatewayAPIClient{} - m, err = jsoncs3.New(storage, client, 0) + m, err = jsoncs3.New(storage, client, 0, nil) Expect(err).ToNot(HaveOccurred()) }) @@ -250,7 +250,7 @@ var _ = Describe("Jsoncs3", func() { }) Expect(s).ToNot(BeNil()) - m, err = jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err = jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) s = shareBykey(&collaboration.ShareKey{ @@ -444,7 +444,7 @@ var _ = Describe("Jsoncs3", func() { }) It("loads the cache when it doesn't have an entry", func() { - m, err := jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err := jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) s, err := m.GetShare(ctx, shareRef) @@ -504,7 +504,7 @@ var _ = Describe("Jsoncs3", func() { }) Expect(err).ToNot(HaveOccurred()) - m, err = jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err = jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) s, err := m.GetShare(ctx, &collaboration.ShareReference{ @@ -617,7 +617,7 @@ var _ = Describe("Jsoncs3", func() { Expect(us).ToNot(BeNil()) Expect(us.GetPermissions().GetPermissions().InitiateFileUpload).To(BeTrue()) - m, err = jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err = jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) s = shareBykey(&collaboration.ShareKey{ @@ -748,7 +748,7 @@ var _ = Describe("Jsoncs3", func() { }) It("syncronizes the user received cache before listing", func() { - m, err := jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err := jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) received, err := m.ListReceivedShares(granteeCtx, []*collaboration.Filter{}) @@ -816,7 +816,7 @@ var _ = Describe("Jsoncs3", func() { }) It("syncronizes the group received cache before listing", func() { - m, err := jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err := jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) received, err := m.ListReceivedShares(granteeCtx, []*collaboration.Filter{}) @@ -860,7 +860,7 @@ var _ = Describe("Jsoncs3", func() { }) It("syncs the cache", func() { - m, err := jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err := jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) rs, err := m.GetReceivedShare(granteeCtx, &collaboration.ShareReference{ @@ -894,7 +894,7 @@ var _ = Describe("Jsoncs3", func() { }) It("syncs the cache", func() { - m, err := jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err := jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) rs, err := m.GetReceivedShare(granteeCtx, &collaboration.ShareReference{ @@ -1017,7 +1017,7 @@ var _ = Describe("Jsoncs3", func() { Expect(err).ToNot(HaveOccurred()) Expect(rs.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - m, err := jsoncs3.New(storage, nil, 0) // Reset in-memory cache + m, err := jsoncs3.New(storage, nil, 0, nil) // Reset in-memory cache Expect(err).ToNot(HaveOccurred()) rs, err = m.GetReceivedShare(granteeCtx, &collaboration.ShareReference{