diff --git a/changelog/unreleased/fix-public-link-lock.md b/changelog/unreleased/fix-public-link-lock.md new file mode 100644 index 00000000000..42a53432446 --- /dev/null +++ b/changelog/unreleased/fix-public-link-lock.md @@ -0,0 +1,6 @@ +Bugfix: Fix an error when lock/unlock a file + +We fixed a bug when anonymous user with viewer role in public link of a folder can lock/unlock a file inside it + +https://github.com/cs3org/reva/pull/4518 +https://github.com/owncloud/ocis/issues/7785 diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index c125698c0b9..603315a460e 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -33,6 +33,7 @@ import ( 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/appctx" + "github.com/cs3org/reva/v2/pkg/conversions" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/events" @@ -230,6 +231,11 @@ 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) { + if !isPublicShareRoleEditor(ctx) { + return &provider.SetLockResponse{ + Status: status.NewPermissionDenied(ctx, nil, "no permission to lock the share"), + }, nil + } err := s.storage.SetLock(ctx, req.Ref, req.Lock) return &provider.SetLockResponse{ @@ -249,6 +255,12 @@ func (s *service) GetLock(ctx context.Context, req *provider.GetLockRequest) (*p // RefreshLock refreshes an existing lock on the given reference func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) { + if !isPublicShareRoleEditor(ctx) { + return &provider.RefreshLockResponse{ + Status: status.NewPermissionDenied(ctx, nil, "no permission to refresh the share lock"), + }, nil + } + err := s.storage.RefreshLock(ctx, req.Ref, req.Lock, req.ExistingLockId) return &provider.RefreshLockResponse{ @@ -258,6 +270,12 @@ func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequ // Unlock removes an existing lock from the given reference func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) { + if !isPublicShareRoleEditor(ctx) { + return &provider.UnlockResponse{ + Status: status.NewPermissionDenied(ctx, nil, "no permission to unlock the share"), + }, nil + } + err := s.storage.Unlock(ctx, req.Ref, req.Lock) return &provider.UnlockResponse{ @@ -1285,3 +1303,9 @@ func estreamFromConfig(c eventconfig) (events.Stream, error) { return stream.NatsFromConfig("storageprovider", false, stream.NatsConfig(c)) } + +func isPublicShareRoleEditor(ctx context.Context) bool { + u := ctxpkg.ContextMustGetUser(ctx) + psr := utils.ReadPlainFromOpaque(u.Opaque, "public-share-role") + return psr != "" && psr == conversions.RoleEditor +} diff --git a/internal/http/services/owncloud/ocdav/locks.go b/internal/http/services/owncloud/ocdav/locks.go index 332b9d22760..e415e6a62d5 100644 --- a/internal/http/services/owncloud/ocdav/locks.go +++ b/internal/http/services/owncloud/ocdav/locks.go @@ -260,11 +260,15 @@ func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Refer return err } - switch res.Status.Code { + switch res.GetStatus().GetCode() { case rpc.Code_CODE_OK: return nil case rpc.Code_CODE_FAILED_PRECONDITION: return errtypes.Aborted("file is not locked") + case rpc.Code_CODE_LOCKED: + appctx.GetLogger(ctx).Error().Str("token", token).Interface("unlock", ref). + Msg("could not unlock " + res.GetStatus().GetMessage()) + return errtypes.Locked("another token") default: return errtypes.NewErrtypeFromStatus(res.Status) } @@ -639,16 +643,19 @@ func (s *svc) unlockReference(ctx context.Context, _ http.ResponseWriter, r *htt t = t[1 : len(t)-1] } - switch err := s.LockSystem.Unlock(ctx, time.Now(), ref, t); err { - case nil: - return http.StatusNoContent, err - case errors.ErrForbidden: - return http.StatusForbidden, err - case errors.ErrLocked: - return http.StatusLocked, err - case errors.ErrNoSuchLock: - return http.StatusConflict, err - default: - return http.StatusInternalServerError, err + err := s.LockSystem.Unlock(ctx, time.Now(), ref, t) + if err == nil { + return http.StatusNoContent, nil + } else { + switch err.(type) { + case errtypes.Aborted, errtypes.Locked: + return http.StatusLocked, err + case errtypes.PermissionDenied: + return http.StatusForbidden, err + case errtypes.PreconditionFailed: + return http.StatusConflict, err + default: + return http.StatusInternalServerError, err + } } }