Skip to content

Commit

Permalink
adjust to cs3 lock api update
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic committed Jan 28, 2022
1 parent caacee2 commit 2a3e1bf
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 69 deletions.
76 changes: 44 additions & 32 deletions internal/http/services/owncloud/ocdav/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors"
"github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/rgrpc/status"
rtrace "github.com/cs3org/reva/pkg/trace"
"github.com/rs/zerolog"
)
Expand Down Expand Up @@ -59,57 +60,68 @@ func (s *svc) handlePathDelete(w http.ResponseWriter, r *http.Request, ns string

func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) {

// FIXME use middleware to check if Header? no, we need to forward the If header so the backend can handle it properly
/*
ih, ok := parseIfHeader(r.Header.Get("If"))
if !ok {
return http.StatusBadRequest, errors.ErrInvalidIfHeader
}
*/
ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "delete")
defer span.End()

req := &provider.DeleteRequest{Ref: ref}

// FIXME the lock token is part of the application level protocol, it should be part of the DeleteRequest message not the outgoing context
ih, ok := parseIfHeader(r.Header.Get("If"))
if !ok {
w.WriteHeader(http.StatusBadRequest)
return // errors.ErrInvalidIfHeader
}
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
addLockIDToOpaque(req.Opaque, ih.lists[0].conditions[0].Token)
}

client, err := s.getClient()
if err != nil {
log.Error().Err(err).Msg("error getting grpc client")
w.WriteHeader(http.StatusInternalServerError)
return
}

ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "delete")
defer span.End()

req := &provider.DeleteRequest{Ref: ref}
res, err := client.Delete(ctx, req)
if err != nil {
span.RecordError(err)
log.Error().Err(err).Msg("error performing delete grpc request")
w.WriteHeader(http.StatusInternalServerError)
return
} else if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
w.WriteHeader(http.StatusNotFound)
// TODO path might be empty or relative...
m := fmt.Sprintf("Resource %v not found", ref.Path)
b, err := errors.Marshal(http.StatusNotFound, m, "")
errors.HandleWebdavError(&log, w, b, err)
}
if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED {
w.WriteHeader(http.StatusForbidden)
// TODO path might be empty or relative...
m := fmt.Sprintf("Permission denied to delete %v", ref.Path)
b, err := errors.Marshal(http.StatusForbidden, m, "")
errors.HandleWebdavError(&log, w, b, err)
}
switch res.Status.Code {
case rpc.Code_CODE_OK:
w.WriteHeader(http.StatusNoContent)
case rpc.Code_CODE_NOT_FOUND:
w.WriteHeader(http.StatusNotFound)
// TODO path might be empty or relative...
m := fmt.Sprintf("Resource %v not found", ref.Path)
b, err := errors.Marshal(http.StatusNotFound, m, "")
errors.HandleWebdavError(&log, w, b, err)
case rpc.Code_CODE_PERMISSION_DENIED:
status := http.StatusForbidden
if lockID := readLockFromOpaque(res.Opaque); lockID != "" {
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We add angle brackets.
w.Header().Set("Lock-Token", "<"+lockID+">")
status = http.StatusLocked
}
if res.Status.Code == rpc.Code_CODE_INTERNAL && res.Status.Message == "can't delete mount path" {
w.WriteHeader(status)
// TODO path might be empty or relative...
m := fmt.Sprintf("Permission denied to delete %v", ref.Path)
b, err := errors.Marshal(status, m, "")
errors.HandleWebdavError(&log, w, b, err)
case rpc.Code_CODE_INTERNAL:
if res.Status.Message == "can't delete mount path" {
w.WriteHeader(http.StatusForbidden)
b, err := errors.Marshal(http.StatusForbidden, res.Status.Message, "")
errors.HandleWebdavError(&log, w, b, err)
}

errors.HandleErrorStatus(&log, w, res.Status)
return
default:
status := status.HTTPStatusFromCode(res.Status.Code)
w.WriteHeader(status)
b, err := errors.Marshal(status, res.Status.Message, "")
errors.HandleWebdavError(&log, w, b, err)
}

w.WriteHeader(http.StatusNoContent)
}

func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID string) {
Expand Down
52 changes: 39 additions & 13 deletions internal/http/services/owncloud/ocdav/locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ import (
"go.opentelemetry.io/otel/attribute"
)

// From RFC4918 http://www.webdav.org/specs/rfc4918.html#lock-tokens
// This specification encourages servers to create Universally Unique Identifiers (UUIDs) for lock tokens,
// and to use the URI form defined by "A Universally Unique Identifier (UUID) URN Namespace" ([RFC4122]).
// However, servers are free to use any URI (e.g., from another scheme) so long as it meets the uniqueness
// requirements. For example, a valid lock token might be constructed using the "opaquelocktoken" scheme
// defined in Appendix C.
//
// Example: "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
//
// we stick to the recommendation and use the URN Namespace
const lockTokenPrefix = "urn:uuid:"

// TODO(jfd) implement lock
// see Web Distributed Authoring and Versioning (WebDAV) Locking Protocol:
// https://www.greenbytes.de/tech/webdav/draft-reschke-webdav-locking-latest.html
Expand Down Expand Up @@ -178,18 +190,13 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails
Ref: details.Root,
Lock: &provider.Lock{
Type: provider.LockType_LOCK_TYPE_EXCL,
Holder: &provider.Lock_User{
User: details.UserID, // no way to set an app lock? TODO maybe via the ownerxml
},
// TODO misuse MTime as expiration, until https://github.com/cs3org/cs3apis/pull/162 is merged
Mtime: &types.Timestamp{
User: details.UserID, // no way to set an app lock? TODO maybe via the ownerxml
//AppName: , // TODO use a urn scheme?
Expiration: &types.Timestamp{
Seconds: uint64(expiration.Unix()),
Nanos: uint32(expiration.Nanosecond()),
},
// FIXME send as opaque when Metadata is changed to an Opaque Map,
// we need it as part of the Lock so it will be persisted
// see https://github.com/cs3org/cs3apis/pull/162#issuecomment-1018580448
Metadata: token.String(),
LockId: lockTokenPrefix + token.String(), // can be a token or a Coded-URL
},
}
res, err := cls.client.SetLock(ctx, r)
Expand All @@ -199,7 +206,7 @@ func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails
if res.Status.Code != rpc.Code_CODE_OK {
return "", errtypes.NewErrtypeFromStatus(res.Status)
}
return token.String(), nil
return lockTokenPrefix + token.String(), nil
}

func (cls *cs3LS) Refresh(ctx context.Context, now time.Time, token string, duration time.Duration) (LockDetails, error) {
Expand Down Expand Up @@ -315,7 +322,27 @@ func parseDepth(s string) int {
}
return invalidDepth
}

