Skip to content

Commit

Permalink
Merge pull request #4990 from kobergj/OCMLocking
Browse files Browse the repository at this point in the history
Allow OCM Locking
  • Loading branch information
kobergj authored Jan 7, 2025
2 parents 759fd1a + d357f5d commit bd76241
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 27 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/ocm-locking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Allow locking via ocm

Implement locking endpoints so files can be locked and unlocked via ocm.

https://github.com/cs3org/reva/pull/4990
2 changes: 1 addition & 1 deletion cmd/reva/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func checkUploadWebdavRef(protocols []*gateway.FileUploadProtocol, md os.FileInf
c.SetHeader(ctxpkg.TokenHeader, token)
c.SetHeader("Upload-Length", strconv.FormatInt(md.Size(), 10))

if err = c.WriteStream(filePath, fd, 0700); err != nil {
if err = c.WriteStream(filePath, fd, 0700, ""); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20241122070217-29b328c136b1
replace github.com/studio-b12/gowebdav => github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202

// exclude the v2 line of go-sqlite3 which was released accidentally and prevents pulling in newer versions of go-sqlite3
// see https://github.com/mattn/go-sqlite3/issues/965 for more details
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxg
github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/aduffeck/gowebdav v0.0.0-20241122070217-29b328c136b1 h1:FAoQBuRdMyGNkp5Mg7HLkaao10BEViEPJNg+5cnW7xk=
github.com/aduffeck/gowebdav v0.0.0-20241122070217-29b328c136b1/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
Expand Down Expand Up @@ -494,6 +492,8 @@ github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202 h1:A1xJ2NKgiYFiaHiLl9B5yw/gUBACSs9crDykTS3GuQI=
github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate
uReq := &provider.InitiateFileUploadRequest{
Ref: cs3Ref,
Opaque: req.Opaque,
LockId: req.LockId,
}

gatewayClient, err := s.gatewaySelector.Next()
Expand Down
74 changes: 60 additions & 14 deletions internal/http/services/owncloud/ocdav/locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type lockInfo struct {
Shared *struct{} `xml:"lockscope>shared"`
Write *struct{} `xml:"locktype>write"`
Owner owner `xml:"owner"`
LockID string `xml:"locktoken>href"`
}

// http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner
Expand Down Expand Up @@ -144,7 +145,7 @@ type LockSystem interface {
//
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
// when to use each error.
Refresh(ctx context.Context, now time.Time, token string, duration time.Duration) (LockDetails, error)
Refresh(ctx context.Context, now time.Time, ref *provider.Reference, token string) error

// Unlock unlocks the lock with the given token.
//
Expand Down Expand Up @@ -184,28 +185,32 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails
}
*/

// Having a lock token provides no special access rights. Anyone can find out anyone
// else's lock token by performing lock discovery. Locks must be enforced based upon
// whatever authentication mechanism is used by the server, not based on the secrecy
// of the token values.
// see: http://www.webdav.org/specs/rfc2518.html#n-lock-tokens
token := uuid.New()

u := ctxpkg.ContextMustGetUser(ctx)

// add metadata via opaque
// TODO: upate cs3api: https://github.com/cs3org/cs3apis/issues/213
o := utils.AppendPlainToOpaque(nil, "lockownername", u.GetDisplayName())
o = utils.AppendPlainToOpaque(o, "locktime", now.Format(time.RFC3339))

lockid := details.LockID
if lockid == "" {
// Having a lock token provides no special access rights. Anyone can find out anyone
// else's lock token by performing lock discovery. Locks must be enforced based upon
// whatever authentication mechanism is used by the server, not based on the secrecy
// of the token values.
// see: http://www.webdav.org/specs/rfc2518.html#n-lock-tokens
token := uuid.New()

lockid = lockTokenPrefix + token.String()
}
r := &provider.SetLockRequest{
Ref: details.Root,
Lock: &provider.Lock{
Opaque: o,
Type: provider.LockType_LOCK_TYPE_EXCL,
User: details.UserID, // no way to set an app lock? TODO maybe via the ownerxml
//AppName: , // TODO use a urn scheme?
LockId: lockTokenPrefix + token.String(), // can be a token or a Coded-URL
LockId: lockid,
},
}
if details.Duration > 0 {
Expand All @@ -227,15 +232,52 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails
}
switch res.GetStatus().GetCode() {
case rpc.Code_CODE_OK:
return lockTokenPrefix + token.String(), nil
return lockid, nil
default:
return "", ocdavErrors.NewErrFromStatus(res.GetStatus())
}

}

func (cls *cs3LS) Refresh(ctx context.Context, now time.Time, token string, duration time.Duration) (LockDetails, error) {
return LockDetails{}, ocdavErrors.ErrNotImplemented
func (cls *cs3LS) Refresh(ctx context.Context, now time.Time, ref *provider.Reference, token string) error {
u := ctxpkg.ContextMustGetUser(ctx)

// add metadata via opaque
// TODO: upate cs3api: https://github.com/cs3org/cs3apis/issues/213
o := utils.AppendPlainToOpaque(nil, "lockownername", u.GetDisplayName())
o = utils.AppendPlainToOpaque(o, "locktime", now.Format(time.RFC3339))

if token == "" {
return errors.New("token is empty")
}

r := &provider.RefreshLockRequest{
Ref: ref,
Lock: &provider.Lock{
Opaque: o,
Type: provider.LockType_LOCK_TYPE_EXCL,
//AppName: , // TODO use a urn scheme?
LockId: token,
User: u.GetId(),
},
}

client, err := cls.selector.Next()
if err != nil {
return err
}

res, err := client.RefreshLock(ctx, r)
if err != nil {
return err
}
switch res.GetStatus().GetCode() {
case rpc.Code_CODE_OK:
return nil

default:
return ocdavErrors.NewErrFromStatus(res.GetStatus())
}
}

func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Reference, token string) error {
Expand Down Expand Up @@ -287,6 +329,8 @@ type LockDetails struct {
OwnerName string
// Locktime is the time the lock was created
Locktime time.Time
// LockID is the lock token
LockID string
}

func readLockInfo(r io.Reader) (li lockInfo, status int, err error) {
Expand Down Expand Up @@ -450,7 +494,7 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.

u := ctxpkg.ContextMustGetUser(ctx)
token, now, created := "", time.Now(), false
ld := LockDetails{UserID: u.Id, Root: ref, Duration: duration, OwnerName: u.GetDisplayName(), Locktime: now}
ld := LockDetails{UserID: u.Id, Root: ref, Duration: duration, OwnerName: u.GetDisplayName(), Locktime: now, LockID: li.LockID}
if li == (lockInfo{}) {
// An empty lockInfo means to refresh the lock.
ih, ok := parseIfHeader(r.Header.Get(net.HeaderIf))
Expand All @@ -463,14 +507,16 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
if token == "" {
return http.StatusBadRequest, ocdavErrors.ErrInvalidLockToken
}
ld, err = s.LockSystem.Refresh(ctx, now, token, duration)
err = s.LockSystem.Refresh(ctx, now, ref, token)
if err != nil {
if err == ocdavErrors.ErrNoSuchLock {
return http.StatusPreconditionFailed, err
}
return http.StatusInternalServerError, err
}

ld.LockID = token

} else {
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
// then the request MUST act as if a "Depth:infinity" had been submitted."
Expand Down
35 changes: 31 additions & 4 deletions pkg/ocm/storage/received/ocm.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,20 +474,47 @@ func (d *driver) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer
return errtypes.NotSupported("operation not supported")
}

// SetLock sets a lock on a file
func (d *driver) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
return errtypes.NotSupported("operation not supported")
client, _, rel, err := d.webdavClient(ctx, nil, ref)
if err != nil {
return err
}

return client.Lock(rel, lock.GetLockId())
}

func (d *driver) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) {
return nil, errtypes.NotSupported("operation not supported")
client, _, rel, err := d.webdavClient(ctx, nil, ref)
if err != nil {
return nil, err
}

token, err := client.GetLock(rel)
if err != nil {
return nil, err
}

return &provider.Lock{LockId: token, Type: provider.LockType_LOCK_TYPE_EXCL}, nil
}

func (d *driver) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error {
return errtypes.NotSupported("operation not supported")
client, _, rel, err := d.webdavClient(ctx, nil, ref)
if err != nil {
return err
}

return client.RefreshLock(rel, lock.GetLockId())
}

// Unlock removes a lock from a file
func (d *driver) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
return errtypes.NotSupported("operation not supported")
client, _, rel, err := d.webdavClient(ctx, nil, ref)
if err != nil {
return err
}

return client.Unlock(rel, lock.GetLockId())
}

