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

Locking fixes #2625

Merged
merged 19 commits into from
Mar 28, 2022
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
12 changes: 12 additions & 0 deletions changelog/unreleased/fix-locking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Bugfix: Fix locking on publik links and the decomposed filesystem

We've fixed the behavior of locking on the decomposed filesystem, so that now
app based locks can be modified user independently (needed for WOPI integration).
Also we added a check, if a lock is already expired and if so, we lazily delete the lock.
The InitiateUploadRequest now adds the Lock to the upload metadata so that an upload to an
locked file is possible.

We'v added the locking api requests to the public link scope checks, so that locking
also can be used on public links (needed for WOPI integration).

https://github.com/cs3org/reva/pull/2625
20 changes: 15 additions & 5 deletions internal/grpc/interceptors/auth/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,24 @@ func extractRef(req interface{}, hasEditorRole bool) (*provider.Reference, bool)
return v.GetRef(), true
case *provider.InitiateFileDownloadRequest:
return v.GetRef(), true
case *appprovider.OpenInAppRequest:
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
case *gateway.OpenInAppRequest:

// Locking
case *provider.GetLockRequest:
return v.GetRef(), true
case *provider.SetLockRequest:
return v.GetRef(), true
case *provider.RefreshLockRequest:
return v.GetRef(), true
case *provider.UnlockRequest:
return v.GetRef(), true

// App provider requests
// App provider requests
case *appregistry.GetAppProvidersRequest:
return &provider.Reference{ResourceId: v.ResourceInfo.Id}, true
return &provider.Reference{ResourceId: v.ResourceInfo.Id, Path: v.ResourceInfo.Path}, true
case *appprovider.OpenInAppRequest:
return &provider.Reference{ResourceId: v.ResourceInfo.Id, Path: v.ResourceInfo.Path}, true
case *gateway.OpenInAppRequest:
return v.GetRef(), true
}

if !hasEditorRole {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,22 +120,58 @@ func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.Unse

// SetLock puts a lock on the given reference
func (s *service) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provider.SetLockResponse, error) {
return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented")
ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref)
switch {
case err != nil:
return nil, err
case st != nil:
return &provider.SetLockResponse{
Status: st,
}, nil
}
return s.gateway.SetLock(ctx, &provider.SetLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock})
}

// GetLock returns an existing lock on the given reference
func (s *service) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) {
return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented")
ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref)
switch {
case err != nil:
return nil, err
case st != nil:
return &provider.GetLockResponse{
Status: st,
}, nil
}
return s.gateway.GetLock(ctx, &provider.GetLockRequest{Opaque: req.Opaque, Ref: ref})
}

// RefreshLock refreshes an existing lock on the given reference
func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) {
return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented")
ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref)
switch {
case err != nil:
return nil, err
case st != nil:
return &provider.RefreshLockResponse{
Status: st,
}, nil
}
return s.gateway.RefreshLock(ctx, &provider.RefreshLockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock})
}

// Unlock removes an existing lock from the given reference
func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) {
return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented")
ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref)
switch {
case err != nil:
return nil, err
case st != nil:
return &provider.UnlockResponse{
Status: st,
}, nil
}
return s.gateway.Unlock(ctx, &provider.UnlockRequest{Opaque: req.Opaque, Ref: ref, Lock: req.Lock})
}

func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) {
Expand Down
1 change: 1 addition & 0 deletions internal/http/services/appprovider/appprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ func (s *svc) handleOpen(w http.ResponseWriter, r *http.Request) {

fileRef := &provider.Reference{
ResourceId: resourceID,
Path: ".",
}

statRes, err := client.Stat(ctx, &provider.StatRequest{Ref: fileRef})
Expand Down
3 changes: 3 additions & 0 deletions internal/http/services/owncloud/ocdav/locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,13 @@ func (cls *cs3LS) Refresh(ctx context.Context, now time.Time, token string, dura
return LockDetails{}, errors.ErrNotImplemented
}
func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Reference, token string) error {
u := ctxpkg.ContextMustGetUser(ctx)

r := &provider.UnlockRequest{
Ref: ref,
Lock: &provider.Lock{
LockId: token, // can be a token or a Coded-URL
User: u.Id,
},
}
res, err := cls.client.Unlock(ctx, r)
Expand Down
10 changes: 10 additions & 0 deletions pkg/auth/scope/publicshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interfa
return checkStorageRef(ctx, &share, &provider.Reference{ResourceId: v.GetResourceId()}), nil
case *provider.StatRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.GetLockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.UnlockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.RefreshLockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.SetLockRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.ListContainerRequest:
return checkStorageRef(ctx, &share, v.GetRef()), nil
case *provider.InitiateFileDownloadRequest:
Expand Down Expand Up @@ -109,6 +117,8 @@ func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interfa
case *appregistry.GetDefaultAppProviderForMimeTypeRequest:
return true, nil

case *appregistry.GetAppProvidersRequest:
return true, nil
case *userv1beta1.GetUserByClaimRequest:
return true, nil
case *userv1beta1.GetUserRequest:
Expand Down
1 change: 0 additions & 1 deletion pkg/rhttp/datatx/manager/spaces/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ func New(m map[string]interface{}) (datatx.DataTX, error) {
func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

var spaceID string
spaceID, r.URL.Path = router.ShiftPath(r.URL.Path)

Expand Down
59 changes: 48 additions & 11 deletions pkg/storage/utils/decomposedfs/node/locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"io/fs"
"os"
"path/filepath"
"time"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
Expand Down Expand Up @@ -121,7 +122,17 @@ func (n Node) ReadLock(ctx context.Context) (*provider.Lock, error) {
appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not decode lock file, ignoring")
return nil, errors.Wrap(err, "Decomposedfs: could not read lock file")
}
return lock, err

// lock already expired
if lock.Expiration != nil && time.Now().After(time.Unix(int64(lock.Expiration.Seconds), int64(lock.Expiration.Nanos))) {
if err = os.Remove(f.Name()); err != nil {
return nil, errors.Wrap(err, "Decomposedfs: could not remove expired lock file")
wkloucek marked this conversation as resolved.
Show resolved Hide resolved
}
// we successfully deleted the expired lock
return nil, errtypes.NotFound("no lock found")
}

return lock, nil
}

// RefreshLock refreshes the node's lock
Expand Down Expand Up @@ -165,13 +176,8 @@ func (n *Node) RefreshLock(ctx context.Context, lock *provider.Lock) error {
return errtypes.PreconditionFailed("mismatching lock")
}

u := ctxpkg.ContextMustGetUser(ctx)
if !utils.UserEqual(oldLock.User, u.Id) {
return errtypes.PermissionDenied("cannot refresh lock of another holder")
}

if !utils.UserEqual(oldLock.User, lock.GetUser()) {
return errtypes.PermissionDenied("cannot change holder when refreshing a lock")
if ok, err := isLockModificationAllowed(ctx, oldLock, lock); !ok {
return err
}

wkloucek marked this conversation as resolved.
Show resolved Hide resolved
if err := json.NewEncoder(f).Encode(lock); err != nil {
Expand Down Expand Up @@ -222,9 +228,8 @@ func (n *Node) Unlock(ctx context.Context, lock *provider.Lock) error {
return errtypes.Locked(oldLock.LockId)
}

u := ctxpkg.ContextMustGetUser(ctx)
if !utils.UserEqual(oldLock.User, u.Id) {
return errtypes.PermissionDenied("mismatching holder")
if ok, err := isLockModificationAllowed(ctx, oldLock, lock); !ok {
return err
}

if err = os.Remove(f.Name()); err != nil {
Expand Down Expand Up @@ -307,3 +312,35 @@ func (n *Node) hasLocks(ctx context.Context) bool {
_, err := os.Stat(n.LockFilePath()) // FIXME better error checking
return err == nil
}

func isLockModificationAllowed(ctx context.Context, oldLock *provider.Lock, newLock *provider.Lock) (bool, error) {
if oldLock.Type == provider.LockType_LOCK_TYPE_SHARED {
return true, nil
}

appNameEquals := oldLock.AppName == newLock.AppName
if !appNameEquals {
return false, errtypes.PermissionDenied("app names of the locks are mismatching")
}

var lockUserEquals, contextUserEquals bool
if oldLock.User == nil && newLock.GetUser() == nil {
// no user lock set
lockUserEquals = true
contextUserEquals = true
} else {
lockUserEquals = utils.UserEqual(oldLock.User, newLock.GetUser())
if !lockUserEquals {
return false, errtypes.PermissionDenied("users of the locks are mismatching")
}

u := ctxpkg.ContextMustGetUser(ctx)
contextUserEquals = utils.UserEqual(oldLock.User, u.Id)
if !contextUserEquals {
return false, errtypes.PermissionDenied("lock holder and current user are mismatching")
}
}

wkloucek marked this conversation as resolved.
Show resolved Hide resolved
return appNameEquals && lockUserEquals && contextUserEquals, nil

}
Loading