Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow OCM Locking #4990

Merged
merged 7 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading