[full-ci] [reva bump] fix an error when lock/unlock a file

This commit is contained in:
Roman Perekhod
2024-03-05 16:36:26 +01:00
parent e33d551579
commit 6fb545b3c1
10 changed files with 154 additions and 68 deletions

View File

@@ -1057,11 +1057,11 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere
return lsRes.Share, lsRes.Status, nil
}
// we currently need to list all shares and match the path if the request is relative to the share jail root
// we currently need to list all accepted shares and match the path if the
// request is relative to the share jail root. Also we need to Stat() the
// shared resource's id to check whether that still exist. There might be
// old shares using the same path but for an already vanished resource id.
if ref.ResourceId.OpaqueId == utils.ShareStorageProviderID && ref.Path != "." {
// we need to list accepted shares and match the path
// look up share for this resourceid
lsRes, err := sharingCollaborationClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{
Filters: []*collaboration.Filter{
{
@@ -1080,15 +1080,32 @@ func (s *service) resolveAcceptedShare(ctx context.Context, ref *provider.Refere
return nil, lsRes.Status, nil
}
for _, receivedShare := range lsRes.Shares {
// make sure to skip unaccepted shares
if receivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
continue
}
if isMountPointForPath(receivedShare.MountPoint.Path, ref.Path) {
// Only return this share if the resource still exists.
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return nil, nil, err
}
sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{
Ref: &provider.Reference{ResourceId: receivedShare.GetShare().GetResourceId()},
})
if err != nil {
appctx.GetLogger(ctx).Debug().
Err(err).
Interface("resourceID", receivedShare.GetShare().GetResourceId()).
Msg("resolveAcceptedShare: failed to stat shared resource")
continue
}
if sRes.Status.Code != rpc.Code_CODE_OK {
appctx.GetLogger(ctx).Debug().
Interface("resourceID", receivedShare.GetShare().GetResourceId()).
Interface("status", sRes.Status).
Msg("resolveAcceptedShare: failed to stat shared resource")
continue
}
return receivedShare, lsRes.Status, nil
}
}
}
return nil, status.NewNotFound(ctx, "sharesstorageprovider: not found "+ref.String()), nil

View File

@@ -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 !canLockPublicShare(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 !canLockPublicShare(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 !canLockPublicShare(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 canLockPublicShare(ctx context.Context) bool {
u := ctxpkg.ContextMustGetUser(ctx)
psr := utils.ReadPlainFromOpaque(u.Opaque, "public-share-role")
return psr == "" || psr == conversions.RoleEditor
}

View File

@@ -21,6 +21,7 @@ package errors
import (
"bytes"
"encoding/xml"
"fmt"
"net/http"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
@@ -193,3 +194,20 @@ func HandleWebdavError(log *zerolog.Logger, w http.ResponseWriter, b []byte, err
log.Err(err).Msg("error writing response")
}
}
func NewErrFromStatus(s *rpc.Status) error {
switch s.GetCode() {
case rpc.Code_CODE_OK:
return nil
case rpc.Code_CODE_DEADLINE_EXCEEDED:
return ErrInvalidTimeout
case rpc.Code_CODE_PERMISSION_DENIED:
return ErrForbidden
case rpc.Code_CODE_LOCKED, rpc.Code_CODE_FAILED_PRECONDITION:
return ErrLocked
case rpc.Code_CODE_UNIMPLEMENTED:
return ErrNotImplemented
default:
return fmt.Errorf(s.GetMessage())
}
}

View File

@@ -21,6 +21,7 @@ package ocdav
import (
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
@@ -34,13 +35,12 @@ import (
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
ocdavErrors "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup"
"github.com/cs3org/reva/v2/pkg/appctx"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/google/uuid"
@@ -172,7 +172,7 @@ type cs3LS struct {
}
func (cls *cs3LS) Confirm(ctx context.Context, now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
return nil, errors.ErrNotImplemented
return nil, ocdavErrors.ErrNotImplemented
}
func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails) (string, error) {
@@ -180,7 +180,7 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails
/*
if !details.ZeroDepth {
The CS3 Lock api currently has no depth property, it only locks single resources
return "", errors.ErrUnsupportedLockInfo
return "", ocdavErrors.ErrUnsupportedLockInfo
}
*/
@@ -225,20 +225,19 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails
if err != nil {
return "", err
}
switch res.Status.Code {
switch res.GetStatus().GetCode() {
case rpc.Code_CODE_OK:
return lockTokenPrefix + token.String(), nil
case rpc.Code_CODE_FAILED_PRECONDITION:
return "", errtypes.Aborted("file is already locked")
default:
return "", errtypes.NewErrtypeFromStatus(res.Status)
return "", ocdavErrors.NewErrFromStatus(res.GetStatus())
}
}
func (cls *cs3LS) Refresh(ctx context.Context, now time.Time, token string, duration time.Duration) (LockDetails, error) {
return LockDetails{}, errors.ErrNotImplemented
return LockDetails{}, ocdavErrors.ErrNotImplemented
}
func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Reference, token string) error {
u := ctxpkg.ContextMustGetUser(ctx)
@@ -260,14 +259,11 @@ func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Refer
return err
}
switch res.Status.Code {
case rpc.Code_CODE_OK:
return nil
case rpc.Code_CODE_FAILED_PRECONDITION:
return errtypes.Aborted("file is not locked")
default:
return errtypes.NewErrtypeFromStatus(res.Status)
newErr := ocdavErrors.NewErrFromStatus(res.GetStatus())
if newErr != nil {
appctx.GetLogger(ctx).Error().Str("token", token).Interface("unlock", ref).Msg("could not unlock " + res.GetStatus().GetMessage())
}
return newErr
}
// LockDetails are a lock's metadata.
@@ -302,7 +298,7 @@ func readLockInfo(r io.Reader) (li lockInfo, status int, err error) {
// http://www.webdav.org/specs/rfc4918.html#refreshing-locks
return lockInfo{}, 0, nil
}
err = errors.ErrInvalidLockInfo
err = ocdavErrors.ErrInvalidLockInfo
}
return lockInfo{}, http.StatusBadRequest, err
}
@@ -349,15 +345,15 @@ func parseTimeout(s string) (time.Duration, error) {
}
const pre = "Second-"
if !strings.HasPrefix(s, pre) {
return 0, errors.ErrInvalidTimeout
return 0, ocdavErrors.ErrInvalidTimeout
}
s = s[len(pre):]
if s == "" || s[0] < '0' || '9' < s[0] {
return 0, errors.ErrInvalidTimeout
return 0, ocdavErrors.ErrInvalidTimeout
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil || 1<<32-1 < n {
return 0, errors.ErrInvalidTimeout
return 0, ocdavErrors.ErrInvalidTimeout
}
return time.Duration(n) * time.Second, nil
}
@@ -420,7 +416,7 @@ func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) (ret
return http.StatusInternalServerError, err
}
if cs3Status.Code != rpc.Code_CODE_OK {
return http.StatusInternalServerError, errtypes.NewErrtypeFromStatus(cs3Status)
return http.StatusInternalServerError, ocdavErrors.NewErrFromStatus(cs3Status)
}
return s.lockReference(ctx, w, r, ref)
@@ -444,12 +440,12 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
sublog := appctx.GetLogger(ctx).With().Interface("ref", ref).Logger()
duration, err := parseTimeout(r.Header.Get(net.HeaderTimeout))
if err != nil {
return http.StatusBadRequest, errors.ErrInvalidTimeout
return http.StatusBadRequest, ocdavErrors.ErrInvalidTimeout
}
li, status, err := readLockInfo(r.Body)
if err != nil {
return status, errors.ErrInvalidLockInfo
return status, ocdavErrors.ErrInvalidLockInfo
}
u := ctxpkg.ContextMustGetUser(ctx)
@@ -459,17 +455,17 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
// An empty lockInfo means to refresh the lock.
ih, ok := parseIfHeader(r.Header.Get(net.HeaderIf))
if !ok {
return http.StatusBadRequest, errors.ErrInvalidIfHeader
return http.StatusBadRequest, ocdavErrors.ErrInvalidIfHeader
}
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
token = ih.lists[0].conditions[0].Token
}
if token == "" {
return http.StatusBadRequest, errors.ErrInvalidLockToken
return http.StatusBadRequest, ocdavErrors.ErrInvalidLockToken
}
ld, err = s.LockSystem.Refresh(ctx, now, token, duration)
if err != nil {
if err == errors.ErrNoSuchLock {
if err == ocdavErrors.ErrNoSuchLock {
return http.StatusPreconditionFailed, err
}
return http.StatusInternalServerError, err
@@ -484,7 +480,7 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
if depth != 0 && depth != infiniteDepth {
// Section 9.10.3 says that "Values other than 0 or infinity must not be
// used with the Depth header on a LOCK method".
return http.StatusBadRequest, errors.ErrInvalidDepth
return http.StatusBadRequest, ocdavErrors.ErrInvalidDepth
}
}
/* our url path has been shifted, so we don't need to do this?
@@ -505,15 +501,16 @@ func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.
// should we do that in the decomposedfs as well? the node does not exist
// this actually is a name based lock ... ugh
token, err = s.LockSystem.Create(ctx, now, ld)
//
if err != nil {
switch err.(type) {
case errtypes.Aborted:
switch {
case errors.Is(err, ocdavErrors.ErrLocked):
return http.StatusLocked, err
case errtypes.PermissionDenied:
case errors.Is(err, ocdavErrors.ErrForbidden):
return http.StatusForbidden, err
default:
return http.StatusInternalServerError, err
}
}
@@ -611,7 +608,7 @@ func (s *svc) handleUnlock(w http.ResponseWriter, r *http.Request, ns string) (s
return http.StatusInternalServerError, err
}
if cs3Status.Code != rpc.Code_CODE_OK {
return http.StatusInternalServerError, errtypes.NewErrtypeFromStatus(cs3Status)
return http.StatusInternalServerError, ocdavErrors.NewErrFromStatus(cs3Status)
}
return s.unlockReference(ctx, w, r, ref)
@@ -639,16 +636,14 @@ 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:
err := s.LockSystem.Unlock(ctx, time.Now(), ref, t)
switch {
case err == nil:
return http.StatusNoContent, nil
case errors.Is(err, ocdavErrors.ErrLocked):
return http.StatusLocked, err
case errors.ErrNoSuchLock:
return http.StatusConflict, err
default:
return http.StatusInternalServerError, err
case errors.Is(err, ocdavErrors.ErrForbidden):
return http.StatusForbidden, err
}
return http.StatusInternalServerError, err
}

View File

@@ -295,12 +295,13 @@ func NewErrtypeFromStatus(status *rpc.Status) error {
case rpc.Code_CODE_UNIMPLEMENTED:
return NotSupported(status.Message)
case rpc.Code_CODE_PERMISSION_DENIED:
// FIXME add locked status!
if strings.HasPrefix(status.Message, "set lock: error: locked by ") {
return Locked(strings.TrimPrefix(status.Message, "set lock: error: locked by "))
}
return PermissionDenied(status.Message)
case rpc.Code_CODE_LOCKED:
// FIXME make something better for that
msg := strings.Split(status.Message, "error: locked by ")
if len(msg) > 1 {
return Locked(msg[len(msg)-1])
}
return Locked(status.Message)
// case rpc.Code_CODE_DATA_LOSS: ?
// IsPartialContent
@@ -350,3 +351,37 @@ func NewErrtypeFromHTTPStatusCode(code int, message string) error {
return InternalError(message)
}
}
// NewHTTPStatusCodeFromErrtype maps an errtype to an http status
func NewHTTPStatusCodeFromErrtype(err error) int {
switch err.(type) {
case NotFound:
return http.StatusNotFound
case AlreadyExists:
return http.StatusConflict
case NotSupported:
return http.StatusNotImplemented
case NotModified:
return http.StatusNotModified
case InvalidCredentials:
return http.StatusUnauthorized
case PermissionDenied:
return http.StatusForbidden
case Locked:
return http.StatusLocked
case Aborted:
return http.StatusPreconditionFailed
case PreconditionFailed:
return http.StatusMethodNotAllowed
case InsufficientStorage:
return http.StatusInsufficientStorage
case BadRequest:
return http.StatusBadRequest
case PartialContent:
return http.StatusPartialContent
case ChecksumMismatch:
return StatusChecksumMismatch
default:
return http.StatusInternalServerError
}
}