func addLockIDToOpaque(o *types.Opaque, l string) {
if o == nil {
o = &types.Opaque{}
}
if o.Map == nil {
o.Map = map[string]*types.OpaqueEntry{}
}
o.Map["lockid"] = &types.OpaqueEntry{
Decoder: "plain",
Value: []byte(l),
}
}
func readLockFromOpaque(o *types.Opaque) string {
if o == nil || o.Map == nil {
return ""
}
if e, ok := o.Map["lockid"]; ok && e.Decoder == "plain" {
return string(e.Value)
}
return ""
}
func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) (retStatus int, retErr error) {
ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path))
defer span.End()
Expand Down Expand Up @@ -363,7 +390,7 @@ func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) (ret
if token == "" {
return http.StatusBadRequest, errors.ErrInvalidLockToken
}
ld, err = s.LockSystem.Refresh(ctx, now, token, duration) // TODO remove opaquelocktoken: or urn:uuid: prefix? or leave as is?
ld, err = s.LockSystem.Refresh(ctx, now, token, duration)
if err != nil {
if err == errors.ErrNoSuchLock {
return http.StatusPreconditionFailed, err
Expand Down Expand Up @@ -426,7 +453,6 @@ func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) (ret
created = true
}
*/
// TODO add opaquelocktoken: or urn:uuid: prefix? or leave as is?
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We add angle brackets.
w.Header().Set("Lock-Token", "<"+token+">")
Expand Down
20 changes: 10 additions & 10 deletions internal/http/services/owncloud/ocdav/propfind/propfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
// -3 indicates unlimited
quota := net.PropQuotaUnknown
size := fmt.Sprintf("%d", md.Size)
var lock *props.LockDiscovery
var lock *provider.Lock
// TODO refactor helper functions: GetOpaqueJSONEncoded(opaque, key string, *struct) err, GetOpaquePlainEncoded(opaque, key) value, err
// or use ok like pattern and return bool?
if md.Opaque != nil && md.Opaque.Map != nil {
Expand All @@ -613,7 +613,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
quota = string(md.Opaque.Map["quota"].Value)
}
if md.Opaque.Map["lock"] != nil && md.Opaque.Map["lock"].Decoder == "json" {
lock = &props.LockDiscovery{}
lock = &provider.Lock{}
err := json.Unmarshal(md.Opaque.Map["lock"].Value, lock)
if err != nil {
sublog.Error().Err(err).Msg("could not unmarshal locks json")
Expand Down Expand Up @@ -1098,7 +1098,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
return &response, nil
}

func activeLocks(log *zerolog.Logger, lock *props.LockDiscovery) string {
func activeLocks(log *zerolog.Logger, lock *provider.Lock) string {
if lock == nil || lock.Type == provider.LockType_LOCK_TYPE_INVALID {
return ""
}
Expand Down Expand Up @@ -1133,25 +1133,25 @@ func activeLocks(log *zerolog.Logger, lock *props.LockDiscovery) string {
// we currently only support depth infinity
activelocks.WriteString("<d:depth>Infinity</d:depth>")

if lock.UserID != nil {
if lock.User != nil {
// TODO document that we just invented cs3:user: to expose the cs3 userid via webdav
activelocks.WriteString("<d:owner><d:href>cs3:user:")
activelocks.WriteString(props.Escape(lock.UserID.OpaqueId + "@" + lock.UserID.Idp))
activelocks.WriteString(props.Escape(lock.User.OpaqueId + "@" + lock.User.Idp))
activelocks.WriteString("</d:href></d:owner>")
}
if lock.App != "" {
if lock.AppName != "" {
// TODO document that we just invented d:application and cs3:app: to expose the WOPI application in xml
activelocks.WriteString("<d:application><d:href>cs3:app:")
user := props.Escape(lock.App)
user := props.Escape(lock.AppName)
activelocks.WriteString(user)
activelocks.WriteString("</d:href></d:application>")
}
activelocks.WriteString("<d:timeout>")
activelocks.WriteString(expiration)
activelocks.WriteString("</d:timeout>")
if lock.LockID != "" {
activelocks.WriteString("<d:locktoken><d:href>opaquelocktoken:")
activelocks.WriteString(props.Escape(lock.LockID))
if lock.LockId != "" {
activelocks.WriteString("<d:locktoken><d:href>")
activelocks.WriteString(props.Escape(lock.LockId))
activelocks.WriteString("</d:href></d:locktoken>")
}
// lockroot is only used when setting the lock
Expand Down
14 changes: 0 additions & 14 deletions internal/http/services/owncloud/ocdav/props/props.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ package props
import (
"bytes"
"encoding/xml"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
)

// PropertyXML represents a single DAV resource property as defined in RFC 4918.
Expand Down Expand Up @@ -122,16 +118,6 @@ type Owner struct {
InnerXML string `xml:",innerxml"`
}

// FTXME remove once https://github.com/cs3org/cs3apis/pull/162 is merged
type LockDiscovery struct {
// Opaque
LockID string
Type provider.LockType
UserID *userpb.UserId
App string
Expiration *types.Timestamp
}

func Escape(s string) string {
for i := 0; i < len(s); i++ {
switch s[i] {
Expand Down

0 comments on commit 2a3e1bf

Please sign in to comment.