Skip to content

Commit

Permalink
Share expiration (#3594)
Browse files Browse the repository at this point in the history
* add an expiration to shares

* add expiration to the share listing

* remove expired shares on access

* implement updating and removing the share expiration

* enhance capability struct to include share expiration

* publish event when share expired
  • Loading branch information
David Christofas authored Jan 12, 2023
1 parent f8c7841 commit e955451
Show file tree
Hide file tree
Showing 23 changed files with 425 additions and 115 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/share-expiration.md
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion internal/grpc/services/gateway/ocmshareprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
7 changes: 4 additions & 3 deletions internal/grpc/services/gateway/usershareprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
9 changes: 9 additions & 0 deletions internal/http/services/owncloud/ocs/conversions/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
7 changes: 4 additions & 3 deletions internal/http/services/owncloud/ocs/data/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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()
Expand Down Expand Up @@ -75,6 +80,26 @@ 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{
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{
Expand All @@ -93,6 +118,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request, statIn
Permissions: &collaboration.SharePermissions{
Permissions: role.CS3ResourcePermissions(),
},
Expiration: expirationTs,
},
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/cbox/share/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion pkg/share/manager/cs3/cs3.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/share/manager/cs3/cs3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
42 changes: 36 additions & 6 deletions pkg/share/manager/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
Loading

0 comments on commit e955451

Please sign in to comment.