func (d *driver) ListStorageSpaces(ctx context.Context, filters []*provider.ListStorageSpacesRequest_Filter, _ bool) ([]*provider.StorageSpace, error) {
Expand Down
5 changes: 3 additions & 2 deletions pkg/ocm/storage/received/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ func (d *driver) Upload(ctx context.Context, req storage.UploadRequest, _ storag
}
})

return &provider.ResourceInfo{}, client.WriteStream(rel, req.Body, 0)
locktoken, _ := ctxpkg.ContextGetLockID(ctx)
return &provider.ResourceInfo{}, client.WriteStream(rel, req.Body, 0, locktoken)
}

// UseIn tells the tus upload middleware which extensions it supports.
Expand Down Expand Up @@ -356,7 +357,7 @@ func (u *upload) FinishUpload(ctx context.Context) error {
return err
}
defer f.Close()
return client.WriteStream(rel, f, 0)
return client.WriteStream(rel, f, 0, "")
}

func (u *upload) Terminate(ctx context.Context) error {
Expand Down
5 changes: 5 additions & 0 deletions pkg/rhttp/datatx/manager/simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -109,6 +110,10 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {

ref := &provider.Reference{Path: fn}

if lockID := r.Header.Get("X-Lock-Id"); lockID != "" {
ctx = ctxpkg.ContextSetLockID(ctx, lockID)
}

info, err := fs.Upload(ctx, storage.UploadRequest{
Ref: ref,
Body: r.Body,
Expand Down
2 changes: 1 addition & 1 deletion pkg/sdk/common/net/webdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (webdav *WebDAVClient) Read(file string) ([]byte, error) {
func (webdav *WebDAVClient) Write(file string, data io.Reader, size int64) error {
webdav.client.SetHeader("Upload-Length", strconv.FormatInt(size, 10))

if err := webdav.client.WriteStream(file, data, 0700); err != nil {
if err := webdav.client.WriteStream(file, data, 0700, ""); err != nil {
return fmt.Errorf("unable to write the data: %v", err)
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/storagespace/storagespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ func SplitStorageID(sid string) (storageID, spaceID string) {
// The result format will look like:
// <storageid>$<spaceid>!<opaqueid>
func FormatResourceID(sid *provider.ResourceId) string {
if sid.OpaqueId == "" {
return FormatStorageID(sid.StorageId, sid.SpaceId)
if sid.GetOpaqueId() == "" {
return FormatStorageID(sid.GetStorageId(), sid.GetSpaceId())
}
return strings.Join([]string{FormatStorageID(sid.StorageId, sid.SpaceId), sid.OpaqueId}, _idDelimiter)
}
Expand Down

0 comments on commit bd76241

Please sign in to comment.